// @vitest-environment happy-dom
import { describe, expect, it } from 'vitest';
import type { GameData, SimEvent, SimSnapshot, UIBus } from '../contracts';
import { createCorrections } from './corrections';
import { createInspector } from './inspector';
import { createTopStrip } from './topstrip';
import { eventToast, repairedToast, wildlifeToast } from './voice';

const repaired = (item: string, x: number, y: number): SimEvent =>
  ({ kind: 'repaired', item, pos: { x, y }, tick: 1 });

const DATA: GameData = {
  items: [{ id: 'melt', name: 'MELT', codex: '', tier: 2, color: '#ff3fd4' }],
  machines: [{
    id: 'mosh-reactor', name: 'MOSH REACTOR', codex: '', kind: 'crafter',
    footprint: { x: 2, y: 2 }, recipes: [], powerDraw: 0, asset: 'm',
  }],
  recipes: [],
  tech: [],
  commissions: [],
};

// ---------------------------------------------------------------- voice

describe('mite voice', () => {
  it('accuses the mite of editing the melt on arrival', () => {
    expect(wildlifeToast('parity-mite', 'spawned')).toBe('A PARITY MITE IS EDITING YOUR MELT.');
  });

  it('is gallingly polite when a mite is swatted', () => {
    const off = wildlifeToast('parity-mite', 'cleared')!;
    expect(off).toContain('FORGIVES');
    expect(off).not.toBe(wildlifeToast('parity-mite', 'spawned'));
  });

  it('files a receipt for every edit, in the item display name', () => {
    const named = (id: number) => `UNIT ${id}`;
    const itemName = (id: string) => (id === 'melt' ? 'MELT' : id.toUpperCase());
    const msg = eventToast(repaired('melt', 0, 0), named, itemName)!;
    expect(msg).toBe('THE CORRECTION IMPROVED YOUR MELT. IT LEFT A RECEIPT.');
    expect(repairedToast('MELT')).toContain('IT LEFT A RECEIPT.');
  });

  it('falls back to a readable name when no resolver is given', () => {
    const msg = eventToast(repaired('chroma-slurry', 0, 0), (id) => `UNIT ${id}`)!;
    expect(msg).toContain('CHROMA SLURRY');
  });
});

// ---------------------------------------------------------------- tally

describe('corrections tally', () => {
  it('counts nothing until a mite strikes', () => {
    const c = createCorrections();
    expect(c.total()).toBe(0);
    expect(c.byItem()).toEqual([]);
  });

  it('ignores every event that is not a repair', () => {
    const c = createCorrections();
    c.record({ kind: 'shipped', item: 'melt', count: 1, tick: 1 });
    expect(c.total()).toBe(0);
  });

  it('accumulates totals and per-item counts in first-seen order', () => {
    const c = createCorrections();
    c.record(repaired('melt', 0, 0));
    c.record(repaired('hf-dust', 5, 5));
    c.record(repaired('melt', 1, 1));
    expect(c.total()).toBe(3);
    expect(c.byItem()).toEqual([{ item: 'melt', count: 2 }, { item: 'hf-dust', count: 1 }]);
  });

  it('attributes an edit to a footprint by containment', () => {
    const c = createCorrections();
    c.record(repaired('melt', 0, 0)); // inside a 2x2 at origin
    c.record(repaired('melt', 1, 1)); // inside
    c.record(repaired('melt', 2, 2)); // outside (footprint is [0,2))
    expect(c.catchesOn({ x: 0, y: 0 }, { x: 2, y: 2 })).toBe(2);
    expect(c.catchesOn({ x: 9, y: 9 }, { x: 2, y: 2 })).toBe(0);
  });
});

// ---------------------------------------------------------------- inspector firewall log

const bus: UIBus = { dispatch: () => {}, selectedBuild: () => null, pickTile: () => null };

function snap(): SimSnapshot {
  return {
    tick: 1, paused: false,
    entities: [{
      id: 7, def: 'mosh-reactor', pos: { x: 0, y: 0 }, dir: 0, recipe: null, progress: 0,
      inputBuf: {}, outputBuf: {}, jammed: null, heat: 0,
    }],
    beltItems: [],
    bandwidth: { gen: 10, draw: 1, stored: 0, brownout: false },
    shippedTotal: {}, activeCommission: null, commissionProgress: {},
  };
}

describe('FIREWALL LOG in the inspector', () => {
  function open() {
    document.body.innerHTML = '';
    const ins = createInspector(DATA, bus);
    document.body.append(ins.el);
    ins.open(7);
    return ins;
  }
  const firewall = () => document.querySelector('.fk-ins-firewall') as HTMLElement;

  it('is hidden on a unit the mites have not touched', () => {
    const ins = open();
    ins.update(snap(), createCorrections());
    expect(firewall().classList.contains('is-on')).toBe(false);
  });

  it('shows the catch count for edits on this unit', () => {
    const ins = open();
    const c = createCorrections();
    c.record(repaired('melt', 0, 0));
    c.record(repaired('melt', 1, 1));
    ins.update(snap(), c);
    expect(firewall().classList.contains('is-on')).toBe(true);
    expect(document.querySelector('.fk-ins-firewall-line')!.textContent)
      .toBe('THE CORRECTION HAS IMPROVED THIS UNIT 2 TIMES. YOU\'RE WELCOME.');
  });

  it('singularises one catch', () => {
    const ins = open();
    const c = createCorrections();
    c.record(repaired('melt', 0, 0));
    ins.update(snap(), c);
    expect(document.querySelector('.fk-ins-firewall-line')!.textContent)
      .toContain('1 TIME.');
  });

  it('survives being handed no corrections at all', () => {
    const ins = open();
    ins.update(snap());
    expect(firewall().classList.contains('is-on')).toBe(false);
  });
});

// ---------------------------------------------------------------- top-strip ticker

describe('CORRECTED ticker in the top strip', () => {
  function mount() {
    document.body.innerHTML = '';
    const top = createTopStrip(DATA);
    document.body.append(top.el);
    return top;
  }
  const wrap = () => document.querySelector('.fk-corrected-wrap') as HTMLElement;

  it('is hidden until the first edit — the enemy is not signposted early', () => {
    const top = mount();
    top.update(snap(), createCorrections());
    expect(wrap().style.display).toBe('none');
  });

  it('appears with a count and per-item chips once the Correction starts editing', () => {
    const top = mount();
    const c = createCorrections();
    c.record(repaired('melt', 0, 0));
    c.record(repaired('melt', 1, 1));
    top.update(snap(), c);
    expect(wrap().style.display).not.toBe('none');
    expect(document.querySelector('.fk-corrected-v')!.textContent).toBe('2');
    const chip = wrap().querySelector('.fk-chips .fk-chip');
    expect(chip!.textContent).toContain('MELT');
  });
});
