/**
 * text-ui variant rendering tests — §210 (v0.5.7, FEEDBACK-17 §1).
 *
 * Verifies the 12 documented enum values in `text.yaml`/`text.d.ts`/
 * `text.a2ui.json` all render distinctly per the `:scope[variant=…]`
 * rules in `text.css`. Pre-§210 the three token-backed variants
 * `subsection` / `deck` / `metric` silently rendered as `body`
 * defaults because the matching `:scope` rules were missing — the
 * yaml + d.ts advertised them but the CSS didn't consume the tokens.
 *
 * Plus a guard: the 6 phantom enum entries removed in §210
 * (`subheading`, `h1`-`h6`) must NOT be in the .d.ts type union or
 * the a2ui.json enum — they had no shipped tokens and rendering them
 * was always body-defaults.
 */

import { describe, it, expect, beforeEach } from 'vitest';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
import '../../core/element.js';
import './text.js';

const tick = () => new Promise((r) => queueMicrotask(r));

function mount(html) {
  const wrap = document.createElement('div');
  wrap.innerHTML = html;
  document.body.appendChild(wrap);
  return wrap.firstElementChild;
}

// jsdom doesn't evaluate @scope rules in `getComputedStyle()`. We
// validate the CSS by reading the rule out of text.css text and
// asserting the variant-specific properties are present — coarse
// but catches the §210 regression class (rule missing entirely).
const HERE = dirname(fileURLToPath(import.meta.url));
const TEXT_CSS = readFileSync(resolve(HERE, 'text.css'), 'utf8');
const TEXT_DTS = readFileSync(resolve(HERE, 'text.d.ts'), 'utf8');
const TEXT_A2UI = JSON.parse(readFileSync(resolve(HERE, 'text.a2ui.json'), 'utf8'));

