/**
 * The demo page's tab list, and the rules that decide which tabs exist.
 *
 * A demo never declares its tabs. The shell derives them from what the demo
 * actually has, which is the only way the four labels can mean the same thing
 * on every page: `Play` is always the visitor doing the task, `Watch` is always
 * a recorded attempt, and the absence of one is a fact about the demo rather
 * than an authoring choice someone forgot to make.
 */

import type { ReactNode } from 'react';

import { TabsList, TabsTrigger } from '@/components/ui/tabs';
import { DEMO_TABS } from '@/lib/demo-kit/types';
import type { DemoTabId } from '@/lib/demo-kit/types';
import { cn } from '@/lib/utils';

/** What a person reads. One word each, so four of them fit at 390px. */
export const TAB_LABELS: Readonly<Record<DemoTabId, string>> = {
  play: 'Play',
  watch: 'Watch',
  reward: 'Reward',
  evidence: 'Evidence',
};

/** Read to a screen reader, where a one-word label is not enough context. */
const TAB_DESCRIPTIONS: Readonly<Record<DemoTabId, string>> = {
  play: 'Play the environment yourself',
  watch: 'Watch a recorded agent attempt',
  reward: 'The reward, and what happens when you change it',
  evidence: 'The environment, its provenance and its limits',
};

export interface TabAvailability {
  /** The demo ships an interactive mode. */
  play: boolean;
  /** At least one recorded run adapted to at least one step. */
  watch: boolean;
}

/**
 * The tabs this demo has, in contract order.
 *
 * `reward` and `evidence` are unconditional: a demo with neither an interactive
 * mode nor a recorded run is still an environment with a grader you can read,
 * and that is the one thing the site refuses to leave out.
 */
export function visibleTabs(has: TabAvailability): DemoTabId[] {
  return DEMO_TABS.filter((id) => {
    if (id === 'play') return has.play;
    if (id === 'watch') return has.watch;
    return true;
  });
}

/**
 * Coerce whatever the URL says into a tab that exists.
 *
 * An unknown or unavailable `?tab=` lands on the default rather than rendering
 * an empty page — a stale permalink to `?tab=watch` on a demo whose traces were
 * pulled must still show something.
 */
export function resolveTab(
  raw: string,
  visible: readonly DemoTabId[],
  fallback: DemoTabId,
): DemoTabId {
  return visible.find((id) => id === raw) ?? fallback;
}

/**
 * The mono index prefix (`01 `–`04 `) is the tab's position in the CONTRACT
 * order, not in the visible list — a demo without Play still shows Watch as
 * `02`, so the numbers mean the same thing on every page.
 */
function indexOf(id: DemoTabId): string {
  return String(DEMO_TABS.indexOf(id) + 1).padStart(2, '0');
}

/**
 * The primary tab strip: underline tabs.
 *
 * Underline rather than the pill segment for one reason — the nested lists
 * inside Watch (Reasoning | Model call) keep the pill style, and primary and
 * secondary navigation must never look alike. The primitive's `underline`
 * variant supplies the rail, the 44px triggers, the equal columns below `sm`
 * and the 2px bar; this adds the sticky behaviour and the index prefix.
 * Sticky under the site header from `lg` only: on a phone a sticky strip eats
 * a fifth of the viewport. `bg-bg/90` is one of the two alpha grounds the
 * spec allows, because it sits on the page ground itself.
 */
export function DemoTabBar({
  tabs,
  className,
}: {
  tabs: readonly DemoTabId[];
  className?: string;
}) {
  return (
    <TabsList
      variant="underline"
      aria-label="How to explore this environment"
      className={cn(
        'sm:gap-2',
        'lg:sticky lg:top-[var(--app-header-h)] lg:z-30 lg:bg-bg/90 lg:backdrop-blur-md',
        className,
      )}
    >
      {tabs.map((id) => (
        <TabsTrigger key={id} value={id} title={TAB_DESCRIPTIONS[id]}>
          <span aria-hidden="true" className="mr-1.5 hidden font-mono text-label text-muted sm:inline">
            {indexOf(id)}
          </span>
          {TAB_LABELS[id]}
        </TabsTrigger>
      ))}
    </TabsList>
  );
}

/**
 * The sentence a tab has to earn, typeset as an assertion rather than a
 * heading.
 *
 * The failure mode of a demo site is a visitor watching something pretty and
 * never learning what it was supposed to prove. The claim is the first thing in
 * every panel for that reason, and it is deliberately not an `<h2>`: the tab
 * trigger is already this panel's accessible name, and a heading here would
 * make the claim navigable furniture instead of a thing someone reads.
 *
 * Never mono, never uppercase, never shortened. This is the one place the
 * language yields to the lesson outright.
 */
export function TabClaim({ children, className }: { children: ReactNode; className?: string }) {
  return (
    <p className={cn('max-w-[60ch] text-pretty text-claim text-fg lg:text-claim-lg', className)}>
      {children}
    </p>
  );
}
