import { describe, it, expect } from 'vitest';
import {
  getClassDef, getClassNames, classSkillCount, classSkillChoices,
  buildCharacter, planLevelUp, applyIncreases, bumpRank,
} from './progression';
import type { AbilityScores } from './types';

const abilities: AbilityScores = { str: 16, dex: 12, con: 14, int: 10, wis: 13, cha: 8 };

describe('class tables', () => {
  it('exposes core classes for both systems', () => {
    expect(getClassNames('5e')).toContain('Wizard');
    expect(getClassNames('pf2e')).toContain('Champion');
    expect(getClassDef('5e', 'fighter')?.hitDie).toBe(10);
  });
  it('pf2e skill count adds INT modifier; 5e does not', () => {
    expect(classSkillCount('5e', 'Rogue', 3)).toBe(4); // fixed
    expect(classSkillCount('pf2e', 'Rogue', 3)).toBe(10); // 7 + INT 3
  });
  it('restricts skill choices for a class with a list, full list otherwise', () => {
    expect(classSkillChoices('5e', ['a', 'b'], 'Cleric')).toContain('religion');
    expect(classSkillChoices('5e', ['acrobatics', 'arcana'], 'Bard')).toEqual(['acrobatics', 'arcana']);
  });
});

describe('buildCharacter — 5e', () => {
  it('computes HP from hit die + CON over levels and sets save proficiencies', () => {
    const built = buildCharacter('5e', { className: 'Fighter', level: 1, abilities, skillChoices: ['athletics'] });
    // d10 + CON 2 at level 1
    expect(built.hp.max).toBe(12);
    expect(built.saveRanks).toEqual({ str: 'trained', con: 'trained' });
    expect(built.skillRanks).toEqual({ athletics: 'trained' });
    expect(built.spellcasting.slots).toEqual([]);
  });
  it('merges background/racial granted skills as trained, alongside class picks', () => {
    const built = buildCharacter('5e', {
      className: 'Fighter', level: 1, abilities,
      skillChoices: ['athletics'], grantedSkills: ['insight', 'religion', 'athletics'],
    });
    // class pick + background/race grants; the duplicate (athletics) is not doubled
    expect(built.skillRanks).toEqual({ athletics: 'trained', insight: 'trained', religion: 'trained' });
  });
  it('gives a full caster spell slots and a spell ability', () => {
    const built = buildCharacter('5e', { className: 'Wizard', level: 3, abilities, skillChoices: [] });
    expect(built.spellcastingAbility).toBe('int');
    expect(built.spellcasting.slots).toEqual([
      { level: 1, max: 4, current: 4 }, { level: 2, max: 2, current: 2 },
    ]);
  });
  it('gives a warlock pact magic', () => {
    const built = buildCharacter('5e', { className: 'Warlock', level: 3, abilities, skillChoices: [] });
    expect(built.spellcasting.pact).toEqual({ level: 2, max: 2, current: 2 });
  });
});

describe('buildCharacter — pf2e', () => {
  it('computes HP = ancestry + (class + CON) × level and sets save ranks/perception', () => {
    const built = buildCharacter('pf2e', { className: 'Fighter', level: 1, abilities, skillChoices: ['athletics'], ancestryHp: 8 });
    expect(built.hp.max).toBe(8 + (10 + 2) * 1); // 20
    expect(built.saveRanks).toMatchObject({ con: 'expert', dex: 'expert', wis: 'trained' });
    expect(built.perceptionRank).toBe('expert');
  });
});

describe('planLevelUp', () => {
  it('flags a 5e ASI at level 4 and updates caster slots', () => {
    const plan = planLevelUp('5e', 'Wizard', 3, 2);
    expect(plan.nextLevel).toBe(4);
    expect(plan.choices.some((c) => c.kind === 'asi')).toBe(true);
    expect(plan.slots?.length).toBeGreaterThan(0);
  });
  it('flags pf2e ability boosts at level 5 and a skill increase', () => {
    const plan = planLevelUp('pf2e', 'Rogue', 4, 1);
    expect(plan.nextLevel).toBe(5);
    expect(plan.choices.some((c) => c.kind === 'boosts')).toBe(true);
    expect(plan.choices.some((c) => c.kind === 'skill-increase')).toBe(true);
  });
});

describe('apply helpers', () => {
  it('5e increases add +1 per pick (same twice = +2)', () => {
    expect(applyIncreases(abilities, ['str', 'str'], '5e').str).toBe(18);
    expect(applyIncreases(abilities, ['dex', 'con'], '5e')).toMatchObject({ dex: 13, con: 15 });
  });
  it('pf2e boosts add +2 under 18, +1 at/over 18', () => {
    expect(applyIncreases({ ...abilities, str: 16 }, ['str'], 'pf2e').str).toBe(18);
    expect(applyIncreases({ ...abilities, str: 18 }, ['str'], 'pf2e').str).toBe(19);
  });
  it('bumpRank walks the ladder and caps at legendary', () => {
    expect(bumpRank('untrained')).toBe('trained');
    expect(bumpRank('expert')).toBe('master');
    expect(bumpRank('legendary')).toBe('legendary');
  });
});
