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

import { DefinitionStrip } from '@/components/site/DefinitionStrip';
import {
  ALL_VERTICALS,
  EnvironmentList,
  FeaturedEnvironment,
  environments,
  filterKeys,
  VerticalFilter,
  type VerticalFilterValue,
} from '@/components/site/EnvironmentPicker';
import { pad2, routes } from '@/content/lineup';
import * as s from '@/content/styles';
import { PROPOSAL_NOTICE } from '@/content/verticals';
import { VERTICAL_LABELS } from '@/lib/demo-kit/registry';
import type { Vertical } from '@/lib/demo-kit/types';
import { pageTitle, useSeo } from '@/lib/seo';

/**
 * The fuller list.
 *
 * Home leads with the picker; this is where you go to see everything and cut it
 * by industry. The built environments are the same `FeaturedEnvironment` cards
 * and the written ones the same `EnvironmentRow` rows, so the two pages cannot
 * drift into describing the same environment two different ways — which is how
 * an honest site quietly becomes a dishonest one.
 */
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: VerticalFilterValue =
    raw && filterKeys.includes(raw as Vertical) ? (raw as Vertical) : ALL_VERTICALS;

  const shown = useMemo(
    () =>
      active === ALL_VERTICALS
        ? environments
        : environments.filter((entry) => entry.verticalKey === active),
    [active],
  );

  const built = shown.filter((entry) => entry.status === 'live');
  const written = shown.filter((entry) => entry.status === 'spec');

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

  return (
    <main className={`${s.shell} py-10 sm:py-14`}>
      <p className={s.label}>
        Gallery · {pad2(environments.length)} environments
      </p>
      <h1 className={`${s.h1} mt-3 max-w-[24ch]`}>Every environment we have built or specified.</h1>
      <p className={`${s.lede} mt-4`}>
        A built environment is playable in this tab, on the code the repository ships. A spec is a
        written environment — task, legal actions, grader, counterweight and the command that
        evaluates it — published in full, with no board yet. There are no coming-soon cards here.
      </p>

      {filterKeys.length > 1 ? (
        <div className="mt-8">
          <h2 className="sr-only">Filter by vertical</h2>
          <VerticalFilter active={active} onSelect={select} />
        </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={`${s.label} mt-6`}>
        {[
          `${pad2(shown.length)} ${shown.length === 1 ? 'environment' : 'environments'}`,
          active === ALL_VERTICALS ? null : VERTICAL_LABELS[active],
          // Zero segments are dropped, not printed as `00`: a filter with no
          // live entry says so by omission, the same way the landing does.
          built.length > 0 ? `${pad2(built.length)} live` : null,
          written.length > 0 ? `${pad2(written.length)} spec` : null,
        ]
          .filter((part): part is string => part !== null)
          .join(' · ')}
      </p>

      {shown.length === 0 ? (
        <div className="card mt-4 p-4 sm:p-5">
          {/* 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. */}
          <h2 className={s.h3}>
            {active === ALL_VERTICALS
              ? 'No environments are registered.'
              : 'Nothing under this vertical.'}
          </h2>
          <p className={`${s.prose} mt-2`}>
            {active === ALL_VERTICALS
              ? 'The registry is empty and the lineup is empty with it, which means the site is mid-build rather than hiding something.'
              : 'No environment is filed here yet, built or written.'}
          </p>
          {active === ALL_VERTICALS ? null : (
            <button
              className={`${s.btnSecondary} mt-4`}
              onClick={() => select(ALL_VERTICALS)}
              type="button"
            >
              Show every environment
            </button>
          )}
        </div>
      ) : null}

      {built.length > 0 ? (
        <section aria-labelledby="built" className="mt-6">
          <h2 className={s.h2} id="built">
            {built.length === 1 ? 'The one you can play' : 'The ones you can play'}
          </h2>
          <ul className={`mt-4 grid gap-4 ${built.length >= 2 ? 'lg:grid-cols-2' : ''}`}>
            {built.map((entry) => (
              <li className="flex" key={entry.id}>
                <FeaturedEnvironment className="w-full" entry={entry} />
              </li>
            ))}
          </ul>
        </section>
      ) : null}

      {written.length > 0 ? (
        <section aria-labelledby="written" className="mt-10">
          <div className="flex flex-wrap items-end justify-between gap-3">
            <h2 className={s.h2} id="written">
              {written.length === 1 ? 'The one that is written' : `The ${written.length} that are written`}
            </h2>
            <span className={s.chipOutline}>{PROPOSAL_NOTICE}</span>
          </div>
          {/* The same rows as the landing, with the same numerals: a filtered
              list keeps each row's place in the whole lineup rather than
              renumbering from 01, so `04` means the same thing on both pages. */}
          <EnvironmentList className="mt-4" entries={written} />
        </section>
      ) : null}

      {/* What a spec has to contain — the same four parts as everywhere else,
          read from the same source, so this page cannot teach a fifth word. */}
      <section aria-labelledby="contains" className="mt-12 sm:mt-16">
        <h2 className={s.h2} id="contains">
          What a spec has to contain
        </h2>
        <p className={`${s.prose} mt-3`}>
          A card that says a demo is coming is not a specification, and the build refuses one. Every
          written environment above states all four of these, and the counterweight that stops the
          score being farmed, before it is allowed on this page.
        </p>
        <DefinitionStrip className="mt-6" />
        <Link className={`${s.btnSecondary} mt-6`} to={routes.honesty}>
          What we measured, and what we didn’t
        </Link>
      </section>
    </main>
  );
}
