import { describe, it, expect } from 'vitest';
import { generate } from '../generate.js';
import type { ScaffoldUiPrimitivesInput } from '../types.js';

function fixture(overrides: Partial<ScaffoldUiPrimitivesInput> = {}): ScaffoldUiPrimitivesInput {
  return { projectPath: '/tmp/web', appCode: 'crm', force: false, ...overrides };
}

const NEW_PRIMITIVES = [
  'src/components/ui/DateInput.tsx',
  'src/components/ui/EnumSelect.tsx',
  'src/components/ui/MultiSelect.tsx',
];

function fileByPath(path: string): { path: string; content: string; strategy: string } {
  const files = generate(fixture());
  const f = files.find((x) => x.path === path);
  if (!f) throw new Error(`missing generated file ${path}`);
  return f;
}

describe('scaffold-ui-primitives / new primitives — file shape', () => {
  it.each(NEW_PRIMITIVES)('emits %s with overwrite strategy + AUTO-GENERATED header', (path) => {
    const f = fileByPath(path);
    expect(f.strategy).toBe('overwrite');
    expect(f.content).toMatch(/AUTO-GENERATED/);
    expect(f.content).toMatch(/scaffold-ui-primitives/);
  });
});

describe('scaffold-ui-primitives / new primitives — theme compliance (non-negotiable)', () => {
  const FORBIDDEN_PALETTES = [
    'slate', 'gray', 'zinc', 'neutral', 'stone',
    'red', 'orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal',
    'cyan', 'sky', 'blue', 'indigo', 'violet', 'purple', 'fuchsia', 'pink', 'rose',
  ];

  it.each(NEW_PRIMITIVES)('%s uses zero hardcoded Tailwind color classes', (path) => {
    const content = fileByPath(path).content;
    for (const palette of FORBIDDEN_PALETTES) {
      const re = new RegExp(`\\b(?:bg|text|border|ring|placeholder|from|to|via|divide|outline)-${palette}-\\d{2,3}\\b`);
      const matches = content.match(re);
      expect(matches, `Hardcoded Tailwind color (${palette}): ${matches?.join(', ')}`).toBeNull();
    }
  });

  it.each(NEW_PRIMITIVES)('%s uses zero dark: prefix (theme swap handles dark mode at the token layer)', (path) => {
    expect(fileByPath(path).content).not.toMatch(/\bdark:/);
  });

  it.each(NEW_PRIMITIVES)('%s uses zero hex literals in className', (path) => {
    const content = fileByPath(path).content;
    const classNameBlocks = content.match(/className=[`"']([^`"']*)[`"']/g) ?? [];
    for (const block of classNameBlocks) {
      expect(block, `Hex literal in className: ${block}`).not.toMatch(/#[0-9a-fA-F]{3,8}/);
    }
  });

  it.each(NEW_PRIMITIVES)('%s references the SmartStack input tokens (radius-input, border-color, bg-card, accent-500)', (path) => {
    const content = fileByPath(path).content;
    expect(content).toMatch(/var\(--radius-input/);
    expect(content).toMatch(/var\(--border-color\)/);
    expect(content).toMatch(/var\(--bg-card\)/);
    expect(content).toMatch(/var\(--color-accent-500\)/);
  });
});

describe('scaffold-ui-primitives / new primitives — component contracts', () => {
  it('DateInput exports the component with an ISO value contract + calendar popover', () => {
    const c = fileByPath('src/components/ui/DateInput.tsx').content;
    expect(c).toMatch(/export function DateInput\(/);
    expect(c).toMatch(/value: string \| null/);
    expect(c).toMatch(/onChange: \(value: string \| null\) => void/);
    expect(c).toMatch(/label\?: string/);
    // Real calendar, not native input: month grid + Intl formatting.
    expect(c).toMatch(/Intl\.DateTimeFormat/);
    expect(c).toMatch(/grid-cols-7/);
    expect(c).toMatch(/role="dialog"/);
    expect(c).not.toMatch(/type="date"/);
  });

  it('EnumSelect exports a single-select listbox with options', () => {
    const c = fileByPath('src/components/ui/EnumSelect.tsx').content;
    expect(c).toMatch(/export function EnumSelect\(/);
    expect(c).toMatch(/options: SelectOption\[\]/);
    expect(c).toMatch(/value: string \| null/);
    expect(c).toMatch(/onChange: \(value: string \| null\) => void/);
    expect(c).toMatch(/role="listbox"/);
    expect(c).toMatch(/role="option"/);
    // Themed dropdown, not the native <select>.
    expect(c).not.toMatch(/<select/);
  });

  it('MultiSelect exports a multi-value chips control (string[])', () => {
    const c = fileByPath('src/components/ui/MultiSelect.tsx').content;
    expect(c).toMatch(/export function MultiSelect\(/);
    expect(c).toMatch(/value: string\[\]/);
    expect(c).toMatch(/onChange: \(value: string\[\]\) => void/);
    expect(c).toMatch(/aria-multiselectable="true"/);
    expect(c).not.toMatch(/<select/);
  });

  it('all three handle keyboard navigation + outside-click close', () => {
    for (const path of NEW_PRIMITIVES) {
      const c = fileByPath(path).content;
      expect(c, path).toMatch(/e\.key === 'Escape'/);
      expect(c, path).toMatch(/addEventListener\('mousedown'/);
    }
  });
});

describe('scaffold-ui-primitives / new primitives — i18n namespaces', () => {
  it('each locale common.json carries dateInput / enumSelect / multiSelect (entityLookup preserved)', () => {
    const files = generate(fixture());
    for (const locale of ['fr', 'en', 'it', 'de']) {
      const f = files.find((x) => x.path === `src/i18n/locales/${locale}/common.json`)!;
      const parsed = JSON.parse(f.content);
      expect(parsed.entityLookup?.placeholder, `${locale} entityLookup preserved`).toBeTruthy();
      expect(parsed.dateInput?.placeholder, `${locale} dateInput`).toBeTruthy();
      expect(parsed.dateInput?.previousMonth).toBeTruthy();
      expect(parsed.enumSelect?.placeholder, `${locale} enumSelect`).toBeTruthy();
      expect(parsed.multiSelect?.placeholder, `${locale} multiSelect`).toBeTruthy();
      expect(parsed.multiSelect?.remove).toBeTruthy();
    }
  });

  it('components resolve their own i18n namespace via useTranslation("common")', () => {
    expect(fileByPath('src/components/ui/DateInput.tsx').content).toMatch(/dateInput\.placeholder/);
    expect(fileByPath('src/components/ui/EnumSelect.tsx').content).toMatch(/enumSelect\.placeholder/);
    expect(fileByPath('src/components/ui/MultiSelect.tsx').content).toMatch(/multiSelect\.placeholder/);
  });
});
