import { describe, it, expect } from 'vitest';
import {
  generateThemeFromPrompt,
  localGenerate,
} from '../generate.js';
import {
  parseHex,
  toHex,
  relativeLuminance,
  contrast,
  report,
  auditTheme,
  adjustForContrast,
  enforceContrast,
} from '../contrast.js';
import { applyTheme } from '../apply.js';

describe('hex parsing', () => {
  it('parses #rgb and #rrggbb', () => {
    expect(parseHex('#000')).toEqual({ r: 0, g: 0, b: 0 });
    expect(parseHex('#fff')).toEqual({ r: 255, g: 255, b: 255 });
    expect(parseHex('#ff0080')).toEqual({ r: 255, g: 0, b: 128 });
  });
  it('throws on invalid hex', () => {
    expect(() => parseHex('not-a-colour')).toThrow();
  });
  it('toHex round-trips', () => {
    expect(toHex(255, 0, 128)).toBe('#ff0080');
  });
  it('toHex clamps out-of-range', () => {
    expect(toHex(300, -50, 128)).toBe('#ff0080');
  });
});

describe('contrast math', () => {
  it('relativeLuminance: pure white = 1, pure black = 0', () => {
    expect(relativeLuminance('#ffffff')).toBeCloseTo(1, 5);
    expect(relativeLuminance('#000000')).toBeCloseTo(0, 5);
  });
  it('contrast: black on white = 21', () => {
    expect(contrast('#000', '#fff')).toBeCloseTo(21, 1);
  });
  it('report: 4.5+ marks AA but not AAA', () => {
    const r = report('#444', '#fff');
    expect(r.aa).toBe(true);
    expect(r.aaa).toBe(true); // #444 on #fff is actually about 9.7
  });
  it('report: low contrast fails both', () => {
    const r = report('#ddd', '#fff');
    expect(r.aa).toBe(false);
    expect(r.aaa).toBe(false);
  });
});

describe('adjustForContrast', () => {
  it('darkens fg against a light bg until target met', () => {
    const out = adjustForContrast('#cccccc', '#ffffff', 4.5);
    expect(contrast(out, '#ffffff')).toBeGreaterThanOrEqual(4.5);
  });
  it('lightens fg against a dark bg until target met', () => {
    const out = adjustForContrast('#555555', '#000000', 4.5);
    expect(contrast(out, '#000000')).toBeGreaterThanOrEqual(4.5);
  });
});

describe('localGenerate (deterministic vibes)', () => {
  it('returns a palette for known keywords', () => {
    const t = localGenerate('A moody midnight noir agent');
    expect(t.name).toBe('midnight');
    expect(t.accent).toMatch(/^#/);
  });
  it('falls back to default on no match', () => {
    const t = localGenerate('definitely-not-a-keyword');
    expect(t.name).toBe('default');
    expect(t.accent).toBe('#6366f1');
  });
  it('different prompts route to different palettes', () => {
    const a = localGenerate('citrus sunrise warm');
    const b = localGenerate('forest sage moss');
    expect(a.name).not.toBe(b.name);
  });
});

describe('generateThemeFromPrompt', () => {
  it('runs the local generator + enforces AA by default', async () => {
    const t = await generateThemeFromPrompt('citrus sunrise');
    const audit = auditTheme(t);
    expect(audit.textPrimaryOnSurface.aa).toBe(true);
  });
  it('respects a custom async generate fn', async () => {
    const stub = async (): Promise<typeof t> => ({
      name: 'stub',
      accent: '#888888',
      surface: '#ffffff',
      surfaceCard: '#ffffff',
      surfaceMuted: '#eeeeee',
      textPrimary: '#bbbbbb', // intentionally low contrast
      textSecondary: '#cccccc',
    });
    const t = await generateThemeFromPrompt('whatever', {
      generate: stub,
      enforce: 'aa',
    });
    // After enforcement, primary text contrast should pass AA.
    expect(contrast(t.textPrimary, t.surface)).toBeGreaterThanOrEqual(4.5);
  });
  it('enforce=off returns raw generator output', async () => {
    const raw = await generateThemeFromPrompt('whatever', {
      enforce: 'off',
      generate: async () => ({
        accent: '#bbbbbb',
        surface: '#ffffff',
        surfaceCard: '#ffffff',
        surfaceMuted: '#eeeeee',
        textPrimary: '#cccccc',
        textSecondary: '#dddddd',
      }),
    });
    expect(raw.textPrimary).toBe('#cccccc');
  });
});

describe('enforceContrast', () => {
  it('does not corrupt already-passing palettes', () => {
    const base = localGenerate('forest');
    const out = enforceContrast(base, 'aa');
    expect(out.surface).toBe(base.surface);
    expect(out.surfaceCard).toBe(base.surfaceCard);
  });
});

describe('applyTheme', () => {
  it('sets + cleans up CSS custom properties on the target', () => {
    const target = document.createElement('div');
    const restore = applyTheme(
      {
        accent: '#7c3aed',
        surface: '#ffffff',
        surfaceCard: '#ffffff',
        surfaceMuted: '#eeeeee',
        textPrimary: '#111111',
        textSecondary: '#666666',
      },
      target,
    );
    expect(target.style.getPropertyValue('--bl-accent')).toBe('#7c3aed');
    restore();
    expect(target.style.getPropertyValue('--bl-accent')).toBe('');
  });
});
