/**
 * SortControl — the library's sort disclosure (spec §02 → Sort; council SORT).
 *
 * A disclosure + non-modal popover, the same APG primitive as SettingsPopover
 * (docs/architecture/10 → "settings are … a disclosure popover") — deliberately
 * NOT an ARIA menu. It differs from SettingsPopover only in its **trigger**: per
 * NN/g recognition-over-recall the trigger shows the current state ("Modified ↓")
 * instead of a bare gear, so the user reads the order without opening the popover.
 * The visible text drops the "Sort:" verb prefix to stay compact in the narrow
 * library rail; the full name rides in `aria-label` (arch 10 §8). The body is a
 * labelled `group` of field buttons (radio
 * group), each showing a direction arrow on the active field.
 *
 * Selection model (spec §02): re-selecting the ACTIVE field flips direction;
 * selecting a DIFFERENT field switches to it and resets to descending — all
 * encapsulated in `SnippetStore.setSort`. Keyboard/focus (APG disclosure): the
 * shared `usePopover` machinery makes at most one popover open at a time;
 * Enter/Space toggle the trigger; Esc closes and returns focus to the trigger;
 * an outside click closes. The panel is portaled to <body> and positioned fixed
 * because the panes clip their content.
 */

import { createPortal } from 'react-dom';
import type { SortBy } from '@core/snippet-sort';
import { usePopover } from '../hooks/usePopover';
import { useSnippetStore } from '../stores/SnippetStore';
import styles from './SortControl.module.css';

const ID = 'library-sort';

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

/** Field labels in display order (the order the popover lists them). */
const FIELDS: ReadonlyArray<{ value: SortBy; label: string }> = [
  { value: 'modified', label: 'Modified' },
  { value: 'created', label: 'Created' },
  { value: 'name', label: 'Name' },
  { value: 'size', label: 'Size' },
];

function labelFor(by: SortBy): string {
  return FIELDS.find((f) => f.value === by)?.label ?? by;
}

export function SortControl() {
  const sortBy = useSnippetStore((s) => s.sortBy);
  const sortOrder = useSnippetStore((s) => s.sortOrder);
  const setSort = useSnippetStore((s) => s.setSort);
  const { open, toggle, triggerRef, setPopNode } = usePopover({
    id: ID,
    align: 'left',
    initialFocus: INITIAL_FOCUS,
  });

  const arrow = sortOrder === 'desc' ? '↓' : '↑';

  return (
    <div className={styles.wrap}>
      <button
        ref={triggerRef}
        type="button"
        className={styles.trigger}
        aria-expanded={open}
        aria-controls={ID}
        // Trigger shows current field + direction for recognition (council SORT,
        // NN/g #6) — but without a "Sort:" prefix, so it stays compact beside the
        // search box in the narrow library pane (the prefix wasted ~36px and
        // crowded search at the pane minimum). The accessible name keeps the verb
        // ("Sort by Modified, descending") via aria-label, since the trimmed
        // visible text ("Modified ↓") would otherwise read as a bare field name.
        aria-label={`Sort by ${labelFor(sortBy)}, ${sortOrder === 'desc' ? 'descending' : 'ascending'}`}
        title={`Sort by ${labelFor(sortBy)}, ${sortOrder === 'desc' ? 'descending' : 'ascending'}`}
        onClick={toggle}
      >
        {labelFor(sortBy)} <span aria-hidden="true">{arrow}</span>
      </button>
      {open &&
        createPortal(
          <div
            ref={setPopNode}
            id={ID}
            className={styles.pop}
            role="group"
            aria-label="Sort snippets"
          >
            <h4 className={styles.title}>Sort by</h4>
            {FIELDS.map((f) => {
              const active = f.value === sortBy;
              return (
                <button
                  key={f.value}
                  type="button"
                  className={`${styles.field} ${active ? styles.active : ''}`}
                  // The active field carries the direction in its accessible name
                  // so AT hears "Modified, descending", not just "Modified".
                  aria-label={
                    active
                      ? `${f.label}, ${sortOrder === 'desc' ? 'descending' : 'ascending'}`
                      : f.label
                  }
                  onClick={() => setSort(f.value)}
                >
                  <span>{f.label}</span>
                  {active && <span aria-hidden="true">{arrow}</span>}
                </button>
              );
            })}
          </div>,
          document.body,
        )}
    </div>
  );
}
