import { TutorialMgr } from '@logic/TutorialMgr';
import { ScoreSystem, BASE_ENEMY_SCORE, COMBO_BONUS } from '@logic/ScoreSystem';
import { WeaponType } from '@data/Interfaces';
import { StorageMgr } from '@common/StorageMgr';

function mem() {
    const m = new Map<string, string>();
    return {
        getItem: (k: string) => (m.has(k) ? (m.get(k) as string) : null),
        setItem: (k: string, v: string) => {
            m.set(k, v);
        },
        removeItem: (k: string) => {
            m.delete(k);
        },
    };
}

describe('TutorialMgr — built-in sequences for 1-1..1-3 (req 11.1-11.3)', () => {
    it('maybeStart returns the first step of 1-1', () => {
        const t = new TutorialMgr(new StorageMgr(mem()));
        const step = t.maybeStart('1-1');
        expect(step).toBeDefined();
        expect(step!.id).toBe('attack');
    });

    it('reportAction advances through the sequence', () => {
        const t = new TutorialMgr(new StorageMgr(mem()));
        t.maybeStart('1-1');
        expect(t.reportAction('fire_shuriken')).toMatchObject({ id: 'joystick' });
        expect(t.reportAction('move')).toMatchObject({ id: 'jump' });
        expect(t.reportAction('jump')).toBe('finished');
    });

    it('completed tutorials are persisted and skipped on replay (req 11.4)', () => {
        const storage = new StorageMgr(mem());
        const t = new TutorialMgr(storage);
        t.maybeStart('1-1');
        t.reportAction('fire_shuriken');
        t.reportAction('move');
        t.reportAction('jump');
        expect(t.isCompleted('1-1')).toBe(true);
        expect(t.maybeStart('1-1')).toBeNull();
    });

    it('resetAll clears the completion set (req 11.5)', () => {
        const t = new TutorialMgr(new StorageMgr(mem()));
        t.maybeStart('1-2');
        t.reportAction('parabolic_jump');
        t.reportAction('attack_switch');
        t.reportAction('parry');
        t.reportAction('jump_attack');
        t.reportAction('pickup_crystal');
        t.resetAll();
        expect(t.maybeStart('1-2')).toBeDefined();
    });

    it('reportAction with wrong action is a no-op', () => {
        const t = new TutorialMgr(new StorageMgr(mem()));
        t.maybeStart('1-1');
        expect(t.reportAction('wrong')).toBe('no_op');
        expect(t.currentStep()!.id).toBe('attack');
    });
});

describe('ScoreSystem — scoring table (req 12.1-12.6)', () => {
    it('sword kill is ×2, shuriken kill is ×1', () => {
        const s = new ScoreSystem();
        s.recordEnemyKill(WeaponType.NinjaSword);
        s.recordEnemyKill(WeaponType.Shuriken);
        expect(s.snapshot().baseScore).toBe(BASE_ENEMY_SCORE * 3);
    });

    it('parry kill is ×3 base', () => {
        const s = new ScoreSystem();
        s.recordParryKill();
        expect(s.snapshot().baseScore).toBe(BASE_ENEMY_SCORE * 3);
    });

    it('5 blade contacts award a +1500 combo bonus (req 12.4)', () => {
        const s = new ScoreSystem();
        for (let i = 0; i < 5; i++) s.recordBladeContact();
        const snap = s.snapshot();
        expect(snap.comboBonus).toBe(COMBO_BONUS);
        expect(snap.comboCount).toBe(1);
        expect(snap.consecutiveBladeHits).toBe(0);
    });

    it('breakBladeChain resets the streak', () => {
        const s = new ScoreSystem();
        s.recordBladeContact();
        s.recordBladeContact();
        s.breakBladeChain();
        s.recordBladeContact();
        expect(s.snapshot().consecutiveBladeHits).toBe(1);
    });

    it('flawless run triples total score; taking damage removes the bonus (req 12.5)', () => {
        const s = new ScoreSystem();
        s.recordEnemyKill(WeaponType.Shuriken);
        const flawlessSnap = s.snapshot();
        expect(flawlessSnap.flawlessMultiplier).toBe(3);
        s.markTaken();
        const damagedSnap = s.snapshot();
        expect(damagedSnap.flawlessMultiplier).toBe(1);
    });

    it('remaining time bonus adds 10 pts / sec (req 12.6)', () => {
        const s = new ScoreSystem();
        s.recordEnemyKill(WeaponType.Shuriken);
        s.setRemainingTimeBonus(30);
        expect(s.snapshot().timeBonus).toBe(300);
    });
});
