/**
 * 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 tab strip.
 *
 * Full width and equal columns below `sm` so four tabs land inside 390px
 * without the list becoming a horizontal scroller — a tab bar you have to
 * scroll hides the tabs, which is the one thing it exists to advertise. From
 * `sm` up it shrinks back to its natural width and sits left.
 */
export function DemoTabBar({
  tabs,
  className,
}: {
  tabs: readonly DemoTabId[];
  className?: string;
}) {
  return (
    <TabsList
      aria-label="How to explore this environment"
      className={cn('flex w-full sm:inline-flex sm:w-auto', className)}
    >
      {tabs.map((id) => (
        <TabsTrigger
          key={id}
          value={id}
          title={TAB_DESCRIPTIONS[id]}
          className="min-w-0 flex-1 px-2 text-[0.8125rem] sm:flex-none sm:px-3 sm:text-sm"
        >
          {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.
 */
export function TabClaim({ children, className }: { children: ReactNode; className?: string }) {
  return (
    <p
      className={cn(
        'max-w-2xl text-pretty text-base leading-snug text-fg sm:text-lg lg:text-xl',
        className,
      )}
    >
      {children}
    </p>
  );
}
