/* ─────────────────────────────────────────────────────────────────────────
 * design-systems/_schema/tokens.schema.ts
 *
 * The structural contract for every brand's `tokens.css`.
 *
 * Each token in the open-design ecosystem belongs to exactly one of
 * four layers, distinguished by who decides the value and what
 * happens when a brand omits the token.
 *
 *   A1-identity    Required. The token *is* the brand. No fallback can
 *                  substitute. (--bg, --fg, --accent, font stacks.)
 *
 *   A1-structure   Required. The token is a structural decision (type
 *                  scale, layout grid, section rhythm) that has no
 *                  cross-brand sensible default — every brand authors
 *                  its own.
 *
 *   A2             Required *in the final tokens.css*, but a sensible
 *                  fallback exists in `_schema/defaults.css` that the
 *                  derive script (PR-B) will inline if a brand's
 *                  DESIGN.md does not specify the value. Authors of
 *                  hand-written brands (default, kami) must include
 *                  every A2 token directly until the derive script
 *                  ships.
 *
 *   B-slot         Optional schema slot. The token exists for cross-
 *                  brand consistency but a brand without the richer
 *                  tier may alias it to the named sibling via `var()`.
 *                  Components that reference B-slot tokens always
 *                  resolve, even on brands that do not differentiate
 *                  the tier.
 *
 *   C-extension    Brand-specific token, declared explicitly per
 *                  brand. Generic cross-brand components must NOT
 *                  reference these. Promote to a B-slot when ≥2 brands
 *                  need the same name; promote to A2 when there is a
 *                  meaningful global default.
 *
 * Why A2 is "required-with-fallback" rather than "optional":
 *   Artifacts are generated by agents pasting one brand's :root block
 *   into a single <style>. There is no runtime cascade from a global
 *   defaults stylesheet. Agents that paste a tokens.css missing a
 *   var() target will produce broken artifacts (`var(--motion-fast)`
 *   resolves to nothing, `transition: var(--motion-fast)` becomes
 *   `transition: ` and the rule is dropped). The fallback lives in
 *   `_schema/defaults.css` so the *derive script* can inline it; the
 *   *runtime* contract remains "every tokens.css must declare every
 *   A1 + A2 + B-slot token".
 *
 * Why C-extension is allowlisted, not free:
 *   Without an allowlist, brand authors can ship arbitrary token
 *   names that other brands' components silently miss. The allowlist
 *   forces a deliberate review when a new brand-only name appears,
 *   and makes the C→B→A promotion path explicit (move the name from
 *   the brand-specific list into TOKEN_SCHEMA when ≥2 brands need it).
 *
 * Sources of truth:
 *   - This file: every shared schema token with its layer + metadata.
 *   - `_schema/defaults.css`: A2 fallback values, mirrored from this
 *     file so humans can sanity-check the contract in real CSS form.
 *   - `_schema/AGENTS.md`: prose narrative + promotion path rules.
 *
 * Drift between this file and defaults.css is enforced by the
 * `design-system: A2 defaults parity` guard check.
 * ─────────────────────────────────────────────────────────────────── */

export type TokenLayer = "A1-identity" | "A1-structure" | "A2" | "B-slot";

export type TokenSpec = {
  /** CSS custom property name including the `--` prefix. */
  readonly name: string;
  readonly layer: TokenLayer;
  /** One-line description for documentation generators. */
  readonly description: string;
  /**
   * A2 only. The default value the derive script inlines when a
   * brand's DESIGN.md does not specify one. Must stay byte-equivalent
   * to the matching declaration in `_schema/defaults.css`.
   */
  readonly fallback?: string;
  /**
   * B-slot only. Sibling token to alias to when the brand has no
   * richer tier (e.g. `--fg-2` aliases to `--fg`). The aliasTo string
   * is a CSS expression, typically `var(--name)`.
   */
  readonly aliasTo?: string;
};

/* eslint-disable @typescript-eslint/no-inferrable-types */

/**
 * Every brand's tokens.css must declare every entry in this list.
 *
 * Order is meaningful for human review — tokens are grouped by intent
 * rather than by layer so reviewers can scan the visual stack from
 * surface → text → border → accent → semantic → typography → spacing
 * → radius → elevation → focus → motion → layout.
 */
