/**
 * SelectControl — the app's value picker: a disclosure trigger showing the current
 * choice plus a portaled, non-modal popover listing the options as plain buttons.
 *
 * This replaces native `<select>` elements wherever a control is part of a designed
 * surface: a native select's popup can't be token-styled and renders differently on
 * every browser/OS, which reads as a foreign object inside an otherwise consistent
 * UI (arch 10 §5 records the resolution). It is the same APG **disclosure**
 * primitive as SortControl/SettingsPopover — deliberately NOT an ARIA menu and not
 * a combobox; a short list of buttons needs neither's contract.
 *
 * Behaviour (the shared `usePopover` machinery): at most one popover is open
 * app-wide (`PopoverStore`); Esc closes and refocuses the trigger; an outside
 * pointer press closes; opening focuses the selected option (or the first);
 * Arrow/Home/End move focus through the options; choosing one fires `onSelect`,
 * closes, and refocuses the trigger. The panel is portaled to <body> and positioned
 * fixed so it escapes pane/modal overflow clipping, flipping above the trigger when
 * the viewport below is too short.
 *
 * The same control doubles as an **action picker** (e.g. "Add field to which
 * channel?"): pass no `value` and a custom `triggerContent`; `beforeOpen` lets the
 * caller intercept the click entirely (the armed-channel fast path).
 */

import { Fragment, type CSSProperties, type ReactNode } from 'react';
import { createPortal } from 'react-dom';
import { usePopover } from '../hooks/usePopover';
import styles from './SelectControl.module.css';

/** Land on the selected option (falling back to the first). */
const INITIAL_FOCUS = ['[aria-current="true"]', 'button'] as const;

export interface SelectControlOption<V extends string> {
  value: V;
  label: string;
  /** Optional secondary line (e.g. "replaces Ship Mode" on an occupied channel). */
  detail?: string;
  /**
   * Draw a group separator above this option (purely visual, `role="presentation"`;
   * keyboard order is untouched) — e.g. the chart-theme picker's boundary between
   * the user's themes and the preset roster.
   */
  dividerBefore?: boolean;
  /**
   * For an action row that opens a dialog instead of picking a value (e.g. the
   * theme picker's "Edit themes…"): sets `aria-haspopup` so AT announces that
   * activating discloses further UI, matching the "…" label convention.
   */
  hasPopup?: 'dialog';
  /**
   * Optional visual leading the option label — e.g. a color-scheme swatch strip
   * or gradient bar. Decorative (`aria-hidden`); the label carries the meaning.
   */
  preview?: ReactNode;
  /**
   * Style applied to the label text — e.g. render a font option's name in its
   * own family, so the option previews the value (the type analogue of a swatch).
   */
  labelStyle?: CSSProperties;
}

export interface SelectControlProps<V extends string> {
  /** Unique id — popover registry key and the panel's DOM id. */
  id: string;
  /** Accessible name for the control ("Aggregate for Y", "Add Region to…"). */
  label: string;
  options: ReadonlyArray<SelectControlOption<V>>;
  /** Current value; omit for an action picker (no option reads as selected). */
  value?: V;
  onSelect: (value: V) => void;
  /** Trigger body; defaults to the current option's label plus a caret. */
  triggerContent?: ReactNode;
  /** Replaces (not extends) the default trigger styling — for chip-styled triggers. */
  triggerClassName?: string;
  triggerTitle?: string;
  disabled?: boolean;
  /** Popover heading; defaults to `label`. */
  heading?: string;
  /** Return false to swallow the trigger click without opening (fast paths). */
  beforeOpen?: () => boolean;
}

export function SelectControl<V extends string>({
  id,
  label,
  options,
  value,
  onSelect,
  triggerContent,
  triggerClassName,
  triggerTitle,
  disabled,
  heading,
  beforeOpen,
}: SelectControlProps<V>) {
  const { open, toggle, closeAndRefocus, triggerRef, popRef, setPopNode } = usePopover({
    id,
    align: 'left',
    flip: true,
    initialFocus: INITIAL_FOCUS,
  });

  const current = value !== undefined ? options.find((o) => o.value === value) : undefined;

  // Arrow/Home/End roving among the option buttons — a convenience on top of the
  // natural Tab order, matching what a native select's popup offers.
  const onPopKeyDown = (e: React.KeyboardEvent) => {
    const pop = popRef.current;
    if (!pop) return;
    // Tab closes the popup and resumes tabbing from the trigger (the native-select
    // convention) — also keeps focus inside a host modal's trap, since the panel is
    // portaled outside it.
    if (e.key === 'Tab') {
      closeAndRefocus();
      return;
    }
    const items = Array.from(pop.querySelectorAll<HTMLButtonElement>('button'));
    const i = items.indexOf(document.activeElement as HTMLButtonElement);
    let next = -1;
    if (e.key === 'ArrowDown') next = i < 0 ? 0 : Math.min(i + 1, items.length - 1);
    else if (e.key === 'ArrowUp') next = i < 0 ? items.length - 1 : Math.max(i - 1, 0);
    else if (e.key === 'Home') next = 0;
    else if (e.key === 'End') next = items.length - 1;
    if (next >= 0) {
      e.preventDefault();
      items[next]?.focus();
    }
  };

  const choose = (v: V) => {
    onSelect(v);
    closeAndRefocus();
  };

  return (
    <>
      <button
        ref={triggerRef}
        type="button"
        className={triggerClassName ?? styles.trigger}
        aria-expanded={open}
        aria-controls={open ? id : undefined}
        aria-label={current ? `${label}: ${current.label}` : label}
        title={triggerTitle}
        disabled={disabled}
        onClick={() => {
          if (beforeOpen && !beforeOpen()) return;
          toggle();
        }}
      >
        {triggerContent ?? (
          <>
            <span className={styles.triggerLabel}>{current?.label ?? '—'}</span>
            <span className={styles.caret} aria-hidden="true">
              ▾
            </span>
          </>
        )}
      </button>
      {open &&
        createPortal(
          <div
            ref={setPopNode}
            id={id}
            className={styles.pop}
            role="group"
            aria-label={label}
            onKeyDown={onPopKeyDown}
          >
            <h4 className={styles.heading}>{heading ?? label}</h4>
            <div className={styles.list}>
              {options.map((o) => {
                const selected = value !== undefined && o.value === value;
                return (
                  <Fragment key={o.value}>
                    {o.dividerBefore && <div className={styles.divider} role="presentation" />}
                    <button
                      type="button"
                      className={`${styles.option} ${selected ? styles.selected : ''}`}
                      aria-current={selected || undefined}
                      aria-haspopup={o.hasPopup}
                      onClick={() => choose(o.value)}
                    >
                      {o.preview !== undefined && (
                        <span className={styles.optionPreview} aria-hidden="true">
                          {o.preview}
                        </span>
                      )}
                      <span className={styles.optionLabel} style={o.labelStyle}>
                        {o.label}
                        {o.detail !== undefined && (
                          <span className={styles.detail}>{o.detail}</span>
                        )}
                      </span>
                      {selected && <span aria-hidden="true">✓</span>}
                    </button>
                  </Fragment>
                );
              })}
            </div>
          </div>,
          document.body,
        )}
    </>
  );
}
