import Button from '../atoms/Button.jsx'
import Dropdown from '../molecules/Dropdown.jsx'
import CloseButton from '../atoms/CloseButton.jsx'
import Divider from '../atoms/Divider.jsx'
import IconFrame from '../atoms/IconFrame.jsx'
import LabeledControl from '../molecules/LabeledControl.jsx'
import ToggleSwitch from '../atoms/ToggleSwitch.jsx'
import Rail from '../molecules/Rail.jsx'
import FullscreenOverlay from '../utilities/FullscreenOverlay.jsx'
import { Tooltip } from '../utilities/Popover.jsx'
import { RailHeader } from './RailKit.jsx'

/**
 * SettingsPanel — Settings beside the thing they change. a settings surface for the thing you are looking at: you
 * change how a list looks WHILE looking at it, so it is a slide-over (or an
 * overlay), never a route (SettingsPanel, kol-r2b2 2026-08-26 — the
 * per-bucket display settings: kinds allow-list, structure, loading, layout).
 *
 * TWO PRESENTATIONS, ONE ANATOMY (the user's ask): `variant="rail"` is a
 * right-anchored rail over a scrim, `variant="overlay"` the same content
 * centred. Only the shell differs; header · intro · sections · footer are the
 * same nodes in both. Neither shell is built here — the rail IS `Rail`
 * (scrim, Escape, focus trap, scroll lock, focus return, the × control) and
 * the overlay IS FullscreenOverlay (scrim, Escape, backdrop dismiss, scroll
 * lock, the corner ×). The source had none of that: scrim click was the only
 * exit, no Escape, no trap, no lock, and a hand-typed `rgba(0,0,0,0.6)` scrim
 * where the DS already owned one.
 *
 * The CONTROLS are the DS controls, not the source's word-buttons: a switch
 * row is `ToggleSwitch`, a choice row is a `Dropdown` (2026-08-27) — the source's
 * segmented variant was flagged by the user as the thing to fix here, not to
 * copy. Sections are the DS `Section` (pass `divided` on every one and the
 * hairline lands between siblings on its own).
 *
 * Text casing is authored at the call site (no text-transform) — the source
 * mixed `Show kinds` / `on` / `grid` in one panel; write them how they should read.
 *
 * @param {boolean}  open      mounted and shown (default true — the parent
 *                             usually conditionally renders it)
 * @param {'rail'|'overlay'} variant  presentation — `rail` (default) or `overlay`, the only two names (user 2026-10-18);
 *                                     `drawer` is the old name of `rail`, still accepted
 * @param {'app'|'site'} tier   THE RAIL'S TIER (RailKit, kol-monitor 2026-10-24; default `app`): its width and padding are
 *                            the tier's tokens, the same the page rails read — `app` 320, `site` 380, the overlay inset.
 *                            The 380 of 2026-08-27 is the site tier's; an app's settings rail is as wide as its other rails
 * @param {'settings'|'page'} role  THE RAIL'S ROLE (RailRoles, 2026-10-25; default `settings`): `page` for a page's own settings rail
 *                            (monitor's Stage, Rack — H), which is this same shell under the page's key; `Rail role` carries it
 * @param {number|string} width  width — overrides the tier's for the rail; the ⌘K overlay's 32rem for the overlay
 * @param {string}   title     header line 1
 * @param {string}   subtitle  header line 2 (the approved drawer passes none)
 * @param {string}   intro     a note under the header (the approved drawer passes none)
 * @param {Function} onClose   × · scrim · Escape
 * @param {ReactNode} footer   the foot slot — `SettingsFooter` is the default shape
 * @param {ReactNode} headerActions  controls on the header line, before the × (the file rail's edit pencil, 2026-10-23)
 * @param {boolean}  backdrop  the rail's scrim — unset, THE ROLE decides (review 2, 2026-10-25: `settings` dims and locks the page,
 *                            `page` does not — *"H does not include dark overlay, that is only settings"*). `false`
 *                            is the Rail's own: the panel alone, no scrim, no scroll lock, no focus trap — a PAGE-LEVEL rail
 *                            over a live page, the bottom rail's setup (SettingsPanelBackdrop, kol-monitor 2026-10-24: the
 *                            rack's and the stage's settings open over the running picture and its keys keep working).
 *                            `rail` only; the overlay keeps its scrim
 * @param {'over'|'push'} mode  the Rail's own (default `over`): `over` slides over the page, `push` sits in the layout and
 *                            pushes it — inline, no scrim, no trap. Forwarded with `backdrop` (same ticket); `rail` only
 * @param {ReactNode} children the sections
 */
