import { describe, it, expect } from 'bun:test';
import { World } from '@common/rpg/core/world';
import { Stat, Health } from '@common/rpg/components/stat';

function world() { return new World(); }

describe('Stat — value / base / modifiers', () => {
    it('value equals base when no modifiers', () => {
        const w = world();
        const e = w.createEntity();
        e.add(new Stat({ value: 10 }), 's');
        const s = e.get(Stat, 's')!;
        expect(s.value).toBe(10);
        expect(s.base).toBe(10);
    });

    it('set() changes base and value', () => {
        const w = world();
        const e = w.createEntity();
        e.add(new Stat({ value: 5 }), 's');
        const s = e.get(Stat, 's')!;
        s.set(20);
        expect(s.base).toBe(20);
        expect(s.value).toBe(20);
    });

    it('update() adds to base', () => {
        const w = world();
        const e = w.createEntity();
        e.add(new Stat({ value: 10 }), 's');
        const s = e.get(Stat, 's')!;
        s.update(5);
        expect(s.value).toBe(15);
    });

    it('applyModifier shifts value without changing base', () => {
        const w = world();
        const e = w.createEntity();
        e.add(new Stat({ value: 10 }), 's');
        const s = e.get(Stat, 's')!;
        s.applyModifier(5);
        expect(s.base).toBe(10);
        expect(s.value).toBe(15);
    });

    it('removeModifier reverts applyModifier', () => {
        const w = world();
        const e = w.createEntity();
        e.add(new Stat({ value: 10 }), 's');
        const s = e.get(Stat, 's')!;
        s.applyModifier(5);
        s.removeModifier(5);
        expect(s.value).toBe(10);
    });

    it('multiple modifiers stack', () => {
        const w = world();
        const e = w.createEntity();
        e.add(new Stat({ value: 10 }), 's');
        const s = e.get(Stat, 's')!;
        s.applyModifier(3);
        s.applyModifier(7);
        expect(s.value).toBe(20);
    });

    it('modifier on max field shifts effective max', () => {
        const w = world();
        const e = w.createEntity();
        e.add(new Stat({ value: 10, max: 20 }), 's');
        const s = e.get(Stat, 's')!;
        s.applyModifier(10, 'max');
        expect(s.max).toBe(30);
    });

    it('modifier on min field shifts effective min', () => {
        const w = world();
        const e = w.createEntity();
        e.add(new Stat({ value: 10, min: 0 }), 's');
        const s = e.get(Stat, 's')!;
        s.applyModifier(5, 'min');
        expect(s.min).toBe(5);
    });

    it('value is clamped to [min, max]', () => {
        const w = world();
        const e = w.createEntity();
        e.add(new Stat({ value: 10, min: 0, max: 20 }), 's');
        const s = e.get(Stat, 's')!;
        s.set(100);
        expect(s.value).toBe(20);
        s.set(-5);
        expect(s.value).toBe(0);
    });

    it('large positibe modifier clamps to max', () => {
        const w = world();
        const e = w.createEntity();
        e.add(new Stat({ value: 10, max: 20 }), 's');
        const s = e.get(Stat, 's')!;
        s.applyModifier(100);
        expect(s.value).toBe(20);
    });

    it('large negative modifier clamps to min', () => {
        const w = world();
        const e = w.createEntity();
        e.add(new Stat({ value: 10, min: 0 }), 's');
        const s = e.get(Stat, 's')!;
        s.applyModifier(-100);
        expect(s.value).toBe(0);
    });

    it("set() emits 'set' event with prev and value", () => {
        const w = world();
        const e = w.createEntity();
        e.add(new Stat({ value: 10 }), 's');
        const s = e.get(Stat, 's')!;
        const events: unknown[] = [];
        e.on('Stat(s).set', ({ data }) => events.push(data));
        s.set(20);
        expect(events).toEqual([{ prev: 10, value: 20 }]);
    });

    it("set() does not emit 'set' when value unchanged", () => {
        const w = world();
        const e = w.createEntity();
        e.add(new Stat({ value: 10, min: 0 }), 's');
        const s = e.get(Stat, 's')!;
        const events: unknown[] = [];
        e.on('Stat(s).set', ({ data }) => events.push(data));
        s.set(-100); // clamped to 0, still changes
        s.set(-200); // still 0, no change
        expect(events.length).toBe(1);
    });
});

describe('Health', () => {
    it('update reduces health', () => {
        const w = world();
        const e = w.createEntity();
        e.add(new Health({ value: 100, min: 0 }));
        const h = e.get(Health)!;
        h.update(-30);
        expect(h.value).toBe(70);
    });

    it('update to zero triggers kill()', () => {
        const w = world();
        const e = w.createEntity();
        e.add(new Health({ value: 10, min: 0 }));
        const h = e.get(Health)!;
        const killed: unknown[] = [];
        e.on('Health.killed', () => killed.push(true));
        h.update(-10);
        expect(killed.length).toBe(1);
        expect(h.value).toBe(0);
    });

    it('kill() emits killed and sets value to 0', () => {
        const w = world();
        const e = w.createEntity();
        e.add(new Health({ value: 50, min: 0 }));
        const h = e.get(Health)!;
        const killed: unknown[] = [];
        e.on('Health.killed', () => killed.push(true));
        h.kill();
        expect(killed.length).toBe(1);
        expect(h.value).toBe(0);
    });

    it('kill() does not emit killed twice for overkill', () => {
        const w = world();
        const e = w.createEntity();
        e.add(new Health({ value: 10, min: 0 }));
        const h = e.get(Health)!;
        const killed: unknown[] = [];
        e.on('Health.killed', () => killed.push(true));
        h.update(-999);
        expect(killed.length).toBe(1);
    });

    it('kill() does not emit killed twice for already killed entity', () => {
        const w = world();
        const e = w.createEntity();
        e.add(new Health({ value: 10, min: 0 }));
        const h = e.get(Health)!;
        const killed: unknown[] = [];
        e.on('Health.killed', () => killed.push(true));
        h.update(-999);
        h.update(-999);
        expect(killed.length).toBe(1);
    });
});
