#!/usr/bin/env node
// #region AuditFrontendStyle [C:3] [TYPE Module] [SEMANTICS audit, design-tokens, guardrail, script, quality-gate]
// @BRIEF Audits frontend source files for design token violations (raw Tailwind colors), manual buttons, oversized pages, and Model-first violations.
// @LAYER Infra
// @RATIONALE Before PR 3-4 page decomposition runs, we need a CI-checkable guardrail that prevents new drift from entering the codebase. This script enforces the semantic-token-only rule established in PR 0+1.
// @USAGE node scripts/audit-frontend-style.mjs [--fix] [--verbose]

import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
import { resolve, relative, dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = resolve(__dirname, '..');
const VERBOSE = process.argv.includes('--verbose');

// ── File discovery (no external deps) ──────────────────────────

function walk(dir, pattern, maxDepth = 10) {
  if (maxDepth <= 0) return [];
  const results = [];
  try {
    for (const entry of readdirSync(dir, { withFileTypes: true })) {
      const full = join(dir, entry.name);
      if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules' && entry.name !== 'build') {
        results.push(...walk(full, pattern, maxDepth - 1));
      } else if (entry.isFile() && pattern.test(entry.name)) {
        results.push(full);
      }
    }
  } catch { /* skip */ }
  return results;
}

const PAGE_FILES = walk(join(ROOT, 'src', 'routes'), /\+page\.svelte$/);
const COMPONENT_FILES = walk(join(ROOT, 'src', 'lib', 'components'), /\.svelte$/);
const LEGACY_COMPONENTS = walk(join(ROOT, 'src', 'components'), /\.svelte$/);

// ── Rules ──────────────────────────────────────────────────────

const RAW_COLOR_PATTERNS = [
  // Raw tailwind color classes (banned in routes/** and lib/ui/**)
  { regex: /\bbg-blue-\d+\b/, message: 'bg-blue-* → bg-primary' },
  { regex: /\bbg-indigo-\d+\b/, message: 'bg-indigo-* → bg-primary or bg-info' },
  { regex: /\bbg-red-\d+\b/, message: 'bg-red-* → bg-destructive or bg-destructive-light' },
  { regex: /\bbg-green-\d+\b/, message: 'bg-green-* → bg-success or bg-success-light' },
  { regex: /\bbg-amber-\d+\b/, message: 'bg-amber-* → bg-warning or bg-warning-light' },
  { regex: /\bbg-emerald-\d+\b/, message: 'bg-emerald-* → bg-success variants' },
  { regex: /\bbg-violet-\d+\b/, message: 'bg-violet-* → bg-primary-light or bg-info-light' },
  { regex: /\bbg-gray-\d+\b/, message: 'bg-gray-* → bg-surface-muted or bg-surface-card' },
  { regex: /\bbg-slate-\d+\b/, message: 'bg-slate-* → bg-surface-* tokens' },
  { regex: /\bbg-white\b(?!.*\bbg-surface-card\b)/, message: 'bg-white → bg-surface-card (use semantic token)' },
  { regex: /\btext-blue-\d+\b/, message: 'text-blue-* → text-primary' },
  { regex: /\btext-indigo-\d+\b/, message: 'text-indigo-* → text-primary or text-info' },
  { regex: /\btext-red-\d+\b/, message: 'text-red-* → text-destructive' },
  { regex: /\btext-green-\d+\b/, message: 'text-green-* → text-success' },
  { regex: /\btext-amber-\d+\b/, message: 'text-amber-* → text-warning' },
  { regex: /\btext-emerald-\d+\b/, message: 'text-emerald-* → text-success' },
  { regex: /\btext-gray-\d+\b/, message: 'text-gray-* → text-text or text-text-muted' },
  { regex: /\btext-slate-\d+\b/, message: 'text-slate-* → text-* tokens' },
  { regex: /\bborder-blue-\d+\b/, message: 'border-blue-* → border-primary-ring' },
  { regex: /\bborder-indigo-\d+\b/, message: 'border-indigo-* → border-primary-ring or border-info-ring' },
  { regex: /\bborder-red-\d+\b/, message: 'border-red-* → border-destructive-ring' },
  { regex: /\bborder-green-\d+\b/, message: 'border-green-* → border-success' },
  { regex: /\bborder-amber-\d+\b/, message: 'border-amber-* → border-warning' },
  { regex: /\bborder-gray-\d+\b/, message: 'border-gray-* → border-border or border-border-strong' },
  { regex: /\bborder-slate-\d+\b/, message: 'border-slate-* → border-* tokens' },
  { regex: /\bring-(blue|indigo|red|green|amber|emerald|violet|gray|slate)-\d+\b/, message: 'ring-*-* → use semantic token ring (primary-ring, destructive-ring, etc.)' },
];

const GRADIENT_PATTERN = /\bbg-gradient-to-[brtl]{1,2}\s+from-\S+\s+via-\S+\s+to-\S+\b/;

const MANUAL_BUTTON_REGEX = /<button\s+(?!.*\bdisabled\b)/;  // false positives possible — manual review

// ── File scopes ────────────────────────────────────────────────

// (populated above via walk())
// PAGE_FILES, COMPONENT_FILES, LEGACY_COMPONENTS

// ── Checks ─────────────────────────────────────────────────────

const violations = [];
const warnings = [];