export default function SettingsPanel({
  open = true,
  variant = 'rail',
  tier = 'app',
  role = 'settings',
  width: widthProp,
  title,
  subtitle,
  intro,
  onClose,
  footer,
  headerActions,
  backdrop,
  mode = 'over',
  children,
  className = '',
}) {
  /* THE APP REGISTER (SettingsPanelChromeAndColumnPreview, kol-r2b2 2026-08-27 — user: "all wrong font styles"): the
   * collection beside the panel is the reference — helper-14 uppercase titles, the eyebrow for section labels, mono-12
   * rows and hints. The header is the kit's `RailHeader` (RailKit, 2026-10-24): the same node every page rail wears. */
  const header = <RailHeader title={title} subtitle={subtitle} actions={headerActions} />
  const body = (
    <>
      {intro && <p className="kol-mono-12 text-meta">{intro}</p>}
      <div className="flex flex-col gap-5">{children}</div>
    </>
  )

  if (variant === 'overlay') {
    if (!open) return null
    /* THE DRAWER'S ANATOMY IN A CENTRED PANEL (user 2026-10-18: *"wrong width wrong close placement, header is
     * bullshit"*): the ⌘K overlay's width, the × on the header's own line as the rail has it, a Divider under the
     * header — it was 380 wide with the × floating in a lane above the title and nothing under the header */
    const width = widthProp ?? 'min(32rem, calc(100vw - 32px))'
    return (
      <FullscreenOverlay open scrim closeButton={false} onClose={onClose}>
        <div
          className={`kol-overlay-panel flex flex-col p-[var(--kol-pad-overlay)] ${className}`}
          style={{ width: typeof width === 'number' ? `${width}px` : width, maxWidth: '100%', maxHeight: '85vh', overflowY: 'auto' }}
        >
          <div className="mb-6 flex items-center gap-4">
            <div className="min-w-0 flex-1">{header}</div>
            <CloseButton onClick={onClose} hover={false} className="shrink-0" />
          </div>
          <div className="flex flex-col gap-4"><Divider />{body}{footer && <div className="mt-2">{footer}</div>}</div>
        </div>
      </FullscreenOverlay>
    )
  }

  /* no 380 here any more: the width is the tier's token unless the caller sets one (RailKit) */
  const width = widthProp
  return (
    /* THE APPROVED DRAWER (SettingsPanelApproved, kol-r2b2 2026-08-27 — user: "LOCK
       THIS"): no edge, no shadow, the Rail's hairline under the header, the sections.
       THE SCRIM IS BACK (settings-drawer-has-no-surface, kol-client-olina
       2026-09-03). `backdrop={false}` stood here from 2026-08-30; the drawer
       paints `bg-surface-primary`, the same token as the page, so the scrim was
       the only thing separating them — with it off the panel was invisible and
       the controls floated in the right third of the screen.
       `backdrop` FORWARDS (SettingsPanelBackdrop, kol-monitor 2026-10-24): the default keeps olina's scrim; a page-level
       rail passes `false` and gets the Rail's own no-scrim setup — the page keeps running under it. */
    <Rail role={role} open={open} onClose={onClose} side="right" variant={tier} width={width} header={header} className={className} edge={false} shadow={false} closeHover={false} backdrop={backdrop} mode={mode}
      /* the foot is the Rail's pinned slot now (RailKit): reset and the verbs stay in reach while the sections scroll */
      footer={footer}>
      {/* the hairline under the header is the Rail's now (the standard header, 2026-10-25) — every rail has it */}
      <div className="flex flex-col gap-4">{body}</div>
    </Rail>
  )
}

/**
 * SettingsRow — One labelled setting. a `LabeledControl inline` (SettingsPanelApproved, 2026-08-27):
 * uppercase label (`kol-helper-10` tracked, meta ink) in a 160px column, the
 * control fills the rest. A switch sits at the far right (`align="end"`, the
 * default); a dropdown fills the row (`align="fill"`). No hint sentences on the
 * page — `hint` rides the control's DS `Tooltip` (a native `title` until
 * 2026-09-22, kol-client-olina). `labelWidth="auto"` passes through
 * to LabeledControl: the label flexes and truncates, the control hugs — for a
 * row living in a column narrower than the 160px label default
 * (SettingsShortcutsComboOverflow, kol-monitor 2026-09-01). `text` is a row whose value is text — the same label,
 * on the baseline, as tall as its text (LabeledControl's `text`; SettingsRowMonoBaseline, kol-monitor 2026-10-18).
 */
