import { useMemo } from 'react';
import { Link, useSearchParams } from 'react-router-dom';
import { ArrowRight, FileText } from 'lucide-react';

import { iconFor } from '@/content/icons';
import {
  allDemos,
  demosForVertical,
  lineup,
  routes,
  verticalForDemo,
  verticalKeysInUse,
} from '@/content/lineup';
import { PROPOSAL_NOTICE } from '@/content/verticals';
// The registry owns the taxonomy's exec-facing names. The lineup's own titles
// are longer marketing headings ("Customer Support Resolution") and would wrap
// two lines inside a filter chip on a phone, so chips use the registry label.
import { VERTICAL_LABELS } from '@/lib/demo-kit/registry';
import type { Vertical } from '@/lib/demo-kit/types';
import * as s from '@/content/styles';
import { pageTitle, useSeo } from '@/lib/seo';

const ALL = 'all';

function verticalLabel(key: Vertical): string {
  return VERTICAL_LABELS[key];
}

export default function Gallery() {
  useSeo({
    title: pageTitle('Every environment'),
    description:
      'Every environment in the lineup: the ones that are built and playable, and the ones published as a specification with the task, the grader and the counterweight written out.',
    canonical: routes.gallery,
    ogImage: '/og/gallery.png',
  });

  /*
   * The filter lives in the URL, not in component state. A filtered gallery is
   * the thing somebody pastes into a message, and it also means the back button
   * undoes a filter change, which is what people expect it to do.
   */
  const [params, setParams] = useSearchParams();
  const raw = params.get('vertical');
  const active: Vertical | typeof ALL =
    raw && verticalKeysInUse.includes(raw as Vertical) ? (raw as Vertical) : ALL;

  const shown = useMemo(
    () => (active === ALL ? allDemos : allDemos.filter((d) => d.vertical === active)),
    [active],
  );

  function select(next: Vertical | typeof ALL) {
    // `replace` so a run of filter taps leaves one entry in history, not eight.
    if (next === ALL) setParams({}, { replace: true });
    else setParams({ vertical: next }, { replace: true });
  }

  const filters: readonly (Vertical | typeof ALL)[] = [ALL, ...verticalKeysInUse];

  const unbuilt = lineup.filter((v) => demosForVertical(v.key).length === 0).length;

  return (
    <main className={`${s.shell} py-10 sm:py-16`}>
      <p className={s.eyebrow}>Gallery</p>
      <h1 className={`${s.h1} mt-3 max-w-3xl`}>Every environment we have built or specified.</h1>
      <p className={`${s.lede} mt-5 max-w-2xl`}>
        A live demo is playable in this tab. A spec is a written environment — task, action set,
        grader, counterweight and the command that evaluates it — published in full, with no
        interactive surface yet. There are no coming-soon cards here.
      </p>

      {filters.length > 2 ? (
        <div className="mt-8">
          <h2 className="sr-only">Filter by vertical</h2>
          <ul aria-label="Filter demos by vertical" className="flex flex-wrap gap-2">
            {filters.map((key) => {
              const selected = key === active;
              return (
                <li key={key}>
                  <button
                    aria-pressed={selected}
                    className={`tap inline-flex items-center rounded-lg border px-4 py-2 text-sm font-medium transition-colors duration-2 ease-enter ${
                      selected
                        ? 'border-brand bg-accent-subtle text-accent-fg'
                        : 'border-border bg-surface text-muted hover:bg-surface-2 hover:text-fg'
                    }`}
                    onClick={() => select(key)}
                    type="button"
                  >
                    {key === ALL ? 'All' : verticalLabel(key)}
                  </button>
                </li>
              );
            })}
          </ul>
        </div>
      ) : null}

      {/*
        The count changes without a page load and without moving focus, so it
        is announced. Without this, a filter tap is silent to a screen reader.
      */}
      <p aria-live="polite" className="mt-6 text-sm text-muted">
        {shown.length === 1 ? '1 environment' : `${shown.length} environments`}
        {active === ALL ? '' : ` in ${verticalLabel(active)}`}
      </p>

      {shown.length === 0 ? (
        <div className="card mt-4 p-6">
          {/* Two different nothings. Telling a visitor "no environment is filed
              under this vertical" when they have not filtered anything reads as
              a broken page rather than an empty one. */}
          <p className={s.h3}>
            {active === ALL ? 'No environments are registered.' : 'Nothing under this vertical.'}
          </p>
          <p className={`${s.prose} mt-2`}>
            {active === ALL
              ? 'The registry is empty, which means the site is mid-build rather than hiding something. The lineup below is written either way.'
              : 'No environment is filed here yet. The proposal for it is still on its own page, written out in full.'}
          </p>
          {active === ALL ? null : (
            <button className={`${s.btnSecondary} mt-4`} onClick={() => select(ALL)} type="button">
              Show every environment
            </button>
          )}
        </div>
      ) : (
        <ul className="mt-4 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
          {shown.map((demo) => {
            const Icon = iconFor(demo.icon);
            const isSpec = demo.status === 'spec';
            const vertical = verticalForDemo(demo);
            return (
              <li className="flex" key={demo.slug}>
                <article className="flex w-full flex-col">
                  <Link
                    // A spec is dimmed but never disabled: it goes to a real
                    // page with a real specification on it, which is the only
                    // thing that makes dimming it honest rather than teasing.
                    aria-label={`${demo.title} — ${isSpec ? 'read the specification' : 'play it'}`}
                    className={`${s.cardLink} h-full ${isSpec ? 'opacity-70 hover:opacity-100' : ''}`}
                    to={routes.demo(demo.slug)}
                  >
                    <span className="flex items-start justify-between gap-3">
                      <Icon aria-hidden="true" className="size-5 shrink-0 text-brand" />
                      {isSpec ? (
                        <span className={`${s.pill} gap-1`}>
                          <FileText aria-hidden="true" className="size-3.5" />
                          Spec
                        </span>
                      ) : (
                        <span className={`${s.pill} border-positive/30 bg-positive/10 text-positive`}>
                          Live
                        </span>
                      )}
                    </span>

                    <h3 className="mt-3 text-lg font-bold tracking-tight text-fg">{demo.title}</h3>
                    <p className={`${s.prose} mt-1.5 text-sm`}>{demo.tagline}</p>

                    <dl className="mt-4 space-y-1.5 text-sm">
                      <div className="flex gap-2">
                        <dt className="shrink-0 text-muted">For</dt>
                        <dd className="text-fg">{demo.persona}</dd>
                      </div>
                      <div className="flex gap-2">
                        <dt className="shrink-0 text-muted">Reward</dt>
                        <dd className="text-fg">{demo.rewardLine}</dd>
                      </div>
                    </dl>

                    <span className="mt-4 inline-flex items-center gap-1.5 text-sm font-semibold text-accent-fg">
                      {isSpec ? 'Read the specification' : 'Play it'}
                      <ArrowRight
                        aria-hidden="true"
                        className="size-4 transition-transform duration-2 ease-enter group-hover:translate-x-0.5"
                      />
                    </span>
                  </Link>

                  {vertical ? (
                    <p className="mt-2 px-1 text-xs text-muted">
                      <Link className={s.link} to={routes.vertical(vertical.slug)}>
                        {vertical.title}
                      </Link>{' '}
                      · {PROPOSAL_NOTICE}
                    </p>
                  ) : null}
                </article>
              </li>
            );
          })}
        </ul>
      )}

      <div className="card mt-12 p-5 sm:p-7">
        {/* Counted, not typed. A hard-coded "eleven" on a site about checkable
            numbers goes stale the first time a demo ships. */}
        <h2 className={s.h2}>
          The {unbuilt} we have not built
        </h2>
        <p className={`${s.prose} mt-3 max-w-2xl`}>
          The lineup is a set of proposals, written to the same four-part shape as the live one.
          Reading one takes a minute and tells you whether the idea survives contact with your own
          numbers.
        </p>
        <Link className={`${s.btnPrimary} mt-5`} to={routes.home}>
          See the lineup
        </Link>
      </div>
    </main>
  );
}