for (const fullPath of [...PAGE_FILES, ...COMPONENT_FILES]) {
  const relPath = relative(ROOT, fullPath);
  const source = readFileSync(fullPath, 'utf-8');
  const lines = source.split('\n');

  // Check 1: Raw color patterns
  for (const { regex, message } of RAW_COLOR_PATTERNS) {
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i];
      if (regex.test(line)) {
        violations.push({
          file: relative(ROOT, fullPath),
          line: i + 1,
          rule: 'raw-color',
          message,
          snippet: line.trim().substring(0, 120),
        });
      }
    }
  }

  // Check 2: Gradient patterns in page files
  if (relPath.startsWith('src/routes/') && GRADIENT_PATTERN.test(source)) {
    violations.push({
      file: relative(ROOT, fullPath),
      line: 0,
      rule: 'no-gradient',
      message: 'bg-gradient-* → use semantic surface tokens',
      snippet: '[gradient found]',
    });
  }

  // Check 3: Page too large
  if (relPath.startsWith('src/routes/') && lines.length > 400) {
    violations.push({
      file: relative(ROOT, fullPath),
      line: 0,
      rule: 'page-too-large',
      message: `Page has ${lines.length} lines (max 400). Decompose with Model + components.`,
      snippet: '',
    });
  }

  // Check 4: Count $state atoms vs Model import
  if (relPath.startsWith('src/routes/')) {
    const stateCount = (source.match(/\$\state\(/g) || []).length;
    const hasModelImport = /import\s+\{.*\}\s+from\s+['"].*Model\.svelte\.ts['"]/.test(source);
    const hasModelNew = /\bnew\s+\w+Model\(\)/.test(source);

    if (stateCount > 5 && !hasModelImport && !hasModelNew) {
      warnings.push({
        file: relative(ROOT, fullPath),
        line: 0,
        rule: 'no-screen-model',
        message: `Page has ${stateCount} $state atoms but no Model import. Consider creating a Screen Model.`,
        snippet: '',
      });
    }
  }

  // Check 5: Manual <button> in page files (warning only — false positives for step wizards etc.)
  if (relPath.startsWith('src/routes/')) {
    const buttonCount = (source.match(/<button\b/g) || []).length;
    const hasButtonImport = /import\s+\{.*Button.*\}\s+from\s+['"]\$lib\/ui['"]/.test(source);
    if (buttonCount > 0 && !hasButtonImport) {
      warnings.push({
        file: relative(ROOT, fullPath),
        line: 0,
        rule: 'manual-button',
        message: `Page has ${buttonCount} raw <button> elements but no <Button> import from $lib/ui.`,
        snippet: '',
      });
    }
  }
}

// Check 6: New files in legacy components/ directory (frozen zone)
if (LEGACY_COMPONENTS.length > 0) {
  console.log(`\n⚠  LEGACY FROZEN: ${LEGACY_COMPONENTS.length} files remain in src/components/. No new files should be added.`);
}

// ── Report ─────────────────────────────────────────────────────

const errorCount = violations.filter(v => v.rule !== 'page-too-large' && v.rule !== 'no-gradient').length;
const oversizedPages = violations.filter(v => v.rule === 'page-too-large');
const gradientPages = violations.filter(v => v.rule === 'no-gradient');

console.log('\n═══ FRONTEND STYLE AUDIT ═══\n');

if (violations.length === 0 && warnings.length === 0) {
  console.log('✅ All checks passed. No violations found.\n');
  process.exit(0);
}

const violationFiles = new Set(violations.map(v => v.file));
const warningFiles = new Set(warnings.map(v => v.file));

console.log(`🔴 VIOLATIONS: ${errorCount} color-token + ${oversizedPages.length} oversized-pages`);
console.log(`🟡 WARNINGS:  ${warnings.length} (model-first, manual-button)`);
console.log(`📁 Files:     ${violationFiles.size + warningFiles.size} total\n`);

if (VERBOSE) {
  for (const v of violations) {
    console.log(`  🔴 ${v.file}:${v.line}  [${v.rule}]  ${v.message}`);
    if (v.snippet) console.log(`      → ${v.snippet}`);
  }
  for (const w of warnings) {
    console.log(`  🟡 ${w.file}  [${w.rule}]  ${w.message}`);
  }
} else {
  // Summary mode
  const byFile = {};
  for (const v of violations) {
    byFile[v.file] = (byFile[v.file] || 0) + 1;
  }
  for (const [file, count] of Object.entries(byFile).sort(([,a], [,b]) => b - a)) {
    console.log(`  ${count.toString().padEnd(4)} violations  ${file}`);
  }
}

// Oversized page summary
if (oversizedPages.length > 0) {
  console.log(`\n📄 OVERSIZED PAGES (>400 lines):`);
  for (const p of oversizedPages) {
    console.log(`    ${p.file} — ${p.message}`);
  }
}

console.log('');
if (errorCount > 0) {
  console.log(`❌ ${errorCount} design-token violations found. Run with --verbose for details.`);
} else {
  console.log('✅ No design-token violations.');
}
if (warnings.length > 0) {
  console.log(`⚠  ${warnings.length} warnings (model-first / manual-button).`);
}

// Exit with error code if violations found (for CI)
process.exit(errorCount > 0 ? 1 : 0);
// #endregion AuditFrontendStyle