export function SettingsRow({ label, hint, align = 'end', labelWidth = 160, text = false, children }) {
  return (
    <LabeledControl inline text={text} label={typeof label === 'string' ? label.toUpperCase() : label} labelWidth={labelWidth}>
      {hint
        ? <Tooltip label={hint} triggerClassName={`inline-flex w-full ${align === 'fill' ? '' : 'justify-end'}`.trim()}>{children}</Tooltip>
        : <span className={`inline-flex w-full ${align === 'fill' ? '' : 'justify-end'}`.trim()}>{children}</span>}
    </LabeledControl>
  )
}

/** SettingsSwitch — An on/off setting. the row's on/off control: the DS ToggleSwitch, bare, sm.
 *  `disabledHint` rides a DS `Tooltip` so a switch that cannot act says why;
 *  inside a row's `hint` it wins while hovered, as the native `title` did. */
export function SettingsSwitch({ on = false, onChange, disabled = false, disabledHint, label, title }) {
  const tip = title ?? (disabled ? disabledHint : undefined)
  const toggle = (
    <ToggleSwitch
      size="sm"
      checked={on}
      onChange={onChange}
      disabled={disabled}
      aria-label={label}
    />
  )
  return tip ? <Tooltip label={tip}>{toggle}</Tooltip> : toggle
}

/** LabeledControlSection — A titled section of labelled controls. a section of LabeledControls: the EYEBROW
 *  (`kol-eyebrow text-fg-80`) standing apart from the rows (gap-3), the rows in
 *  their own stack — `rowGap` 1 for switch rows (24 tall already), 2 for
 *  dropdown rows. `divided` = a hairline above (between sections).
 *  Was `SettingsSection` until 2026-08-27 (LabeledControlSection, kol-r2b2 —
 *  user ruling: it named the place it was first used, not what it is; kol-fxr
 *  took it for a params rail the same day). RENAMED, NO ALIAS — the render is
 *  the 0.104.0 locked composition, untouched. */
export function LabeledControlSection({ label, divided = false, rowGap = 2, children, className = '' }) {
  return (
    <div className={`flex flex-col gap-3 ${divided ? 'kol-section--divided' : ''} ${className}`.replace(/\s+/g, ' ').trim()}>
      {label && <p className="kol-eyebrow text-fg-80">{label}</p>}
      <div className={`flex flex-col ${rowGap === 1 ? 'gap-1' : 'gap-2'}`}>{children}</div>
    </div>
  )
}

/** SettingsChoice — A one-of-many setting. the row's one-of-N control: the DS Dropdown, sm · primary
 *  (SettingsPanelEyebrowAndDropdowns — user: "put the toggles inside a dropdown,
 *  because it's super messy like it is"). Options are values or `{ value, label }`;
 *  width is the call site's (`className="w-40"`).
 *
 *  `tone` · `size` · `variant` pass through (kol-fxr 2026-08-28): they were
 *  hardcoded, so a settings page ruled sunken could not be told — and kol-fxr
 *  rebuilt this same Dropdown in a local `ChoiceRow`, which is the exact
 *  duplication this component exists to prevent. A component that wraps another
 *  has to forward the wrapped one's seams or it becomes a wall. */
/* `variant` UNSET by default (the Hub review, 2026-09-26): a forced `primary` blocked the
 * tone of a `kol-tone-*` wrapper, so a sunken settings page drew a light well in every row
 * unless the row said `tone="sunken"` itself. Unset, Dropdown takes the wrapper's tone and
 * falls back to primary — the same render wherever there is no wrapper. */
export function SettingsChoice({ options = [], value, onChange, ariaLabel, tone = 'default', size = 'sm', variant, className = '' }) {
  const opts = options.map((o) => (o != null && typeof o === 'object' ? o : { value: o, label: String(o) }))
  return <Dropdown size={size} variant={variant} tone={tone} value={value} onChange={onChange} options={opts} className={`w-full ${className}`.trim()} ariaLabel={ariaLabel} />
}

/* THE CONTROL CHIP (SettingsPanelCompliance, user 2026-08-27: "WROOOONG" on the
 * 0.98.0 Tag pills): `.kol-control` sm on kol-mono-12, filled when on, meta ink
 * when off, the count in meta after the label — the SAME string ContentFilters'
 * kind chips, ViewToggle's text variant and the SELECT / FLAT strip wear. No
 * Tag, no uppercase, no inverse. */
export const CHIP_CLS = 'kol-control kol-control-sm kol-mono-12'
export const chipCls = (on) => `${CHIP_CLS} ${on ? 'kol-control--filled' : 'text-meta hover:text-emphasis'}`

