import { describe, it, expect } from 'vitest';
import { characterDefaults, type Character, type Quest } from '@/lib/schemas';
import { questSignals, casterSignals, buildSignals } from './signals';

function pc(over: Partial<Character> = {}): Character {
  return {
    id: 'pc', campaignId: 'c', system: '5e', kind: 'pc', name: 'Mage', ancestry: '', className: 'Wizard', level: 5,
    abilities: { str: 8, dex: 14, con: 12, int: 16, wis: 10, cha: 10 },
    hp: { current: 30, max: 30, temp: 0 }, speed: 30, armorBonus: 0,
    skillRanks: {}, saveRanks: {}, perceptionRank: 'trained',
    ...characterDefaults(), notes: '', createdAt: '', updatedAt: '',
    ...over,
  };
}
function quest(over: Partial<Quest> = {}): Quest {
  return { id: 'q', campaignId: 'c', title: 'Find the relic', status: 'active', description: '', reward: '', objectives: [], createdAt: '', updatedAt: '', ...over };
}

describe('questSignals', () => {
  it('offers to complete a quest whose objectives are all done', () => {
    const q = quest({ objectives: [{ id: 'o1', text: 'a', done: true }, { id: 'o2', text: 'b', done: true }] });
    const [s] = questSignals([q]);
    expect(s?.action).toEqual({ type: 'completeQuest', questId: 'q', label: 'Mark complete' });
  });

  it('stays quiet when an objective is unfinished or there are none', () => {
    expect(questSignals([quest({ objectives: [{ id: 'o1', text: 'a', done: false }] })])).toEqual([]);
    expect(questSignals([quest({ objectives: [] })])).toEqual([]);
    expect(questSignals([quest({ status: 'completed', objectives: [{ id: 'o1', text: 'a', done: true }] })])).toEqual([]);
  });
});

describe('casterSignals', () => {
  it('flags a caster with all slots expended and offers a short rest', () => {
    const c = pc({ spellcasting: { slots: [{ level: 1, max: 3, current: 0 }, { level: 2, max: 2, current: 0 }], spells: [] } });
    const sig = casterSignals([c]).find((s) => s.id === 'no-slots-pc');
    expect(sig?.action).toEqual({ type: 'shortRest', characterId: 'pc', label: 'Short rest' });
  });

  it('does not flag when some slots remain', () => {
    const c = pc({ spellcasting: { slots: [{ level: 1, max: 3, current: 1 }], spells: [] } });
    expect(casterSignals([c]).some((s) => s.id === 'no-slots-pc')).toBe(false);
  });

  it('warns when concentrating while bloodied', () => {
    const c = pc({ hp: { current: 10, max: 30, temp: 0 }, concentration: { spellId: 's', spellName: 'Bless' } });
    expect(casterSignals([c]).some((s) => s.id === 'conc-risk-pc')).toBe(true);
  });
});

describe('buildSignals', () => {
  it('merges detectors, de-dupes, and sorts danger → warn → info', () => {
    const downed = pc({ id: 'a', name: 'A', hp: { current: 0, max: 30, temp: 0 } });
    const noSlots = pc({ id: 'b', name: 'B', spellcasting: { slots: [{ level: 1, max: 2, current: 0 }], spells: [] } });
    const out = buildSignals({ characters: [downed, noSlots], notes: [], quests: [], activeEncounter: undefined });
    expect(out.length).toBeGreaterThan(0);
    // severity is non-decreasing (danger=0 first)
    const ranks = out.map((s) => ({ danger: 0, warn: 1, info: 2 })[s.severity]);
    expect(ranks).toEqual([...ranks].sort((x, y) => x - y));
    // unique ids
    expect(new Set(out.map((s) => s.id)).size).toBe(out.length);
  });
});
