import { readFileSync, readdirSync, statSync } from 'node:fs';
import { join, relative } from 'node:path';
import { describe, expect, it } from 'vitest';

/**
 * گاردِ تم: رنگ‌ها باید از توکن‌های `styles.css` بیایند تا دارک‌مود همه‌جا کار کند.
 * hex ثابت و کلاس‌های پالت Tailwind با تغییر تم عوض نمی‌شوند.
 */

const ROOT = join(__dirname, '..');

/** رنگ‌های عمداً ثابت: دادهٔ کاربر یا پوششی که در هر دو تم یکسان است. */
const INTENTIONAL = [
  'pages/TagsSettingsPage.tsx',          // TAG_COLORS — پالت انتخابی کاربر برای برچسب
  'stores/uiStore.ts',                   // BrandFixed — هیوی نارنجی عمداً hex ثابت است
  'lib/avatarColors.ts',                 // گرادیان تزئینی آواتار، در هر دو تم یکسان
];

/** پالت پیش‌فرض Tailwind v4 — هر خانوادهٔ دیگری باید در `@theme` تعریف شده باشد. */
const TAILWIND_FAMILIES = new Set([
  'slate', 'gray', 'zinc', 'neutral', 'stone', 'red', 'orange', 'amber', 'yellow',
  'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'violet',
  'purple', 'fuchsia', 'pink', 'rose',
]);

const HEX = /#[0-9a-fA-F]{3,8}\b/;
const PALETTE_UTIL =
  /(?:^|[\s"'`{])(?:dark:|hover:|focus:|active:|group-hover:|md:|lg:|sm:)*(?:bg|text|border|divide|ring|fill|stroke)-(?:white|black|gray|slate|zinc|neutral|stone|red|green|emerald|blue|sky|amber|yellow|orange|purple|indigo|violet|pink|rose|teal|cyan)(?:-\d{2,3})?\b/;

function sourceFiles(): string[] {
  const out: string[] = [];
  (function walk(dir: string) {
    for (const entry of readdirSync(dir)) {
      const p = join(dir, entry);
      if (statSync(p).isDirectory()) { walk(p); continue; }
      if (!/\.tsx?$/.test(entry) || /\.test\.tsx?$/.test(entry)) continue;
      const rel = relative(ROOT, p);
      if (rel.startsWith('test/')) continue;
      out.push(rel);
    }
  })(ROOT);
  return out;
}

/**
 * کامنت‌ها و fallbackهای `var(--x, #hex)` رنگِ رندرشده نیستند؛ `bg-black/NN` هم
 * اسکریم است و عمداً در هر دو تم یکسان می‌ماند (مثل `.overlay` در styles.css).
 */
function strip(src: string): string {
  return src
    .replace(/\/\*[\s\S]*?\*\//g, '')
    .replace(/(^|[^:])\/\/.*$/gm, '$1')
    .replace(/var\(\s*--[a-z0-9-]+\s*,[^)]*\)/gi, 'var(--token)')
    .replace(/\bbg-black\/\d+/g, '');
}

describe('theme tokens', () => {
  const files = sourceFiles();

  it('سورس ادمین را واقعاً پیدا می‌کند', () => {
    expect(files.length).toBeGreaterThan(100);
  });

  it('هیچ کلاس dark: مرده‌ای کنار inline style رنگی نمانده است', () => {
    const offenders = files.filter((f) => {
      const src = readFileSync(join(ROOT, f), 'utf8');
      return src.split('\n').some((line) =>
        /style=\{\{[^}]*(?:color|background)/.test(line)
        && /className=[^>]*\bdark:(?:text|bg|border)-/.test(line));
    });
    expect(offenders).toEqual([]);
  });

  it('فایل‌های پاک‌شده hex ثابت ندارند', () => {
    const offenders = files
      .filter((f) => !INTENTIONAL.includes(f))
      .filter((f) => HEX.test(strip(readFileSync(join(ROOT, f), 'utf8'))));
    expect(offenders).toEqual([]);
  });

  it('فایل‌های پاک‌شده کلاس پالت Tailwind ندارند', () => {
    const offenders = files
      .filter((f) => !INTENTIONAL.includes(f))
      .filter((f) => PALETTE_UTIL.test(strip(readFileSync(join(ROOT, f), 'utf8'))));
    expect(offenders).toEqual([]);
  });

  /**
   * `text-primary-600` و امثالش هیچ رنگی تولید نمی‌کنند: نه در پالت پیش‌فرض Tailwind
   * هستند و نه در `@theme` تعریف شده‌اند — یعنی کلاسِ مرده در هر دو تم.
   */
  it('کلاس رنگی با مقیاس تعریف‌نشده ندارد', () => {
    const theme = readFileSync(join(ROOT, 'styles.css'), 'utf8');
    const declared = new Set(
      [...theme.matchAll(/--color-([a-z]+)-\d{2,3}\s*:/g)].map((m) => m[1]),
    );
    const utilities = new RegExp(
      String.raw`\b(?:bg|text|border|ring|divide|from|to|via|outline|accent|caret|fill|stroke|placeholder|shadow)-([a-z]+)-\d{2,3}\b`,
      'g',
    );
    const offenders: string[] = [];
    for (const f of files) {
      const src = strip(readFileSync(join(ROOT, f), 'utf8'));
      for (const m of src.matchAll(utilities)) {
        if (!TAILWIND_FAMILIES.has(m[1]) && !declared.has(m[1])) offenders.push(`${f}: ${m[0]}`);
      }
    }
    expect(offenders).toEqual([]);
  });
});