describe('text-ui §210 — variant enum vs CSS rule completeness', () => {
  beforeEach(() => { document.body.innerHTML = ''; });

  // ── Mounting smoke — every documented variant constructs without crash ──
  const documentedVariants = [
    'body', 'heading', 'title', 'subsection', 'display', 'caption',
    'label', 'kicker', 'deck', 'section', 'metric', 'code',
  ];

  it.each(documentedVariants)('mounts <text-ui variant="%s"> without error', async (variant) => {
    const t = mount(`<text-ui variant="${variant}">Sample</text-ui>`);
    await tick();
    expect(t).toBeDefined();
    expect(t.getAttribute('variant')).toBe(variant);
  });

  // ── CSS-side: every documented variant has a :scope rule ──
  it.each(documentedVariants)('text.css ships a :scope[variant="%s"] rule', (variant) => {
    expect(TEXT_CSS).toMatch(new RegExp(`:scope\\[variant="${variant}"\\]`));
  });

  // ── Three token-backed variants restored in §210 ──
  it.each(['subsection', 'deck', 'metric'])(
    'text.css :scope[variant="%s"] consumes role tokens (not body defaults)',
    (variant) => {
      // The rule must reference the role-specific token, not fall through
      // to var(--a-body-*) defaults.
      const ruleMatch = TEXT_CSS.match(
        new RegExp(`:scope\\[variant="${variant}"\\]\\s*\\{([^}]+)\\}`)
      );
      expect(ruleMatch, `rule missing for variant="${variant}"`).toBeTruthy();
      const rule = ruleMatch[1];
      expect(rule).toMatch(new RegExp(`var\\(--a-${variant}-family\\)`));
      expect(rule).toMatch(new RegExp(`var\\(--a-${variant}-weight\\)`));
      expect(rule).toMatch(new RegExp(`var\\(--a-${variant}-size\\)`));
    }
  );

  // ── Type-side: 6 phantom entries removed in §210 ──
  const removedPhantoms = ['subheading', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];

  it.each(removedPhantoms)('text.d.ts variant union does NOT contain phantom "%s"', (variant) => {
    expect(TEXT_DTS).not.toMatch(new RegExp(`'${variant}'`));
  });

  it.each(removedPhantoms)('text.a2ui.json variant enum does NOT contain phantom "%s"', (variant) => {
    expect(TEXT_A2UI.properties.variant.enum).not.toContain(variant);
  });

  // ── a2ui.json enum and .d.ts union and CSS rules are mutually consistent ──
  it('a2ui.json variant enum matches .d.ts union and CSS rules 1:1', () => {
    const a2uiVariants = TEXT_A2UI.properties.variant.enum;
    // §247b (v0.5.10): text.d.ts was hand-authored to use a named type alias
    // `UITextVariant` instead of an inline union (per-variant JSDoc + ARIA-role
    // disclaimer). Match either the type-alias body OR an inline union; both
    // forms expose the same set of string-literal members.
    const dtsAliasMatch = TEXT_DTS.match(/export\s+type\s+UITextVariant\s*=\s*((?:[\s\S]*?))(?:;|\n\n)/);
    const dtsUnionMatch = TEXT_DTS.match(/variant:\s*((?:'[^']+'\s*\|?\s*)+);/);
    const variantsSource = dtsAliasMatch?.[1] ?? dtsUnionMatch?.[1];
    expect(variantsSource).toBeTruthy();
    const dtsVariants = [...variantsSource.matchAll(/'([^']+)'/g)].map(m => m[1]);

    expect(a2uiVariants.sort()).toEqual(dtsVariants.sort());
    expect(a2uiVariants.sort()).toEqual([...documentedVariants].sort());

    // Every variant has a CSS rule.
    for (const v of a2uiVariants) {
      expect(TEXT_CSS).toMatch(new RegExp(`:scope\\[variant="${v}"\\]`));
    }
  });
});

// ─────────────────────────────────────────────────────────────────────
// v0.6.18 (FB-10) — overlay attributes on top of `variant`
// ─────────────────────────────────────────────────────────────────────

describe('text-ui v0.6.18 — overlay props (FB-10)', () => {
  beforeEach(() => { document.body.innerHTML = ''; });

  // ── Attribute reflection: each new prop reflects to the host ──
  it.each([
    ['size', 'sm'],
    ['size', 'md'],
    ['size', 'lg'],
    ['color', 'subtle'],
    ['color', 'strong'],
    ['color', 'accent'],
    ['color', 'danger'],
    ['color', 'success'],
    ['color', 'warning'],
    ['color', 'default'],
    ['weight', 'regular'],
    ['weight', 'medium'],
    ['weight', 'semibold'],
    ['weight', 'bold'],
    ['text-align', 'start'],
    ['text-align', 'center'],
    ['text-align', 'end'],
    ['text-align', 'justify'],
  ])('<text-ui %s="%s"> reflects the attribute', async (prop, value) => {
    const el = mount(`<text-ui ${prop}="${value}">x</text-ui>`);
    await tick();
    expect(el.getAttribute(prop)).toBe(value);
  });

  // ── CSS-side: every documented value has a :scope[attr="value"] rule ──
  const sizes = ['sm', 'md', 'lg'];
  const colors = ['default', 'subtle', 'strong', 'accent', 'danger', 'success', 'warning'];
  const weights = ['regular', 'medium', 'semibold', 'bold'];
  const aligns = ['start', 'center', 'end', 'justify'];

  it.each(sizes)('text.css ships :scope[size="%s"] rule', (s) => {
    expect(TEXT_CSS).toMatch(new RegExp(`:scope\\[size="${s}"\\]`));
  });

  it.each(colors)('text.css ships :scope[color="%s"] rule', (c) => {
    expect(TEXT_CSS).toMatch(new RegExp(`:scope\\[color="${c}"\\]`));
  });

  it.each(weights)('text.css ships :scope[weight="%s"] rule', (w) => {
    expect(TEXT_CSS).toMatch(new RegExp(`:scope\\[weight="${w}"\\]`));
  });

  it.each(aligns)('text.css ships :scope[text-align="%s"] rule', (a) => {
    expect(TEXT_CSS).toMatch(new RegExp(`:scope\\[text-align="${a}"\\]`));
  });

  // ── yaml-vs-impl consistency: every prop in the a2ui enum has a CSS rule ──
  it('a2ui.json color enum matches CSS rules 1:1', () => {
    const colorEnum = TEXT_A2UI.properties.color?.enum ?? [];
    expect(colorEnum.sort()).toEqual([...colors].sort());
    for (const c of colorEnum) {
      expect(TEXT_CSS).toMatch(new RegExp(`:scope\\[color="${c}"\\]`));
    }
  });

  it('a2ui.json size enum matches CSS rules 1:1', () => {
    const sizeEnum = TEXT_A2UI.properties.size?.enum ?? [];
    expect(sizeEnum.sort()).toEqual([...sizes].sort());
    for (const s of sizeEnum) {
      expect(TEXT_CSS).toMatch(new RegExp(`:scope\\[size="${s}"\\]`));
    }
  });

  it('a2ui.json weight enum matches CSS rules 1:1', () => {
    const weightEnum = TEXT_A2UI.properties.weight?.enum ?? [];
    expect(weightEnum.sort()).toEqual([...weights].sort());
    for (const w of weightEnum) {
      expect(TEXT_CSS).toMatch(new RegExp(`:scope\\[weight="${w}"\\]`));
    }
  });

  // ── Permissive fallback: unknown values are no-ops (no JS error) ──
  it('unknown color value renders without throwing', async () => {
    const el = mount('<text-ui color="banana">x</text-ui>');
    await tick();
    expect(el.getAttribute('color')).toBe('banana');
    // The variant default wins; no CSS rule matches "banana". Test the
    // shape: no thrown error during mount, no console.error.
  });
});