export const TOKEN_SCHEMA: readonly TokenSpec[] = [
  // ─── Surface ──────────────────────────────────────────────────────
  { name: "--bg",           layer: "A1-identity",  description: "Page background — defines the brand canvas." },
  { name: "--surface",      layer: "A1-identity",  description: "Card / lifted container background." },
  { name: "--surface-warm", layer: "B-slot",       description: "Tertiary surface tier (kami warm-sand).",
    aliasTo: "var(--surface)" },

  // ─── Foreground ───────────────────────────────────────────────────
  { name: "--fg",     layer: "A1-identity", description: "Primary text color." },
  { name: "--fg-2",   layer: "B-slot",      description: "Secondary text tier (kami dark-warm).",
    aliasTo: "var(--fg)" },
  { name: "--muted",  layer: "A1-identity", description: "Subtext / captions." },
  { name: "--meta",   layer: "B-slot",      description: "Tertiary FG / metadata tier (kami stone).",
    aliasTo: "var(--muted)" },

  // ─── Border ───────────────────────────────────────────────────────
  { name: "--border",      layer: "A1-identity", description: "Default border / card edge." },
  { name: "--border-soft", layer: "B-slot",      description: "Inner row separator that should not visually compete.",
    aliasTo: "var(--border)" },

  // ─── Accent ───────────────────────────────────────────────────────
  { name: "--accent",        layer: "A1-identity", description: "Brand accent. ≤2 visible uses per screen (lint enforced)." },
  { name: "--accent-on",     layer: "A2",          description: "FG when --accent is the bg.",
    fallback: "#ffffff" },
  { name: "--accent-hover",  layer: "A2",          description: "Hover state for elements using --accent as bg.",
    fallback: "color-mix(in oklab, var(--accent), black 8%)" },
  { name: "--accent-active", layer: "A2",          description: "Active state for elements using --accent as bg.",
    fallback: "color-mix(in oklab, var(--accent), black 14%)" },

  // ─── Semantic ─────────────────────────────────────────────────────
  { name: "--success", layer: "A2", description: "Success state.", fallback: "#16a34a" },
  { name: "--warn",    layer: "A2", description: "Warning state.", fallback: "#eab308" },
  { name: "--danger",  layer: "A2", description: "Danger state.",  fallback: "#dc2626" },

  // ─── Typography — fonts ───────────────────────────────────────────
  { name: "--font-display", layer: "A1-identity", description: "Display / heading font stack." },
  { name: "--font-body",    layer: "A1-identity", description: "Body font stack." },
  { name: "--font-mono",    layer: "A2",          description: "Monospace font stack — used by kbd, code, tabular metrics.",
    fallback: 'ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Monaco, Consolas, monospace' },

  // ─── Typography — type scale ──────────────────────────────────────
  { name: "--text-xs",   layer: "A1-structure", description: "Type scale step — extra small (≈11–12px)." },
  { name: "--text-sm",   layer: "A1-structure", description: "Type scale step — small (≈12–14px)." },
  { name: "--text-base", layer: "A1-structure", description: "Type scale step — body baseline." },
  { name: "--text-lg",   layer: "A1-structure", description: "Type scale step — H3 / featured body." },
  { name: "--text-xl",   layer: "A1-structure", description: "Type scale step — H2." },
  { name: "--text-2xl",  layer: "A1-structure", description: "Type scale step — section title." },
  { name: "--text-3xl",  layer: "A1-structure", description: "Type scale step — H1." },
  { name: "--text-4xl",  layer: "A1-structure", description: "Type scale step — display / hero." },

  // ─── Typography — leading & tracking ──────────────────────────────
  { name: "--leading-body",     layer: "A1-structure", description: "Line-height for reading body." },
  { name: "--leading-tight",    layer: "A1-structure", description: "Line-height for headings." },
  { name: "--tracking-display", layer: "A1-structure", description: "Letter-spacing applied to display sizes." },

  // ─── Spacing — base scale ─────────────────────────────────────────
  { name: "--space-1",  layer: "A2", description: "Base spacing — 4px tier.",  fallback: "4px" },
  { name: "--space-2",  layer: "A2", description: "Base spacing — 8px tier.",  fallback: "8px" },
  { name: "--space-3",  layer: "A2", description: "Base spacing — 12px tier.", fallback: "12px" },
  { name: "--space-4",  layer: "A2", description: "Base spacing — 16px tier.", fallback: "16px" },
  { name: "--space-5",  layer: "A2", description: "Base spacing — 20px tier.", fallback: "20px" },
  { name: "--space-6",  layer: "A2", description: "Base spacing — 24px tier.", fallback: "24px" },
  { name: "--space-8",  layer: "A2", description: "Base spacing — 32px tier.", fallback: "32px" },
  { name: "--space-12", layer: "A2", description: "Base spacing — 48px tier.", fallback: "48px" },

  // ─── Section rhythm ───────────────────────────────────────────────
  { name: "--section-y-desktop", layer: "A1-structure", description: "Vertical padding between sections — desktop." },
  { name: "--section-y-tablet",  layer: "A1-structure", description: "Vertical padding between sections — tablet." },
  { name: "--section-y-phone",   layer: "A1-structure", description: "Vertical padding between sections — phone." },

  // ─── Radius ───────────────────────────────────────────────────────
  { name: "--radius-sm",   layer: "A2", description: "Small radius — buttons, inputs, chips.", fallback: "8px" },
  { name: "--radius-md",   layer: "A2", description: "Medium radius — cards, modals.",         fallback: "12px" },
  { name: "--radius-lg",   layer: "A2", description: "Large radius — featured containers.",    fallback: "16px" },
  { name: "--radius-pill", layer: "A2", description: "Pill radius — avatars, badges.",         fallback: "9999px" },

  // ─── Elevation ────────────────────────────────────────────────────
  { name: "--elev-flat",   layer: "A2", description: "No elevation.",                          fallback: "none" },
  { name: "--elev-ring",   layer: "A2", description: "Hairline ring (1px box-shadow border).", fallback: "0 0 0 1px var(--border)" },
  { name: "--elev-raised", layer: "A2", description: "Raised surface (blur or whisper).",
    fallback: "0 2px 8px color-mix(in oklab, var(--fg), transparent 92%)" },

  // ─── Focus ────────────────────────────────────────────────────────
  { name: "--focus-ring", layer: "A2", description: "Keyboard focus indicator.",
    fallback: "0 0 0 3px color-mix(in oklab, var(--accent), transparent 70%)" },

  // ─── Motion ───────────────────────────────────────────────────────
  { name: "--motion-fast",   layer: "A2", description: "Hover / micro-state duration.", fallback: "150ms" },
  { name: "--motion-base",   layer: "A2", description: "General state-change duration.", fallback: "200ms" },
  { name: "--ease-standard", layer: "A2", description: "Standard easing curve.",
    fallback: "cubic-bezier(0.2, 0, 0, 1)" },

  // ─── Layout ───────────────────────────────────────────────────────
  { name: "--container-max",            layer: "A1-structure", description: "Max content container width." },
  { name: "--container-gutter-desktop", layer: "A1-structure", description: "Container side gutter — desktop." },
  { name: "--container-gutter-tablet",  layer: "A1-structure", description: "Container side gutter — tablet." },
  { name: "--container-gutter-phone",   layer: "A1-structure", description: "Container side gutter — phone." },
];

