import * as React from 'react';
import { Link } from 'react-router-dom';
import { ArrowRight, ArrowUpRight, GitBranch, Menu } from 'lucide-react';

import { VERTICAL_LABELS, VERTICAL_ORDER } from '@/lib/demo-kit/registry';
import type { Vertical } from '@/lib/demo-kit/types';
import { routes } from '@/content/lineup';
import { CORE_TERMS } from '@/content/glossary';
import { PROPOSAL_NOTICE } from '@/content/verticals';
import * as s from '@/content/styles';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
  navigationMenuTriggerStyle,
} from '@/components/ui/navigation-menu';
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from '@/components/ui/accordion';
import {
  Sheet,
  SheetBody,
  SheetClose,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from '@/components/ui/sheet';
import { ContrastToggle } from '@/components/site/ContrastToggle';
import { DemoIcon } from '@/components/site/DemoIcon';
import { environments, StatusPill } from '@/components/site/EnvironmentPicker';
import type { EnvironmentEntry } from '@/components/site/EnvironmentPicker';
import { PIG_URL, REPO_URL, VERIFIERS_WORDLE_URL } from '@/components/site/links';
import { ThemeToggle } from '@/components/site/ThemeToggle';
import { Wordmark } from '@/components/site/Wordmark';
import { cn } from '@/lib/utils';

/*
 * This header is GENERATED. Nothing in it names a demo.
 *
 * The Environments panel is the whole catalogue — the same `environments`
 * array the landing and the gallery render, so a demo that exists is in the
 * menu, in its vertical, and (if it is the first live one) behind the CTA,
 * with zero edits to this file. The only hand-written text here is chrome:
 * the labels of the four destinations and the quoted taskset.
 *
 * Built for fifty entries: rows are 40px, groups carry sticky mono headers
 * inside the panel's own scroll, and the count in each column header is
 * derived. A type-to-filter input is DEFERRED until the catalogue passes
 * about twenty entries — it is console plumbing the content does not yet
 * justify, and a search box over fourteen rows says "we could not organise
 * fourteen rows".
 */

/**
 * The real upstream taskset, quoted rather than paraphrased.
 *
 * A mega-menu that contains source code instead of more links is the cheapest
 * signal on the whole site that this is not a brochure — it is the first thing
 * a technical buyer sees, and it is true before they have clicked anything.
 */
const TASKSET_SOURCE = `class WordleConfig(TextArenaConfig):
    game: Literal["Wordle-v0"] = "Wordle-v0"

class WordleTaskset(TextArenaTaskset, vf.Taskset[TextArenaTask, WordleConfig]):
    pass`;

/* -------------------------------------------------------------- catalogue */

interface Group {
  key: Vertical | null;
  label: string;
  entries: EnvironmentEntry[];
}

/**
 * Entries grouped under their vertical, in `VERTICAL_ORDER`, keeping each
 * group's internal order (registry order for demos, lineup rank for
 * proposals). A proposal the contract has no key for lands in a final group so
 * it is reachable from the bar like everything else.
 */
function groupByVertical(entries: readonly EnvironmentEntry[]): Group[] {
  const buckets = new Map<Vertical | null, EnvironmentEntry[]>();
  for (const entry of entries) {
    const bucket = buckets.get(entry.verticalKey);
    if (bucket) bucket.push(entry);
    else buckets.set(entry.verticalKey, [entry]);
  }
  const groups: Group[] = VERTICAL_ORDER.flatMap((key) => {
    const bucket = buckets.get(key);
    return bucket ? [{ key, label: VERTICAL_LABELS[key], entries: bucket }] : [];
  });
  const rest = buckets.get(null);
  if (rest) groups.push({ key: null, label: 'Beyond the lineup', entries: rest });
  return groups;
}

interface Catalogue {
  live: EnvironmentEntry[];
  spec: EnvironmentEntry[];
  liveGroups: Group[];
  specGroups: Group[];
  /** The primary CTA: `Play {title}` for the first live environment. */
  cta: { href: string; label: string; short: string };
}

function useCatalogue(): Catalogue {
  return React.useMemo(() => {
    // `environments` is already ordered live-first, then registry specs, then
    // the ranked lineup. Not re-sorted here: if this file starts re-sorting,
    // the header and the gallery will eventually disagree about "first".
    const live = environments.filter((entry) => entry.status === 'live');
    const spec = environments.filter((entry) => entry.status === 'spec');
    const primary = live[0];
    // When nothing is live, the bar points at the catalogue rather than
    // promising a board that does not exist.
    const cta = primary
      ? { href: primary.href, label: `Play ${primary.title}`, short: 'Play' }
      : { href: routes.gallery, label: 'All environments', short: 'Browse' };
    return {
      live,
      spec,
      liveGroups: groupByVertical(live),
      specGroups: groupByVertical(spec),
      cta,
    };
  }, []);
}

/* ------------------------------------------------------------------- atoms */

/** The mono column / group label. */
function PanelLabel({
  children,
  className,
  sticky = false,
}: {
  children: React.ReactNode;
  className?: string;
  sticky?: boolean;
}) {
  return (
    <p
      className={cn(
        // Arbitrary size rather than `text-label`: this string goes through
        // `cn`, and tailwind-merge v2 reads an unknown `text-*` as a COLOUR, so
        // `text-label text-muted` would silently drop the size.
        'px-2 pb-1.5 pt-2 font-mono text-[0.6875rem] font-medium uppercase leading-[1.2] tracking-label text-muted',
        // Sticky inside the panel's own scroll, on the panel's own surface, so
        // the vertical a row belongs to is never off-screen at fifty entries.
        sticky && 'sticky top-0 z-10 bg-surface',
        className,
      )}
    >
      {children}
    </p>
  );
}

/**
 * Spreads `...rest` onto the Link, and that is not tidiness.
 *
 * Both `NavigationMenuLink asChild` and `SheetClose asChild` work by cloning
 * this element and merging their own `onClick` into it. A component that takes
 * `entry` and ignores everything else silently drops those handlers: the row
 * still navigates, and the mega panel or the mobile sheet stays open on top of
 * the page you just arrived at.
 */
function DemoRow({
  entry,
  className,
  ...rest
}: { entry: EnvironmentEntry } & Omit<React.ComponentProps<typeof Link>, 'to'>) {
  const isSpec = entry.status === 'spec';
  return (
    <Link
      to={entry.href}
      className={cn(
        // 44px below lg (the sheet), 40px from lg (the panel). Never both
        // visible at once, so one row serves both.
        'flex min-h-11 items-start gap-2.5 rounded-md px-2 py-1.5 transition-colors duration-1 ease-enter hover:bg-surface-2 lg:min-h-10',
        className,
      )}
      {...rest}
    >
      {entry.rank === null ? null : (
        <span className="w-5 shrink-0 pt-0.5 font-mono text-mono-data text-muted">
          {String(entry.rank).padStart(2, '0')}
        </span>
      )}
      <DemoIcon name={entry.icon} className="mt-0.5 size-4 text-border-strong" />
      <span className="flex min-w-0 flex-1 flex-col gap-0.5">
        <span className="flex flex-wrap items-center gap-x-2 gap-y-1">
          <span className="text-ui font-medium leading-tight text-fg">{entry.title}</span>
          <StatusPill status={entry.status} />
          {entry.note === null ? null : <Badge variant="muted">{entry.note}</Badge>}
        </span>
        <span className="text-small text-muted">
          {/*
            A spec row shows the BUYER, not the technology: "For the Head of
            Claims" says who is meant to care, where a taskset name says
            nothing to the person reading this in a boardroom.
          */}
          {isSpec ? `For the ${entry.persona}` : entry.tagline}
        </span>
      </span>
    </Link>
  );
}

/**
 * Groups of rows under sticky vertical labels. `wrap` is what turns a row into
 * a panel link or a sheet-closing link — the two contexts close differently.
 */
function GroupedRows({
  groups,
  wrap,
  empty,
}: {
  groups: Group[];
  wrap: (row: React.ReactElement, key: string) => React.ReactNode;
  empty: string;
}) {
  if (groups.length === 0) {
    return <p className="px-2 py-2 text-small text-muted">{empty}</p>;
  }
  return (
    <>
      {groups.map((group) => (
        <section key={group.key ?? 'none'} aria-label={group.label}>
          <PanelLabel sticky>{group.label}</PanelLabel>
          {group.entries.map((entry) => wrap(<DemoRow entry={entry} />, entry.id))}
        </section>
      ))}
    </>
  );
}

// `text-sm`, not `text-ui`: this string is composed through `cn`, where
// tailwind-merge v2 treats an unknown `text-*` as a colour and drops it.
const panelLink =
  'inline-flex min-h-10 items-center gap-1 rounded-md px-2 text-sm font-medium text-accent-fg underline-offset-4 hover:underline';

/* ------------------------------------------------------------------ panels */

function EnvironmentsPanel({ live, spec, liveGroups, specGroups }: Catalogue) {
  return (
    <div className="flex w-[min(92vw,880px)] flex-col">
      <div className="grid max-h-[70vh] grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] gap-4 overflow-y-auto overscroll-contain p-3">
        <section aria-label={`Live, ${live.length}`} className="flex flex-col">
          <PanelLabel className="text-fg">
            Live <span className="text-muted">({String(live.length).padStart(2, '0')})</span>
          </PanelLabel>
          <GroupedRows
            groups={liveGroups}
            empty="No playable environment yet."
            wrap={(row, key) => (
              <NavigationMenuLink asChild key={key}>
                {row}
              </NavigationMenuLink>
            )}
          />
        </section>

        <section aria-label={`Specifications, ${spec.length}`} className="flex flex-col border-l border-border pl-4">
          <PanelLabel className="text-fg">
            Spec <span className="text-muted">({String(spec.length).padStart(2, '0')})</span>
          </PanelLabel>
          <GroupedRows
            groups={specGroups}
            empty="Nothing written that is not built."
            wrap={(row, key) => (
              <NavigationMenuLink asChild key={key}>
                {row}
              </NavigationMenuLink>
            )}
          />
        </section>
      </div>

      <div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-2 border-t border-border bg-surface-2 px-4 py-2.5">
        <p className="text-small text-muted">
          Every playable environment replays a real recorded rollout.
        </p>
        <span className="flex items-center gap-3">
          <Badge variant="outline">{PROPOSAL_NOTICE}</Badge>
          <NavigationMenuLink asChild>
            <Link to={routes.gallery} className={panelLink}>
              All environments
              <ArrowRight aria-hidden="true" className="size-3.5" />
            </Link>
          </NavigationMenuLink>
        </span>
      </div>
    </div>
  );
}

function TasksetSource({ className }: { className?: string }) {
  return (
    <div className={cn('flex flex-col gap-2 rounded-md border border-border bg-surface-3 p-3', className)}>
      <p className="font-mono text-label uppercase tracking-label text-muted">The actual taskset</p>
      {/*
        Wrapped, not scrolled. The source is quoted verbatim — reformatting it
        to fit would make it stop being a quote — and the widest line is a third
        wider than this column at any font size worth reading. In a panel whose
        entire job is "this is real code", a visible wrap beats a third of the
        line hidden behind a scrollbar nobody in a boardroom will drag.
      */}
      <pre className="whitespace-pre-wrap break-words font-mono text-[0.6875rem] leading-relaxed text-fg [font-feature-settings:'liga'_0]">
        <code>{TASKSET_SOURCE}</code>
      </pre>
      <a
        href={VERIFIERS_WORDLE_URL}
        target="_blank"
        rel="noreferrer noopener"
        className="inline-flex min-h-10 w-fit items-center gap-1 font-mono text-mono-data text-accent-fg underline-offset-4 hover:underline"
      >
        verifiers/environments/wordle
        <ArrowUpRight aria-hidden="true" className="size-3.5" />
      </a>
    </div>
  );
}

function Glossary() {
  return (
    <dl className="flex flex-col gap-3">
      {CORE_TERMS.map((entry) => (
        <div key={entry.key} className="flex flex-col gap-0.5">
          <dt className="text-ui font-medium text-fg">{entry.term}</dt>
          <dd className="text-small text-fg-2">{entry.gloss}</dd>
        </div>
      ))}
    </dl>
  );
}

function HowItWorksPanel({ ctaHref }: { ctaHref: string }) {
  return (
    <div
      // 820px, not more: the panel hangs off the LEFT edge of the nav, which
      // starts about 100px in, so anything wider pushes past the right edge of
      // a 1024px laptop and gives the whole page a horizontal scrollbar.
      className="w-[min(90vw,820px)] p-4"
    >
      <div className="grid grid-cols-[minmax(0,0.85fr)_minmax(0,1fr)] gap-5">
        <div className="flex flex-col gap-3">
          <Glossary />
          <NavigationMenuLink asChild>
            <Link to={ctaHref} className={cn(panelLink, '-ml-2 mt-1 w-fit')}>
              See all five on one recorded run
              <ArrowRight aria-hidden="true" className="size-3.5" />
            </Link>
          </NavigationMenuLink>
        </div>
        <TasksetSource className="self-start" />
      </div>
    </div>
  );
}

/* ------------------------------------------------------------------ mobile */

const sheetRow =
  'tap flex items-center justify-between gap-3 border-b border-border py-3 text-ui font-medium text-fg';

function MobileNav(catalogue: Catalogue) {
  const { liveGroups, specGroups, cta } = catalogue;
  return (
    <Sheet>
      <SheetTrigger asChild>
        <Button variant="ghost" size="touch" className="px-3" aria-label="Open menu">
          <Menu aria-hidden="true" />
          <span aria-hidden="true">Menu</span>
        </Button>
      </SheetTrigger>
      {/*
        The sheet is a flex column: header, a scrolling body, then a pinned
        footer. Scrolling the BODY rather than the content root is what keeps a
        long lineup reachable on a short phone, and `overscroll-contain` on it
        stops the flick chaining into the page underneath.
      */}
      <SheetContent side="right" className="w-[min(92vw,24rem)] rounded-l-xl shadow-overlay">
        <SheetHeader>
          <SheetTitle className="font-mono text-label uppercase tracking-label text-muted">
            Menu
          </SheetTitle>
          <SheetDescription className="sr-only">
            Every environment, how they work, and the rest of the site.
          </SheetDescription>
        </SheetHeader>

        <SheetBody className="px-4 py-2">
          {/* Environments open by default: the menu's first job is the catalogue. */}
          <Accordion type="multiple" defaultValue={['environments']}>
            <AccordionItem value="environments">
              <AccordionTrigger>Environments</AccordionTrigger>
              <AccordionContent className="flex flex-col">
                <PanelLabel className="text-fg">Live</PanelLabel>
                <GroupedRows
                  groups={liveGroups}
                  empty="No playable environment yet."
                  wrap={(row, key) => (
                    <SheetClose asChild key={key}>
                      {row}
                    </SheetClose>
                  )}
                />
                <PanelLabel className="mt-2 text-fg">Spec</PanelLabel>
                <GroupedRows
                  groups={specGroups}
                  empty="Nothing written that is not built."
                  wrap={(row, key) => (
                    <SheetClose asChild key={key}>
                      {row}
                    </SheetClose>
                  )}
                />
                <SheetClose asChild>
                  {/* Explicit `min-h-11`, not `.tap`: `.tap` lives in the
                      components layer and the utility `min-h-10` in `panelLink`
                      would win over it, leaving a 40px target on a phone. */}
                  <Link to={routes.gallery} className={cn(panelLink, 'mt-1 min-h-11 w-fit')}>
                    All environments
                    <ArrowRight aria-hidden="true" className="size-3.5" />
                  </Link>
                </SheetClose>
              </AccordionContent>
            </AccordionItem>

            <AccordionItem value="how">
              <AccordionTrigger>How it works</AccordionTrigger>
              <AccordionContent className="flex flex-col gap-3">
                <Glossary />
                <TasksetSource />
              </AccordionContent>
            </AccordionItem>
          </Accordion>

          <SheetClose asChild>
            <Link to={routes.honesty} className={sheetRow}>
              Honesty
              <ArrowRight aria-hidden="true" className="size-4 text-border-strong" />
            </Link>
          </SheetClose>
          <a href={REPO_URL} target="_blank" rel="noreferrer noopener" className={sheetRow}>
            Source on Gitea
            <ArrowUpRight aria-hidden="true" className="size-4 text-border-strong" />
          </a>
          <a href={PIG_URL} target="_blank" rel="noreferrer noopener" className={sheetRow}>
            primeintellectgrowth.com
            <ArrowUpRight aria-hidden="true" className="size-4 text-border-strong" />
          </a>

          <div className="grid grid-cols-2 gap-2 py-3">
            <ThemeToggle labelled />
            <ContrastToggle labelled />
          </div>
        </SheetBody>

        <SheetFooter>
          <SheetClose asChild>
            <Button
              asChild
              size="lg"
              className="min-h-12 w-full border border-accent-edge text-[0.9375rem]"
            >
              <Link to={cta.href}>
                {cta.label}
                <ArrowRight aria-hidden="true" />
              </Link>
            </Button>
          </SheetClose>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}

/* ------------------------------------------------------------------ header */

export function SiteHeader() {
  const catalogue = useCatalogue();
  const { cta } = catalogue;

  return (
    <header
      // The height token already folds in --safe-top (56px + safe-top at
      // EVERY width), so the padding and the height come from the same source
      // and a notch cannot push the row off the bottom edge of the bar.
      className="sticky top-0 z-50 h-[var(--app-header-h)] border-b border-border bg-surface/85 pt-[var(--safe-top)] backdrop-blur-md"
    >
      <div className={cn(s.shell, 'flex h-full items-center gap-2')}>
        <Link
          to="/"
          className="tap -ml-2 flex shrink-0 items-center rounded-md px-2"
          aria-label="PIG demo, home"
        >
          <Wordmark />
        </Link>

        {/*
          `h-full` is not cosmetic. The viewport hangs off the Root's
          `top-full`, so a Root only as tall as its 36px triggers drops the
          panel INSIDE the header, over its own bottom border.
        */}
        <NavigationMenu className="hidden h-full lg:flex" delayDuration={120}>
          <NavigationMenuList>
            <NavigationMenuItem>
              <NavigationMenuTrigger>Environments</NavigationMenuTrigger>
              <NavigationMenuContent>
                <EnvironmentsPanel {...catalogue} />
              </NavigationMenuContent>
            </NavigationMenuItem>

            <NavigationMenuItem>
              <NavigationMenuTrigger>How it works</NavigationMenuTrigger>
              <NavigationMenuContent>
                <HowItWorksPanel ctaHref={cta.href} />
              </NavigationMenuContent>
            </NavigationMenuItem>

            <NavigationMenuItem>
              <NavigationMenuLink asChild>
                <Link to={routes.honesty} className={navigationMenuTriggerStyle()}>
                  Honesty
                </Link>
              </NavigationMenuLink>
            </NavigationMenuItem>
          </NavigationMenuList>
        </NavigationMenu>

        <div className="ml-auto flex items-center gap-1">
          <div className="hidden items-center gap-1 lg:flex">
            <ThemeToggle />
            <ContrastToggle />
            <Button asChild variant="ghost" size="icon">
              <a href={REPO_URL} target="_blank" rel="noreferrer noopener" aria-label="Source on Gitea">
                <GitBranch aria-hidden="true" />
              </a>
            </Button>
          </div>

          {/* Desktop: the full verb + title, 40px. Phone: the verb only, so it
              fits at 320px beside the menu button and is still 44px. */}
          <Button asChild className="ml-1 hidden h-10 border border-accent-edge lg:inline-flex">
            <Link to={cta.href}>
              {cta.label}
              <ArrowRight aria-hidden="true" />
            </Link>
          </Button>
          <Button asChild size="touch" className="border border-accent-edge lg:hidden">
            <Link to={cta.href} aria-label={cta.label}>
              {cta.short}
            </Link>
          </Button>

          <div className="lg:hidden">
            <MobileNav {...catalogue} />
          </div>
        </div>
      </div>
    </header>
  );
}
