import { describe, expect, it } from 'vitest';
import type { GameData, MachineDef, MachineKind, SimEvent } from '../contracts';
import { createAccentLookup, machineChassis } from './palette';
import { eventToast, jamText, machineFlavor } from './voice';

function def(over: Partial<MachineDef> = {}): MachineDef {
  return {
    id: 'quantizer', name: 'QUANTIZER', codex: '', kind: 'crafter',
    footprint: { x: 2, y: 2 }, recipes: [], powerDraw: 0, asset: 'quantizer', ...over,
  };
}

const named = (id: number) => (id === 5 ? 'MOSH REACTOR' : `UNIT ${id}`);

describe('machineFlavor', () => {
  it('prefers the codex line from data once LANE-DATA fills it', () => {
    expect(machineFlavor(def({ flavor: 'The dial goes to CRIME.' }), 'quantizer'))
      .toBe('The dial goes to CRIME.');
  });

  it('falls back to the hand-transcribed line while data is empty', () => {
    expect(machineFlavor(def(), 'quantizer')).toContain('Rounds coefficients to zero');
  });

  it('gives an unknown machine generic copy rather than nothing', () => {
    expect(machineFlavor(def({ id: 'wormhole' }), 'wormhole')).toContain('Industrial equipment');
  });

  it('survives a missing def', () => {
    expect(machineFlavor(undefined, 'belt')).toContain('Moving things is the entire job');
  });
});

describe('jamText', () => {
  it('distinguishes backed-up from starved', () => {
    expect(jamText('output full')).toBe('OUTPUT BUFFER AT CAPACITY. THIS IS A YOU PROBLEM.');
    expect(jamText('starved')).toBe('INTAKE STARVED. NOTHING IS ARRIVING.');
  });

  it('passes an unknown reason through uppercased rather than swallowing it', () => {
    expect(jamText('belt on fire')).toBe('BELT ON FIRE');
  });
});

describe('eventToast', () => {
  it('names the machine that jammed and says why', () => {
    const e: SimEvent = { kind: 'jammed', entity: 5, reason: 'starved', tick: 1 };
    expect(eventToast(e, named)).toBe('MOSH REACTOR: INTAKE STARVED. NOTHING IS ARRIVING.');
  });

  it('has different copy for a brownout arriving and lifting', () => {
    const on = eventToast({ kind: 'brownout', on: true, tick: 1 }, named);
    const off = eventToast({ kind: 'brownout', on: false, tick: 2 }, named);
    expect(on).not.toBe(off);
    expect(on).toContain('STILL LIFE');
  });

  it('reports a thermal scram against the machine that scrammed', () => {
    const e: SimEvent = { kind: 'scram', entity: 5, on: true, tick: 9 };
    expect(eventToast(e, named)).toContain('MOSH REACTOR');
    expect(eventToast(e, named)).toContain('THERMAL SCRAM');
  });

  it('confirms a removal, because demolition should feel acknowledged', () => {
    const e: SimEvent = { kind: 'removed', entity: 1, def: 'belt', tick: 1 };
    expect(eventToast(e, named)).toBe('UNIT RECLAIMED. THE FLOOR REMEMBERS.');
  });

  it('stays quiet for the high-frequency events', () => {
    for (const e of [
      { kind: 'shipped', item: 'melt', count: 1, tick: 1 },
      { kind: 'crafted', entity: 1, recipe: 'mosh', tick: 1 },
      { kind: 'placed', entity: 1, def: 'belt', tick: 1 },
    ] as SimEvent[]) {
      expect(eventToast(e, named)).toBeNull();
    }
  });
});

describe('machineChassis', () => {
  it('uses the art-directed body colour from data', () => {
    expect(machineChassis(def({ color: '#4e5a62' }))).toBe('#4e5a62');
  });

  it('falls back to a neutral for a machine DATA has not art-directed', () => {
    expect(machineChassis(def({ color: undefined }))).toMatch(/^#/);
  });
});

describe('createAccentLookup', () => {
  // contracts v3: color is the chassis; the accent derives from what the machine makes.
  const data: GameData = {
    items: [
      { id: 'melt', name: 'MELT', codex: '', tier: 2, color: '#ff7a3f' },
      { id: 'hf-dust', name: 'HF DUST', codex: '', tier: 1, color: '#e8e0ff' },
    ],
    machines: [],
    recipes: [
      { id: 'mosh', machine: 'mosh-reactor', inputs: {}, outputs: { melt: 1 }, ticks: 90, bandwidth: 10 },
    ],
    tech: [],
    commissions: [],
  };
  const accentOf = createAccentLookup(data);

  it('derives the accent from the first thing the machine outputs', () => {
    expect(accentOf(def({ id: 'mosh-reactor', recipes: ['mosh'] }))).toBe('#ff7a3f');
  });

  it('falls back to the codex glow for machines that output nothing', () => {
    // Belts carry; they do not produce. Their accent has to come from the codex table.
    expect(accentOf(def({ id: 'belt', kind: 'belt', recipes: [] }))).toBe('#5a7a5a');
  });

  it('falls back by kind for an unknown machine with no recipes', () => {
    expect(accentOf(def({ id: 'brand-new-thing', kind: 'power', recipes: [] }))).toBe('#3fffe0');
  });

  it('ignores a recipe id that does not resolve rather than throwing', () => {
    expect(accentOf(def({ id: 'x', kind: 'crafter', recipes: ['ghost-recipe'] }))).toBe('#3fffe0');
  });

  it('always returns something for an unheard-of kind', () => {
    expect(accentOf(def({ id: 'x', kind: 'teleporter' as MachineKind, recipes: [] }))).toMatch(/^#/);
  });
});
