import { AlignHorizontal, TextDisplay, TextRegion, type ColorLike } from "@common/display/text";
import { gameLoop } from "@common/game";
import Input from "@common/input";
import { Attacked, Damage } from "@common/rpg/components/combat";
import { Equipment } from "@common/rpg/components/equipment";
import { Experience, type LevelUpEvent } from "@common/rpg/components/experience";
import { Inventory } from "@common/rpg/components/inventory";
import { Item, Items } from "@common/rpg/components/item";
import { Name } from "@common/rpg/components/name";
import { getWorldRandom } from "@common/rpg/components/random";
import { Health } from "@common/rpg/components/stat";
import { Entity, World } from "@common/rpg/core/world";
import { CombatSystem, type HitEvent } from "@common/rpg/systems/combat";
import { capitalize } from "@common/utils";

const createPlayer = (world: World, weapon: Entity): Entity => {
    const player = world.createEntity();
    player.add(new Health({ value: 100, max: 100 }));
    const inv = player.add(new Inventory());
    player.add(new Equipment('weapon'));
    player.add(new Experience({ base: 50, factor: 1.5 }));
    player.add(new Name("Player"));

    inv.add(weapon);
    inv.equip(weapon);

    return player;
}

const createEnemy = (world: World, level: number): Entity => {
    const enemy = world.createEntity();
    const inv = enemy.add(new Inventory());
    enemy.add(new Equipment('weapon'));

    const random = getWorldRandom(world);
    enemy.add(new Name(random.nextName({ maxLength: 7 }) + ' The Goblin'));

    const hp = random.nextInt(level * 10, level * 20);
    enemy.add(new Health({ value: hp, max: hp }));

    const attack = random.nextInt(level * 3, level * 6);

    const weaponName = random.choice(['sword', 'axe', 'dagger', 'mace']);
    const weapon = createWeapon(world, weaponName, attack, level);

    inv.add(weapon);
    inv.equip(weapon);

    return enemy;
}

const createWeapon = (world: World, id: string, damage: number, variance: number): Entity => {
    const name = id.split(/[-_ ]+/).map(capitalize).join(' ');
    const weapon = Items.register(world, `${id}_*`, name, { equippable: { slotType: 'weapon' } });

    weapon.add(new Damage({ value: damage, variance }));

    return weapon;
}

const drawEntity = (display: TextDisplay, entity: Entity, x: number, y: number, anchorH: AlignHorizontal) => {
    const { value: hp, max: maxHp } = entity.get(Health)!;
    let text = `HP: ${hp}/${maxHp}`;

    const xp = entity.get(Experience);
    if (xp) {
        text += `\nLevel: ${xp.level}`;
        text += `\nXP: ${xp.xp}/${xp.xpForNext}`;
    }

    const title = entity.get(Name)?.state ?? entity.id;

    display.drawStringInBox(text, x, y, { title, anchorH });

    const weapon = entity.get(Equipment)!.getItem('weapon');

    if (weapon) {
        const { value: damage, variance } = weapon.get(Damage)!;
        const minDamage = damage - variance;
        const maxDamage = damage + variance;
        display.drawStringInBox(`Damage: ${minDamage}-${maxDamage}`, x, y + (xp ? 5 : 3), { title: weapon.get(Item)!.name, anchorH });
    }
}

export default gameLoop(() => {
    const world = new World();

    world.addSystem(new CombatSystem());
    const display = new TextDisplay();

    const fists = createWeapon(world, 'fists', 7, 2);
    const player = createPlayer(world, fists);
    const enemy = createEnemy(world, 1);

    const log: TextRegion[] = [];
    const state = {
        world,
        display,
        player,
        enemy,
        needUpdate: true,
        blocked: false,
        log,
        addLog(text: string, color?: ColorLike) {
            log.push(new TextRegion(text, color));
            if (log.length > 10) {
                log.shift();
            }
            state.needUpdate = true;
        }
    };

    world.on<HitEvent>('Combat.hit', ({ target, data }) => {
        if (target instanceof Entity) {
            const targetName = target === player ? "Player" : target.get(Name)?.state ?? target.id;
            state.addLog(`${targetName} hit by ${data.amount}`);
        }
    });

    world.on('Combat.killed', ({ target }) => {
        if (target instanceof Entity) {
            target.destroy();
            player.get(Experience)!.award(10);
            state.addLog("Enemy killed!");
        }
    });

    player.on<LevelUpEvent>('Experience.levelup', ({ data }) => {
        const health = player.get(Health)!;

        health.applyModifier(Math.round(health.max! * 0.1), 'max');
        health.set(health.max!);

        state.addLog(`Player leveled up to level ${data.level}`);
    })

    return state;
}, (_dt, state) => {
    if (Input.isReleased(Input.KeyCode.SPACE, Input.KeyCode.MOUSE_LEFT) && !state.blocked) {
        const weapon = state.player.get(Equipment)!.getItem('weapon')!;
        state.enemy.add(new Attacked({ attackerId: state.player.id, sourceId: weapon.id }));
        state.blocked = true;

        setTimeout(() => {
            if (state.enemy.destroyed) {
                state.enemy = createEnemy(state.world, state.player.get(Experience)!.level);
            } else {
                const enemyWeapon = state.enemy.get(Equipment)!.getItem('weapon')!;
                state.player.add(new Attacked({ attackerId: state.enemy.id, sourceId: enemyWeapon.id }));
            }
            state.blocked = false;
            state.needUpdate = true;
        }, 500);

        state.needUpdate = true;
    }

    if (state.needUpdate) {
        state.world.update(1);

        state.display.fillBox(
            0,
            0,
            state.display.width,
            state.display.height,
            ' ',
        );

        drawEntity(state.display, state.player, 0, 0, AlignHorizontal.LEFT);
        if (!state.enemy.destroyed) {
            drawEntity(state.display, state.enemy, state.display.width - 1, 0, AlignHorizontal.RIGHT);
        }

        for (let i = 0; i < state.log.length; i++) {
            const entry = state.log[i];
            state.display.setRegion(entry, 0, state.display.height - state.log.length + i);
        }

        state.needUpdate = false;
    }

    state.display.update();
});