import { describe, expect, it } from 'vitest';
import { readdirSync, readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join, relative } from 'node:path';
import { THEME_REGISTRY_META } from '../lib/theme';

/**
 * Enforcement canary for theme-system spec §4.2 (token-purity).
 *
 * Theme presentation components (.astro Shells + override templates, and any
 * co-located .tsx under src/themes/**) MUST reference contract token names only
 * (`var(--mod-color-*)`, shape tokens) — never raw color literals.
 *
 * Replaces the vacuous ast-grep `mod-ui-no-layout-raw-color.yml` rule (language: tsx),
 * which never scanned .astro files because ast-grep has no .astro grammar.
 */
const THEMES = dirname(fileURLToPath(import.meta.url));

/** Any hex literal after stripping known non-color anchor/id attribute values. */
const HEX_COLOR_RE =
  /#([0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{4}|[0-9a-fA-F]{3})\b/g;
const RAW_COLOR_FN_RES: readonly { label: string; re: RegExp }[] = [
  { label: 'rgb(', re: /\brgb\s*\(/gi },
  { label: 'rgba(', re: /\brgba\s*\(/gi },
  { label: 'hsl(', re: /\bhsl\s*\(/gi },
  { label: 'hsla(', re: /\bhsla\s*\(/gi },
  { label: 'oklch(', re: /\boklch\s*\(/gi },
  { label: 'oklab(', re: /\boklab\s*\(/gi },
  { label: 'hwb(', re: /\bhwb\s*\(/gi },
  { label: 'lab(', re: /\blab\s*\(/gi },
  { label: 'lch(', re: /\blch\s*\(/gi },
  { label: 'color-mix(', re: /\bcolor-mix\s*\(/gi },
  { label: 'color(', re: /\bcolor\s*\(/gi },
];

const TEST_FILE_RE = /\.(?:test|spec)\.|\.canary\.test\.ts$/;

function isThemePresentationFile(name: string): boolean {
  if (TEST_FILE_RE.test(name)) return false;
  return name.endsWith('.astro') || name.endsWith('.tsx');
}

function stripHtmlAttributeLiterals(text: string): string {
  return text
    .replace(/(?:href|xlink:href|id|name|headers)\s*=\s*"[^"]*"/gi, '')
    .replace(/(?:href|xlink:href|id|name|headers)\s*=\s*'[^']*'/gi, '')
    .replace(/\baria-[\w-]+\s*=\s*"[^"]*"/gi, '')
    .replace(/\baria-[\w-]+\s*=\s*'[^']*'/gi, '');
}

/** SVG paint refs like fill="url(#gradId)" — fragment ids may look like hex colors. */
function stripSvgUrlFragmentRefs(text: string): string {
  return text.replace(/url\s*\(\s*#[^)]+\)/gi, '');
}

/** CSS/HTML comments may contain hex literals that are not real color values. */
function stripComments(text: string): string {
  return text
    .replace(/\/\*[\s\S]*?\*\//g, '')
    .replace(/<!--[\s\S]*?-->/g, '');
}

function themeComponentFiles(dir: string): string[] {
  const out: string[] = [];
  for (const ent of readdirSync(dir, { withFileTypes: true })) {
    const full = join(dir, ent.name);
    if (ent.isDirectory()) out.push(...themeComponentFiles(full));
    else if (isThemePresentationFile(ent.name)) out.push(full);
  }
  return out;
}

interface Violation {
  file: string;
  literal: string;
}

function collectRawColorViolations(): Violation[] {
  const violations: Violation[] = [];
  for (const file of themeComponentFiles(THEMES)) {
    const text = stripComments(
      stripSvgUrlFragmentRefs(
        stripHtmlAttributeLiterals(readFileSync(file, 'utf8')),
      ),
    );
    const rel = relative(THEMES, file);

    for (const m of text.matchAll(HEX_COLOR_RE)) {
      violations.push({ file: rel, literal: m[0] });
    }
    for (const { label, re } of RAW_COLOR_FN_RES) {
      re.lastIndex = 0;
      if (re.test(text)) violations.push({ file: rel, literal: label });
    }
  }
  return violations;
}

describe('token-purity canary (theme-system spec §4.2)', () => {
  const files = themeComponentFiles(THEMES);
  const violations = collectRawColorViolations();

  it('scans the real theme component tree (guards against a vacuous pass)', () => {
    const astroFiles = files.filter((f) => f.endsWith('.astro'));
    const minAstroFiles = THEME_REGISTRY_META.length * 2;
    expect(astroFiles.length).toBeGreaterThanOrEqual(minAstroFiles);
  });

  it('theme components use contract tokens only — no raw color literals', () => {
    expect(
      violations,
      `Raw color literal(s) in theme source — use var(--mod-color-*) / shape tokens per §4.2:\n${violations
        .map((v) => `  ${v.file}: ${v.literal}`)
        .join('\n')}`,
    ).toEqual([]);
  });
});
