import { describe, it, expect } from 'bun:test';
import { World } from '@common/rpg/core/world';
import { Stat } from '@common/rpg/components/stat';
import { Effect, EffectTemplate } from '@common/rpg/components/effect';
import { Equipment, Equippable } from '@common/rpg/components/equipment';

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

function makeSword(w: World, id = 'sword') {
    const sword = w.createEntity(id);
    sword.add(new Equippable('weapon'));
    return sword;
}

function makePlayer(w: World, slots: ConstructorParameters<typeof Equipment>[0] = { slotName: 'weapon', type: 'weapon' }) {
    const player = w.createEntity('player');
    player.add(new Stat({ value: 10 }), 'str');
    player.add(new Equipment(slots));
    return player;
}

describe('Equipment — equip', () => {
    it('equips item into a typed slot', () => {
        const w = world();
        makeSword(w);
        const player = makePlayer(w);
        const result = player.get(Equipment)!.equip({ slotName: 'weapon', itemId: 'sword' });
        expect(result).toBeTrue();
        expect(player.get(Equipment)!.getItem('weapon')).toBe('sword');
    });

    it('returns false for unknown slot', () => {
        const w = world();
        makeSword(w);
        const player = makePlayer(w);
        expect(player.get(Equipment)!.equip({ slotName: 'head', itemId: 'sword' })).toBeFalse();
    });

    it('returns false for missing item entity', () => {
        const w = world();
        const player = makePlayer(w);
        expect(player.get(Equipment)!.equip({ slotName: 'weapon', itemId: 'ghost' })).toBeFalse();
    });

    it('returns false when item has no Equippable component', () => {
        const w = world();
        w.createEntity('rock'); // no Equippable
        const player = makePlayer(w);
        expect(player.get(Equipment)!.equip({ slotName: 'weapon', itemId: 'rock' })).toBeFalse();
    });

    it('returns false when slot type does not match Equippable.slotType', () => {
        const w = world();
        const helmet = w.createEntity('helmet');
        helmet.add(new Equippable('armor'));
        const player = makePlayer(w); // slot type = 'weapon'
        expect(player.get(Equipment)!.equip({ slotName: 'weapon', itemId: 'helmet' })).toBeFalse();
    });

    it('generic slot (no type) accepts any Equippable', () => {
        const w = world();
        makeSword(w);
        const player = w.createEntity('player');
        player.add(new Equipment('slot1')); // generic slot
        expect(player.get(Equipment)!.equip({ slotName: 'slot1', itemId: 'sword' })).toBeTrue();
    });

    it('equipping occupied slot auto-unequips first', () => {
        const w = world();
        makeSword(w, 'sword1');
        makeSword(w, 'sword2');
        const player = makePlayer(w);
        const eq = player.get(Equipment)!;
        eq.equip({ slotName: 'weapon', itemId: 'sword1' });
        eq.equip({ slotName: 'weapon', itemId: 'sword2' });
        expect(eq.getItem('weapon')).toBe('sword2');
    });

    it("emits 'equip' event", () => {
        const w = world();
        makeSword(w);
        const player = makePlayer(w);
        const events: unknown[] = [];
        player.on('Equipment.equip', ({ data }) => events.push(data));
        player.get(Equipment)!.equip({ slotName: 'weapon', itemId: 'sword' });
        expect(events).toEqual([{ slotName: 'weapon', itemId: 'sword' }]);
    });
});

