/**
 * r18.test.ts — R18-colors-outside-classname detection.
 *
 * R18 catches Tailwind color utilities in JS data structures (objects, arrays,
 * config maps) OUTSIDE className attributes. Unlike R14/R15 which scan className
 * values only, R18 finds colors in string literals that bypass the design token
 * system (e.g. const OPTIONS = [{ selectedBorder: 'border-gray-500' }]).
 */
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { audit, loadTokens } from '../audit.js';
import type { UiPolishArgs } from '../types.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

function writeFile(root: string, rel: string, content: string): void {
  const abs = path.join(root, rel);
  mkdirSync(path.dirname(abs), { recursive: true });
  writeFileSync(abs, content, 'utf-8');
}

const PAGE_WITH_COLOR_IN_OBJECT = `
export function TestPage() {
  const OPTIONS = [{ selectedBorder: 'border-gray-500', dotBg: 'bg-gray-500' }];
  return <div />;
}
`;

const PAGE_WITH_COLOR_IN_CLASSNAME = `
export function TestPage() {
  return <div className="bg-blue-500 text-white" />;
}
`;

const PAGE_WITH_COLOR_IN_ARRAY = `
export function TestPage() {
  const colors = ['text-green-600', 'text-red-500'];
  return <div />;
}
`;

const PAGE_WITHOUT_COLORS = `
export function TestPage() {
  return <div className="flex items-center gap-2" />;
}
`;

const PAGE_WITH_TOKENS_ONLY = `
export function TestPage() {
  const styles = {
    success: 'bg-[var(--success-bg)] text-[var(--success-text)]',
    error: 'bg-[var(--error-bg)] text-[var(--error-text)]',
  };
  return <div />;
}
`;

const PAGE_WITH_MIXED = `
export function TestPage() {
  const config = {
    selected: 'border-blue-500',
    default: 'border-gray-300',
  };
  return (
    <div>
      <div className="flex items-center" />
      <div className="bg-blue-500 text-white" />
    </div>
  );
}
`;

function baseArgs(projectPath: string): UiPolishArgs {
  return {
    projectPath,
    mode: 'audit' as const,
    rules: ['R18'],
    paths: [],
    urls: [],
    regenerateThreshold: 0,
  };
}

describe('ui-polish / R18 — colors-outside-classname', () => {
  let root: string;
  let tokens: any;

  beforeEach(async () => {
    root = mkdtempSync(path.join(tmpdir(), 'audit-r18-'));
    const tokensPath = path.resolve(__dirname, '../../../tokens.json');
    tokens = await loadTokens(tokensPath);
  });

  afterEach(() => {
    rmSync(root, { recursive: true, force: true });
  });

  it('fires when color utility appears in object value', async () => {
    writeFile(root, 'src/pages/test/TestPage.tsx', PAGE_WITH_COLOR_IN_OBJECT);
    const report = await audit(baseArgs(root), tokens);
    const findings = report.findings.filter((f) => f.ruleId === 'R18-colors-outside-classname');
    expect(findings.length).toBeGreaterThan(0);
    expect(findings.some((f) => f.snippet?.includes('border-gray-500'))).toBe(true);
    expect(findings.some((f) => f.snippet?.includes('bg-gray-500'))).toBe(true);
  });

  it('does not fire for color in className (that is R14/R15 domain)', async () => {
    writeFile(root, 'src/pages/test/TestPage.tsx', PAGE_WITH_COLOR_IN_CLASSNAME);
    const report = await audit(baseArgs(root), tokens);
    const r18Findings = report.findings.filter((f) => f.ruleId === 'R18-colors-outside-classname');
    expect(r18Findings.length).toBe(0);
  });

  it('fires when color utilities appear in array literal', async () => {
    writeFile(root, 'src/pages/test/TestPage.tsx', PAGE_WITH_COLOR_IN_ARRAY);
    const report = await audit(baseArgs(root), tokens);
    const findings = report.findings.filter((f) => f.ruleId === 'R18-colors-outside-classname');
    expect(findings.length).toBeGreaterThan(0);
    expect(findings.some((f) => f.snippet?.includes('text-green-600'))).toBe(true);
    expect(findings.some((f) => f.snippet?.includes('text-red-500'))).toBe(true);
  });

  it('does not fire when no color utilities are present', async () => {
    writeFile(root, 'src/pages/test/TestPage.tsx', PAGE_WITHOUT_COLORS);
    const report = await audit(baseArgs(root), tokens);
    const findings = report.findings.filter((f) => f.ruleId === 'R18-colors-outside-classname');
    expect(findings.length).toBe(0);
  });

  it('passes when colors are properly wrapped in var(--token)', async () => {
    writeFile(root, 'src/pages/test/TestPage.tsx', PAGE_WITH_TOKENS_ONLY);
    const report = await audit(baseArgs(root), tokens);
    const findings = report.findings.filter((f) => f.ruleId === 'R18-colors-outside-classname');
    expect(findings.length).toBe(0);
  });

  it('fires on object but not className (mixed case)', async () => {
    writeFile(root, 'src/pages/test/TestPage.tsx', PAGE_WITH_MIXED);
    const report = await audit(baseArgs(root), tokens);
    const findings = report.findings.filter((f) => f.ruleId === 'R18-colors-outside-classname');
    // Should find the 'border-blue-500' and 'border-gray-300' in the config object
    expect(findings.length).toBeGreaterThan(0);
    // The className colors are out of R18 scope (handled by R14/R15)
    const inObject = findings.filter((f) =>
      f.snippet?.includes('border-blue-500') || f.snippet?.includes('border-gray-300')
    );
    expect(inObject.length).toBeGreaterThan(0);
  });
});