/* eslint-enable @typescript-eslint/no-inferrable-types */

/**
 * Brand-specific tokens (Layer C) that are not part of the shared
 * schema but are explicitly allowed for the named brand.
 *
 * Adding a name here means: "this token exists only in this brand's
 * tokens.css; cross-brand components must not reference it." When a
 * second brand adopts the same name, promote the entry into
 * TOKEN_SCHEMA (typically as a B-slot or A2) and remove it here.
 */
export const BRAND_EXTENSIONS: Readonly<Record<string, readonly string[]>> = {
  default: [
    "--space-20", // 80px — used as section-y-desktop's twin; only default needs it
  ],
  openai: [
    "--space-16", // 64px — major section gap in OpenAI's DESIGN.md §5 spacing scale
  ],
  kami: [
    "--accent-light", // brighter ink-blue for links on dark surfaces
    "--text-md", // 15px lede tier between --text-base and --text-lg
    "--leading-display", // 1.10 — only kami needs this tier
    "--leading-dense", // 1.40 — resume / one-pager rhythm
    "--tracking-eyebrow", // uppercase eyebrow tracking
    "--tracking-label", // small uppercase label tracking
    "--space-7", // 28px — kami's card interior
    "--space-18", // 72px — section gap (web)
    "--space-22", // 88px — page top padding (web)
    "--radius-xs", // 2px — kami tags
    "--radius-xl", // 16px — kami hero containers
    "--elev-ring-accent", // 1px brand ring used as primary-button edge
  ],
};

/**
 * Prefixes that match any token starting with the given string. Use
 * for whole families of brand-specific tokens (e.g. kami's pre-blended
 * tag tints `--tag-bg-faint / --tag-bg-soft / ...`) where the
 * individual member tokens shouldn't have to be enumerated.
 *
 * A prefix in this list applies to *any* brand. To restrict a prefix
 * to one brand, list each member name in BRAND_EXTENSIONS instead.
 */
export const BRAND_EXTENSION_PREFIXES: readonly string[] = [
  "--tag-bg-",
];

/**
 * Names that are intentionally absent from the shared schema and not
 * tracked per-brand: `--leading-display`, `--leading-dense`, etc.
 * begin life in BRAND_EXTENSIONS[brand] and graduate to the schema
 * once a second brand adopts them.
 */

// ─── Helpers (consumed by the guard checks) ─────────────────────────

export function getRequiredA1Names(): readonly string[] {
  return TOKEN_SCHEMA.filter((t) => t.layer === "A1-identity" || t.layer === "A1-structure").map((t) => t.name);
}

export function getRequiredA2Names(): readonly string[] {
  return TOKEN_SCHEMA.filter((t) => t.layer === "A2").map((t) => t.name);
}

export function getBSlotNames(): readonly string[] {
  return TOKEN_SCHEMA.filter((t) => t.layer === "B-slot").map((t) => t.name);
}

export function getAllSchemaNames(): readonly string[] {
  return TOKEN_SCHEMA.map((t) => t.name);
}

export function isAllowedExtension(brand: string, name: string): boolean {
  if (BRAND_EXTENSION_PREFIXES.some((prefix) => name.startsWith(prefix))) return true;
  const brandList = BRAND_EXTENSIONS[brand];
  if (brandList != null && brandList.includes(name)) return true;
  return false;
}
