"use client";

import * as React from "react";
import { SearchIcon, XIcon } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select";
import { Spinner } from "@/components/ui/spinner";
import { Surface } from "./surface";
import { cn } from "@/lib/utils";

export type SettingsSection = {
  id: string;
  title: string;
  description?: string;
  /** Labels of the settings inside, so "receipt footer" finds the Receipts section. */
  keywords?: string[];
  content: React.ReactNode;
  /** false when the content brings its own container (a table): it is the surface, so the section isn't one too. */
  surface?: boolean;
};

const now = () => Date.now();

const matches = (s: SettingsSection, q: string) => {
  const t = q.trim().toLowerCase();
  if (!t) return true;
  return [s.title, s.description ?? "", ...(s.keywords ?? [])].some((x) => x.toLowerCase().includes(t));
};

/**
 * Settings page (spec 03 §11.7): a section list beside one long page of sections, so related settings are
 * read together and saved once. The section list is in-page anchors with the current section marked as you
 * scroll; "Find a setting" narrows it to sections whose names or settings match. Below about 768 px the
 * list becomes a select. One save bar for the page, shown only when something changed; it saves every
 * changed section together.
 *
 * `scrollRoot` is the element that scrolls (the window when omitted).
 */
export function SettingsLayout({
  sections,
  changedSections = [],
  saving = false,
  onSave,
  onDiscard,
  scrollRoot,
}: {
  sections: SettingsSection[];
  /** Titles of sections with unsaved changes. Empty: no save bar. */
  changedSections?: string[];
  saving?: boolean;
  onSave: () => void;
  onDiscard: () => void;
  scrollRoot?: React.RefObject<HTMLElement | null>;
}) {
  const [query, setQuery] = React.useState("");
  const [active, setActive] = React.useState(sections[0]?.id);
  const shown = React.useMemo(() => sections.filter((s) => matches(s, query)), [sections, query]);
  const dirty = changedSections.length > 0;
  const clickedAt = React.useRef(0);

  React.useEffect(() => {
    const root = scrollRoot?.current;
    const spy = () => {
      // A click in the section list wins while its scroll settles (short last sections may never reach the top).
      if (now() - clickedAt.current < 1000) return;
      const top = (root ? root.getBoundingClientRect().top : 0) + 96;
      let current = shown[0]?.id;
      for (const s of shown) {
        const el = document.getElementById(`settings-${s.id}`);
        if (el && el.getBoundingClientRect().top <= top) current = s.id;
      }
      // At the very end the last sections can't reach the top: mark the last one.
      const atEnd = root ? root.scrollTop + root.clientHeight >= root.scrollHeight - 2 : window.scrollY + window.innerHeight >= document.documentElement.scrollHeight - 2;
      if (atEnd && (root ? root.scrollTop > 0 : window.scrollY > 0)) current = shown[shown.length - 1]?.id;
      setActive(current);
    };
    spy();
    window.addEventListener("scroll", spy, { capture: true, passive: true });
    return () => window.removeEventListener("scroll", spy, { capture: true });
  }, [scrollRoot, shown]);

  const go = (id: string) => {
    const el = document.getElementById(`settings-${id}`);
    if (!el) return;
    el.scrollIntoView({ behavior: "smooth", block: "start" });
    setActive(id);
    clickedAt.current = now();
    // Move focus to the section heading so keyboard and screen-reader users land there too.
    el.querySelector<HTMLElement>("h2")?.focus({ preventScroll: true });
  };

  return (
    <div className="@container">
      <div className="grid gap-6 @3xl:grid-cols-[13rem_minmax(0,1fr)]">
        {/* Section list */}
        <div className="space-y-3 @3xl:sticky @3xl:top-4 @3xl:self-start">
          <div className="relative">
            <SearchIcon className="pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground" aria-hidden />
            <Input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Find a setting" aria-label="Find a setting" className="pr-8 pl-8" />
            {query ? (
              <button type="button" aria-label="Clear search" onClick={() => setQuery("")} className="absolute top-1/2 right-1.5 flex size-6 -translate-y-1/2 cursor-pointer items-center justify-center rounded-sm text-muted-foreground hover:text-foreground">
                <XIcon className="size-3.5" />
              </button>
            ) : null}
          </div>
          <nav aria-label="Sections" className="hidden @3xl:block">
            <ul className="space-y-0.5">
              {shown.map((s) => (
                <li key={s.id}>
                  <a
                    href={`#settings-${s.id}`}
                    onClick={(e) => {
                      e.preventDefault();
                      go(s.id);
                    }}
                    aria-current={active === s.id ? "location" : undefined}
                    className={cn(
                      "flex items-center justify-between rounded-md border-l-2 px-2.5 py-1.5 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
                      active === s.id
                        ? "border-l-primary bg-[var(--color-bg-selected)] font-medium text-primary"
                        : "border-l-transparent text-muted-foreground hover:bg-[var(--color-bg-hover)] hover:text-foreground",
                    )}
                  >
                    {s.title}
                    {changedSections.includes(s.title) ? <span className="size-1.5 rounded-full bg-primary" aria-label="unsaved changes" /> : null}
                  </a>
                </li>
              ))}
            </ul>
          </nav>
          <div className="@3xl:hidden">
            <NativeSelect aria-label="Go to section" value={active} onChange={(e) => go(e.target.value)} className="w-full">
              {shown.map((s) => (
                <NativeSelectOption key={s.id} value={s.id}>
                  {s.title}
                </NativeSelectOption>
              ))}
            </NativeSelect>
          </div>
          <p className="sr-only" aria-live="polite">
            {query ? `${shown.length} ${shown.length === 1 ? "section matches" : "sections match"}` : ""}
          </p>
        </div>

        {/* Sections */}
        <div className="min-w-0 space-y-10">
          {shown.length ? (
            shown.map((s) => {
              const body = (
                <>
                  <div className="space-y-1">
                    <h2 id={`settings-${s.id}-h`} tabIndex={-1} className="text-lg leading-6 font-semibold outline-none">
                      {s.title}
                    </h2>
                    {s.description ? <p className="text-sm text-muted-foreground">{s.description}</p> : null}
                  </div>
                  {s.content}
                </>
              );
              const props = { id: `settings-${s.id}`, "aria-labelledby": `settings-${s.id}-h`, className: "max-w-[720px] scroll-mt-4 space-y-4" };
              // Each settings group on its own white surface (card spec C39), unless its content is its own container
              return s.surface === false ? (
                <section key={s.id} {...props}>
                  {body}
                </section>
              ) : (
                <Surface as="section" density="comfortable" key={s.id} {...props}>
                  {body}
                </Surface>
              );
            })
          ) : (
            <div className="rounded-lg border border-dashed border-border p-8 text-center">
              <p className="text-sm font-medium">No settings match &ldquo;{query}&rdquo;</p>
              <p className="mt-1 text-sm text-muted-foreground">Try another word, such as receipt, tax or password.</p>
              <Button size="sm" variant="outline" className="mt-3" onClick={() => setQuery("")}>
                Show all settings
              </Button>
            </div>
          )}
        </div>
      </div>

      {/* Save bar: only when something changed; side padding is density.comfortable.panel-padding (action bar spec 12.5); the shadow is shadow.overlay cast upwards, so the bar lifts off the cards under it */}
      {dirty ? (
        <div role="region" aria-label="Unsaved changes" className="sticky bottom-0 z-10 mt-6 flex flex-wrap items-center gap-3 border-t border-[var(--color-border-subtle)] bg-card px-[var(--density-comfortable-panel-padding)] py-3 shadow-[0_-4px_12px_-2px_#0011101F,0_-2px_4px_-2px_#0011100F]">
          <p className="mr-auto text-sm">
            <span className="font-medium">Unsaved changes</span>
            <span className="text-muted-foreground"> in {changedSections.join(", ")}</span>
          </p>
          <Button variant="ghost" onClick={onDiscard} disabled={saving}>
            Discard
          </Button>
          <Button onClick={onSave} disabled={saving}>
            {saving ? <Spinner /> : null}
            {saving ? "Saving…" : "Save changes"}
          </Button>
        </div>
      ) : null}
    </div>
  );
}
