import { evaluatePrereq } from './prereq-evaluator';
import type { CharacterContext, EvalResult } from './types';

function isFail(r: EvalResult): r is { ok: false; reason: string } {
  return 'ok' in r && r.ok === false;
}

function makeCtx(overrides: Partial<CharacterContext> = {}): CharacterContext {
  return {
    level: 5,
    className: 'Fighter',
    ancestryName: 'Human',
    heritageName: undefined,
    abilities: { STR: 16, DEX: 14, CON: 14, INT: 10, WIS: 12, CHA: 10 },
    skills: {},
    feats: new Set<string>(),
    ...overrides,
  };
}

describe('evaluatePrereq — empty/null', () => {
  it('returns ok for null prereq', () => {
    expect(evaluatePrereq(null, makeCtx())).toEqual({ ok: true });
  });

  it('returns ok for empty string', () => {
    expect(evaluatePrereq('', makeCtx())).toEqual({ ok: true });
  });
});

describe('evaluatePrereq — skill rank', () => {
  it('returns ok when skill rank meets requirement', () => {
    const ctx = makeCtx({ skills: { Athletics: 'TRAINED' } });
    expect(evaluatePrereq('Trained in Athletics', ctx)).toEqual({ ok: true });
  });

  it('returns ok when skill rank exceeds requirement', () => {
    const ctx = makeCtx({ skills: { Athletics: 'EXPERT' } });
    expect(evaluatePrereq('Trained in Athletics', ctx)).toEqual({ ok: true });
  });

  it('returns ok:false with German reason when skill rank below requirement', () => {
    const ctx = makeCtx({ skills: { Athletics: 'UNTRAINED' } });
    const result = evaluatePrereq('Trained in Athletics', ctx);
    expect(isFail(result)).toBe(true);
    if (isFail(result)) {
      expect(result.reason).toMatch(/Athletics/i);
      // German wording check — must contain a German keyword
      expect(result.reason).toMatch(/(benötig|fehlt|Voraussetzung|mindestens)/);
    }
  });

  it('returns ok:false when skill is missing entirely', () => {
    const ctx = makeCtx({ skills: {} });
    const result = evaluatePrereq('Trained in Athletics', ctx);
    expect(isFail(result)).toBe(true);
  });
});

describe('evaluatePrereq — disjunctive (OR-list)', () => {
  it('returns ok when any of the listed skills matches', () => {
    const ctx = makeCtx({ skills: { Arcana: 'TRAINED' } });
    expect(
      evaluatePrereq('Trained in Arcana, Trained in Nature, or Trained in Religion', ctx),
    ).toEqual({ ok: true });
  });

  it('returns ok:false when no listed skill matches', () => {
    const ctx = makeCtx({ skills: { Athletics: 'TRAINED' } });
    const result = evaluatePrereq(
      'Trained in Arcana, Trained in Nature, or Trained in Religion',
      ctx,
    );
    expect(isFail(result)).toBe(true);
  });
});

describe('evaluatePrereq — conjunctive (semicolon AND)', () => {
  it('returns ok when both clauses match', () => {
    const ctx = makeCtx({ skills: { Deception: 'TRAINED', Stealth: 'TRAINED' } });
    expect(evaluatePrereq('Trained in Deception; Trained in Stealth', ctx)).toEqual({
      ok: true,
    });
  });

  it('returns ok:false when one clause is missing', () => {
    const ctx = makeCtx({ skills: { Deception: 'TRAINED' } });
    const result = evaluatePrereq('Trained in Deception; Trained in Stealth', ctx);
    expect(isFail(result)).toBe(true);
  });
});

describe('evaluatePrereq — bare feat name', () => {
  it('returns ok when the feat is held', () => {
    const ctx = makeCtx({ feats: new Set(['Power Attack']) });
    expect(evaluatePrereq('Power Attack', ctx)).toEqual({ ok: true });
  });

  it('returns ok:false when the feat is missing', () => {
    const ctx = makeCtx({ feats: new Set() });
    const result = evaluatePrereq('Power Attack', ctx);
    expect(isFail(result)).toBe(true);
  });
});

describe('evaluatePrereq — heritage', () => {
  it('returns ok when heritage matches', () => {
    const ctx = makeCtx({ heritageName: 'Unbreakable Goblin' });
    expect(evaluatePrereq('Unbreakable Goblin heritage', ctx)).toEqual({ ok: true });
  });
});

describe('evaluatePrereq — class ref', () => {
  it('returns ok when class matches', () => {
    const ctx = makeCtx({ className: 'Fighter' });
    expect(evaluatePrereq('Fighter', ctx)).toEqual({ ok: true });
  });
});

describe('evaluatePrereq — non-evaluable patterns (D-02 → unknown)', () => {
  it('returns unknown for spellcasting refs', () => {
    const result = evaluatePrereq('spellcasting class feature', makeCtx());
    expect(result).toEqual({ unknown: true, raw: 'spellcasting class feature' });
  });

  it('returns unknown for deity refs', () => {
    const result = evaluatePrereq('worshipper of Droskar', makeCtx());
    expect('unknown' in result && result.unknown).toBe(true);
  });

  it('returns unknown for age refs', () => {
    const result = evaluatePrereq('at least 100 years old', makeCtx());
    expect('unknown' in result && result.unknown).toBe(true);
  });

  it('returns unknown for vision-trait refs', () => {
    const result = evaluatePrereq('low-light vision', makeCtx());
    expect('unknown' in result && result.unknown).toBe(true);
  });

  it('returns unknown for free-text patterns the parser cannot classify', () => {
    const result = evaluatePrereq('You worship a god of fire and destruction', makeCtx());
    expect('unknown' in result && result.unknown).toBe(true);
  });
});