/**
 * SettingsMulti — A many-of-many setting in one dropdown. a many-of-N control as ONE Dropdown (SettingsPanelApproved,
 * 2026-08-27 — the kinds row): the trigger reads `N of M <noun>`, every entry
 * toggles and `✓` marks the ones that are on. No grouping.
 * @param {Array}  options   [{ value, label }]
 * @param {Array|Set} selected
 * @param {Function} onToggle (value) => void
 * @param {string} noun      the trigger's noun (default 'kinds')
 * @param {string} variant   passed to the Dropdown like `SettingsChoice`'s — unset inherits the page's
 *                           tone. It was hard-coded `primary` until 2026-09-29, so on a sunken settings
 *                           page it was the one lighter dropdown in the section.
 */
export function SettingsMulti({ options = [], selected = [], onToggle, noun = 'kinds', tone = 'default', size = 'sm', variant, className = '' }) {
  const on = selected instanceof Set ? selected : new Set(selected)
  const opts = [
    { value: '__summary', label: `${on.size} of ${options.length} ${noun}` },
    ...options.map((o) => ({ value: o.value, label: `${on.has(o.value) ? '✓ ' : ''}${o.label ?? String(o.value)}` })),
  ]
  return <Dropdown size={size} variant={variant} tone={tone} value="__summary" options={opts} onChange={(v) => (v === '__summary' ? null : onToggle?.(v))} className={`w-full ${className}`.trim()} />
}

/**
 * SettingsChipRow — A row of toggle chips. a wrap of toggle chips with optional counts (an
 * allow-list: every chip sets a default, never a gate). `allChip` puts an
 * "all" chip first — the same control chip, filled when every option is on
 * (SettingsPanelEyebrowAndDropdowns — user: "Show all in ghost mode? makes no
 * sense"); `onAll(nextAllOn)` fires when it is clicked.
 * @param {Array}  options   [{ value, label, count? }]
 * @param {Array|Set} selected  the values that are on
 * @param {Function} onToggle  (value) => void
 * @param {boolean|string} allChip  render the "all" chip (a string = its label)
 * @param {Function} onAll     (nextAllOn: boolean) => void
 */
export function SettingsChipRow({ options = [], selected = [], onToggle, allChip = false, onAll }) {
  const on = selected instanceof Set ? selected : new Set(selected)
  const allOn = options.length > 0 && options.every((o) => on.has(o.value))
  return (
    <div className="flex flex-wrap gap-1">
      {allChip && (
        <button type="button" aria-pressed={allOn} onClick={() => onAll?.(!allOn)} className={chipCls(allOn)}>
          {typeof allChip === 'string' ? allChip : 'all'}
        </button>
      )}
      {options.map((o) => (
        <button
          key={String(o.value)}
          type="button"
          aria-pressed={on.has(o.value)}
          onClick={() => onToggle?.(o.value)}
          className={chipCls(on.has(o.value))}
        >
          {o.label ?? String(o.value)}
          {o.count > 0 && <span className="text-meta"> {o.count}</span>}
        </button>
      ))}
    </div>
  )
}

/** SettingsFooter — Reset settings to their defaults. one `IconFrame refresh` (primary · sm) at the
 *  right = reset to defaults (SettingsPanelApproved, 2026-08-27). No status word,
 *  no text button. `customised` is accepted for compatibility and unused.
 *
 *  `children` ride the SAME row, before reset (BrowsePageRulingsAndSeams,
 *  kol-r2b2 2026-09-02): a consumer wanting one more control in that footer had
 *  no way in, so kol-r2b2 ran a `MutationObserver` on `document.body` watching
 *  for `[aria-label="Reset to defaults"]` and portalled a theme chip into its
 *  parent — a copy string in a `querySelector`, watching the whole document, to
 *  place one button. The slot is the seam that ends it.
 *
 *  `gap-2` on the row (user 2026-08-28): `flex justify-end` with no gap is right
 *  for one control and wrong for two — the chip and the reset icon touched. */
export function SettingsFooter({ onReset, resetLabel = 'Reset to defaults', resetDisabled = false, children }) {
  return (
    /* no Divider here since 2026-10-25: the rule is the Rail's foot band, full width */
    <div className="kol-ink-step flex justify-end items-center gap-2">
        {children}
        {/* `resetDisabled`: the control stays in its place, dimmed and inert — one footer shape on every rail, whether or
          * not the app has preferences to reset yet (user 2026-10-24: *"standardise what goes in"*) */}
        {(onReset || resetDisabled) && (
          <Tooltip label={resetDisabled ? 'Nothing to reset' : resetLabel}>
            <IconFrame name="refresh" variant="nav" size="sm" onClick={resetDisabled ? undefined : onReset} aria-label={resetLabel} aria-disabled={resetDisabled || undefined} className={resetDisabled ? 'opacity-40 pointer-events-none' : ''} />
          </Tooltip>
        )}
      </div>
  )
}
