/**
 * SettingsPopover — the per-pane settings disclosure (spec §07; arch 10).
 *
 * Astrolabe distributes preferences to where they apply: a small gear button in a
 * pane's toolbar discloses a popover of **live** controls for that cluster
 * (editor / preview / library). This is the shared primitive behind all of them.
 *
 * It is a **disclosure + non-modal popover** (WAI-ARIA APG disclosure; Carbon
 * popover), deliberately NOT an ARIA menu: a menu lists actions/commands
 * (menuitem/checkbox/radio), whereas these panels hold sliders, number/text
 * inputs, and radio groups — so the container is a labelled `group`, not a
 * `menu`. The gear carries `aria-expanded` + `aria-controls`; Enter/Space toggle;
 * Esc closes and returns focus to the gear; an outside click closes. Non-modal,
 * so there is no focus trap (unlike the modal shell). At most one popover is open
 * at a time, and any can be opened imperatively (the Cmd/Ctrl+, shortcut).
 *
 * The panel is **portaled to `document.body`** and positioned `fixed` from the
 * gear's rect, because the panes clip their content (`overflow: auto/hidden`); an
 * in-flow absolute popover would be cut off. Position re-measures on scroll/resize.
 */

import { type ReactNode } from 'react';
import { createPortal } from 'react-dom';
import { usePopover } from '../hooks/usePopover';
import { Icon } from './Icon';
import { IconButton } from './IconButton';
import styles from './SettingsPopover.module.css';

/** Focus the first interactive control on open (any kind — these are forms). */
const INITIAL_FOCUS = ['input, button, select, textarea, [tabindex]'] as const;

export interface SettingsPopoverProps {
  /** Stable id, also the popover's element id for `aria-controls` (e.g. 'editor-settings'). */
  id: string;
  /** Accessible name for the gear button. */
  label: string;
  /** Heading shown at the top of the popover, and its group label. */
  title: string;
  /** Which edge of the gear the popover aligns to (default right). */
  align?: 'left' | 'right';
  children: ReactNode;
}

export function SettingsPopover({
  id,
  label,
  title,
  align = 'right',
  children,
}: SettingsPopoverProps) {
  const { open, toggle, triggerRef, setPopNode } = usePopover({
    id,
    align,
    initialFocus: INITIAL_FOCUS,
  });

  return (
    <div className={styles.wrap}>
      <IconButton
        ref={triggerRef}
        label={label}
        aria-expanded={open}
        aria-controls={id}
        onClick={toggle}
      >
        <Icon name="settings" />
      </IconButton>
      {open &&
        createPortal(
          <div ref={setPopNode} id={id} className={styles.pop} role="group" aria-label={title}>
            <h4 className={styles.title}>{title}</h4>
            {children}
          </div>,
          document.body,
        )}
    </div>
  );
}

/** A label + control row inside a settings popover (keeps clusters tidy + uniform). */
export function SettingRow({
  label,
  htmlFor,
  children,
}: {
  label: string;
  htmlFor?: string;
  children: ReactNode;
}) {
  return (
    <div className={styles.row}>
      {htmlFor ? (
        <label className={styles.label} htmlFor={htmlFor}>
          {label}
        </label>
      ) : (
        <span className={styles.label}>{label}</span>
      )}
      <div className={styles.control}>{children}</div>
    </div>
  );
}

/** A right-aligned footer row for popover-level actions (e.g. Reset). */
export function SettingFooter({ children }: { children: ReactNode }) {
  return <div className={styles.footer}>{children}</div>;
}

/** A range slider with a live value read-out (font size, render debounce, …). */
export function RangeControl({
  id,
  min,
  max,
  step = 1,
  value,
  suffix,
  onChange,
}: {
  id: string;
  min: number;
  max: number;
  step?: number;
  value: number;
  suffix?: string;
  onChange: (value: number) => void;
}) {
  return (
    <>
      <input
        id={id}
        type="range"
        className={styles.range}
        min={min}
        max={max}
        step={step}
        value={value}
        onChange={(e) => onChange(Number(e.target.value))}
      />
      <span className={styles.value}>
        {value}
        {suffix ? ` ${suffix}` : ''}
      </span>
    </>
  );
}

/** A small integer number input (e.g. tab size). Coerces to a clamped integer. */
export function NumberControl({
  id,
  min,
  max,
  value,
  onChange,
}: {
  id: string;
  min: number;
  max: number;
  value: number;
  onChange: (value: number) => void;
}) {
  return (
    <input
      id={id}
      type="number"
      className={styles.number}
      min={min}
      max={max}
      step={1}
      value={value}
      onChange={(e) =>
        onChange(Math.min(max, Math.max(min, Math.round(Number(e.target.value) || min))))
      }
    />
  );
}

/** A monospaced free-text input (e.g. the custom date format). */
export function TextControl({
  id,
  value,
  placeholder,
  onChange,
}: {
  id: string;
  value: string;
  placeholder?: string;
  onChange: (value: string) => void;
}) {
  return (
    <input
      id={id}
      type="text"
      className={styles.text}
      value={value}
      placeholder={placeholder}
      spellCheck={false}
      onChange={(e) => onChange(e.target.value)}
    />
  );
}

/** A text-style reset action for a popover footer. */
export function ResetButton({ onClick, children }: { onClick: () => void; children: ReactNode }) {
  return (
    <button type="button" className={styles.reset} onClick={onClick}>
      {children}
    </button>
  );
}