describe('Equipment — equip-scope effects', () => {
    it('clones equip-scope Effect onto owner on equip', () => {
        const w = world();
        const sword = makeSword(w);
        sword.add(new Effect({ targetKey: 'str', delta: 5 })); // scope: equip (default)
        const player = makePlayer(w);
        player.get(Equipment)!.equip({ slotName: 'weapon', itemId: 'sword' });
        expect(player.get(Stat, 'str')!.value).toBe(15);
    });

    it('does NOT clone onHit-scope Effect onto owner on equip', () => {
        const w = world();
        const sword = makeSword(w);
        sword.add(new EffectTemplate({ targetKey: 'str', delta: 99 }));
        const player = makePlayer(w);
        player.get(Equipment)!.equip({ slotName: 'weapon', itemId: 'sword' });
        expect(player.get(Stat, 'str')!.value).toBe(10); // unaffected
        expect(player.getAll(Effect).length).toBe(0);
    });

    it('clones multiple equip effects', () => {
        const w = world();
        const sword = makeSword(w);
        sword.add(new Effect({ targetKey: 'str', delta: 3 }));
        sword.add(new Effect({ targetKey: 'str', delta: 7 }));
        const player = makePlayer(w);
        player.get(Equipment)!.equip({ slotName: 'weapon', itemId: 'sword' });
        expect(player.get(Stat, 'str')!.value).toBe(20);
    });

    it('equip + onHit: only equip effects reach owner', () => {
        const w = world();
        const sword = makeSword(w);
        sword.add(new Effect({ targetKey: 'str', delta: 5 }));
        sword.add(new EffectTemplate({ targetKey: 'str', delta: 99 }));
        const player = makePlayer(w);
        player.get(Equipment)!.equip({ slotName: 'weapon', itemId: 'sword' });
        expect(player.get(Stat, 'str')!.value).toBe(15);
        expect(player.getAll(Effect).length).toBe(1);
    });
});

describe('Equipment — unequip', () => {
    it('unequip reverts cloned effects', () => {
        const w = world();
        const sword = makeSword(w);
        sword.add(new Effect({ targetKey: 'str', delta: 5 }));
        const player = makePlayer(w);
        const eq = player.get(Equipment)!;
        eq.equip({ slotName: 'weapon', itemId: 'sword' });
        expect(player.get(Stat, 'str')!.value).toBe(15);
        eq.unequip('weapon');
        expect(player.get(Stat, 'str')!.value).toBe(10);
    });

    it('unequip clears slot itemId', () => {
        const w = world();
        makeSword(w);
        const player = makePlayer(w);
        const eq = player.get(Equipment)!;
        eq.equip({ slotName: 'weapon', itemId: 'sword' });
        eq.unequip('weapon');
        expect(eq.getItem('weapon')).toBeNull();
    });

    it('unequip returns false on empty slot', () => {
        const w = world();
        const player = makePlayer(w);
        expect(player.get(Equipment)!.unequip('weapon')).toBeFalse();
    });

    it("unequip emits 'unequip' event", () => {
        const w = world();
        makeSword(w);
        const player = makePlayer(w);
        const eq = player.get(Equipment)!;
        eq.equip({ slotName: 'weapon', itemId: 'sword' });
        const events: unknown[] = [];
        player.on('Equipment.unequip', ({ data }) => events.push(data));
        eq.unequip('weapon');
        expect(events).toEqual([{ slotName: 'weapon', itemId: 'sword' }]);
    });
});

describe('Equipment — queries', () => {
    it('getEquipped returns all filled slots', () => {
        const w = world();
        makeSword(w, 'sword1');
        makeSword(w, 'sword2');
        const player = w.createEntity('player');
        player.add(new Equipment(
            { slotName: 'main', type: 'weapon' },
            { slotName: 'off', type: 'weapon' },
        ));
        const eq = player.get(Equipment)!;
        eq.equip({ slotName: 'main', itemId: 'sword1' });
        eq.equip({ slotName: 'off', itemId: 'sword2' });
        const equipped = eq.getEquipped();
        expect(equipped.length).toBe(2);
        expect(equipped.map(e => e.slotName).sort()).toEqual(['main', 'off']);
    });

    it('findCompatibleSlot prefers typed slot over generic', () => {
        const w = world();
        const player = w.createEntity('player');
        player.add(new Equipment(
            'generic',
            { slotName: 'weapon', type: 'weapon' },
        ));
        const eq = player.get(Equipment)!;
        expect(eq.findCompatibleSlot('weapon')).toBe('weapon');
    });

    it('findCompatibleSlot falls back to generic slot', () => {
        const w = world();
        const player = w.createEntity('player');
        player.add(new Equipment('generic'));
        const eq = player.get(Equipment)!;
        expect(eq.findCompatibleSlot('weapon')).toBe('generic');
    });

    it('findCompatibleSlot returns null when no compatible slot', () => {
        const w = world();
        const player = w.createEntity('player');
        player.add(new Equipment({ slotName: 'armor', type: 'armor' }));
        const eq = player.get(Equipment)!;
        expect(eq.findCompatibleSlot('weapon')).toBeNull();
    });
});
