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,
  };
}

describe('scaffold-ui-primitives / generate — file shape', () => {
  it('emits EntityLookup.tsx with overwrite strategy', () => {
    const files = generate(fixture());
    const lookup = files.find((f) => f.path === 'src/components/ui/EntityLookup.tsx');
    expect(lookup).toBeDefined();
    expect(lookup!.strategy).toBe('overwrite');
  });

  it('emits one locale file per supported language with deep-merge-json strategy', () => {
    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`);
      expect(f, `missing locale ${locale}`).toBeDefined();
      expect(f!.strategy).toBe('deep-merge-json');
      const parsed = JSON.parse(f!.content);
      expect(parsed.entityLookup).toBeDefined();
      expect(parsed.entityLookup.placeholder).toBeTruthy();
      expect(parsed.entityLookup.empty).toBeTruthy();
      expect(parsed.entityLookup.error).toBeTruthy();
    }
  });

  it('includes the AUTO-GENERATED header so re-runs do not surprise the dev', () => {
    const files = generate(fixture());
    const lookup = files.find((f) => f.path === 'src/components/ui/EntityLookup.tsx')!;
    expect(lookup.content).toMatch(/AUTO-GENERATED/);
    expect(lookup.content).toMatch(/scaffold-ui-primitives/);
  });
});

describe('scaffold-ui-primitives / generate — theme compliance (non-negotiable)', () => {
  // Catalog of Tailwind palette tokens. Any of these in the EntityLookup
  // would break the theme swap contract — the audit lives here so a future
  // hand-edit can't silently regress.
  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('uses zero hardcoded Tailwind color classes (bg-*-XXX / text-*-XXX / border-*-XXX)', () => {
    const files = generate(fixture());
    const lookup = files.find((f) => f.path === 'src/components/ui/EntityLookup.tsx')!;
    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 = lookup.content.match(re);
      expect(matches, `Hardcoded Tailwind color found (${palette}): ${matches?.join(', ')}`).toBeNull();
    }
  });

  it('uses zero dark: prefix (theme swap handles dark mode at the token layer)', () => {
    const files = generate(fixture());
    const lookup = files.find((f) => f.path === 'src/components/ui/EntityLookup.tsx')!;
    expect(lookup.content).not.toMatch(/\bdark:/);
  });

  it('uses zero hex literals in className (only via tokens)', () => {
    const files = generate(fixture());
    const lookup = files.find((f) => f.path === 'src/components/ui/EntityLookup.tsx')!;
    // Spot any `className="…#abc123…"` style hex literals — there must be none.
    const classNameBlocks = lookup.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('references the SmartStack tokens it depends on (bg-card, border-color, accent-500, text-primary, text-muted, bg-hover)', () => {
    const files = generate(fixture());
    const lookup = files.find((f) => f.path === 'src/components/ui/EntityLookup.tsx')!;
    expect(lookup.content).toMatch(/var\(--bg-card\)/);
    expect(lookup.content).toMatch(/var\(--border-color\)/);
    expect(lookup.content).toMatch(/var\(--color-accent-500\)/);
    expect(lookup.content).toMatch(/var\(--text-primary\)/);
    expect(lookup.content).toMatch(/var\(--text-muted\)/);
    expect(lookup.content).toMatch(/var\(--bg-hover\)/);
  });
});

describe('scaffold-ui-primitives / generate — EntityLookup contract', () => {
  it('exports an EntityLookup component with the documented props shape', () => {
    const files = generate(fixture());
    const lookup = files.find((f) => f.path === 'src/components/ui/EntityLookup.tsx')!;
    expect(lookup.content).toMatch(/export function EntityLookup<TItem extends RefItem = RefItem>/);
    // Required props per the spec
    expect(lookup.content).toMatch(/apiEndpoint: string/);
    expect(lookup.content).toMatch(/value: string \| null/);
    expect(lookup.content).toMatch(/onChange: \(id: string \| null, item\?: TItem\) => void/);
    expect(lookup.content).toMatch(/label: string/);
    // Optional props that must be present in the interface
    expect(lookup.content).toMatch(/mapOption\?:/);
    expect(lookup.content).toMatch(/required\?: boolean/);
    expect(lookup.content).toMatch(/disabled\?: boolean/);
    expect(lookup.content).toMatch(/error\?: string/);
    expect(lookup.content).toMatch(/multi\?: boolean/);
  });

  it('fetches lookup results from apiEndpoint using api.get with debouncedSearch', () => {
    const files = generate(fixture());
    const lookup = files.find((f) => f.path === 'src/components/ui/EntityLookup.tsx')!;
    expect(lookup.content).toMatch(/api\.get<PaginatedResult<TItem>>\(apiEndpoint/);
    expect(lookup.content).toMatch(/search: debouncedSearch/);
  });

  it('emits 300ms debounce constant', () => {
    const files = generate(fixture());
    const lookup = files.find((f) => f.path === 'src/components/ui/EntityLookup.tsx')!;
    expect(lookup.content).toMatch(/DEBOUNCE_MS = 300/);
  });

  it('renders an ARIA combobox with role=combobox + role=listbox + role=option', () => {
    const files = generate(fixture());
    const lookup = files.find((f) => f.path === 'src/components/ui/EntityLookup.tsx')!;
    expect(lookup.content).toMatch(/role="combobox"/);
    expect(lookup.content).toMatch(/role="listbox"/);
    expect(lookup.content).toMatch(/role="option"/);
    expect(lookup.content).toMatch(/aria-expanded=\{open\}/);
    expect(lookup.content).toMatch(/aria-autocomplete="list"/);
  });

  it('handles keyboard navigation (ArrowDown/Up/Enter/Escape)', () => {
    const files = generate(fixture());
    const lookup = files.find((f) => f.path === 'src/components/ui/EntityLookup.tsx')!;
    expect(lookup.content).toMatch(/e\.key === 'ArrowDown'/);
    expect(lookup.content).toMatch(/e\.key === 'ArrowUp'/);
    expect(lookup.content).toMatch(/e\.key === 'Enter'/);
    expect(lookup.content).toMatch(/e\.key === 'Escape'/);
  });

  it('throws not-implemented when multi=true (reserved API)', () => {
    const files = generate(fixture());
    const lookup = files.find((f) => f.path === 'src/components/ui/EntityLookup.tsx')!;
    expect(lookup.content).toMatch(/multi-select is not implemented yet/);
  });

  it('uses i18n via useTranslation("common") with entityLookup.* namespace', () => {
    const files = generate(fixture());
    const lookup = files.find((f) => f.path === 'src/components/ui/EntityLookup.tsx')!;
    expect(lookup.content).toMatch(/useTranslation\('common'\)/);
    expect(lookup.content).toMatch(/entityLookup\.placeholder/);
    expect(lookup.content).toMatch(/entityLookup\.empty/);
    expect(lookup.content).toMatch(/entityLookup\.error/);
    expect(lookup.content).toMatch(/entityLookup\.loading/);
    expect(lookup.content).toMatch(/entityLookup\.clear/);
  });
});
