import { Attacked, Crit, Damage, Defense } from "../components/combat";
import { Effect, EffectTemplate } from "../components/effect";
import { getWorldRandom, Random } from "../components/random";
import { Health } from "../components/stat";
import { System, World } from "../core/world";

let hitEffectCounter = 0;

export class CombatSystem extends System {
    override update(world: World) {
        let random: Random | undefined;

        for (const [target] of world.query(Attacked)) {
            const health = target.get(Health);
            if (!health) {
                console.warn(`[CombatSystem] Target ${target.id} has no Health component`);
                for (const attack of target.getAll(Attacked)) target.remove(attack);
                continue;
            }

            let damageSum = 0;
            const hitEvents: { attackerId: string; sourceId: string | null; amount: number; damageType: string }[] = [];
            let lastHit: { attackerId: string; sourceId: string | null } | null = null;

            for (const attack of target.getAll(Attacked)) {
                const { attackerId, sourceId } = attack.state;
                target.remove(attack);

                const attacker = world.getEntity(attackerId);
                if (!attacker) {
                    console.warn(`[CombatSystem] Attacker ${attackerId} not found`);
                    continue;
                }

                const source = sourceId ? world.getEntity(sourceId) : attacker;
                if (!source) {
                    console.warn(`[CombatSystem] Source ${sourceId} not found`);
                    continue;
                }

                const damage = source.get(Damage);
                if (!damage) {
                    console.warn(`[CombatSystem] No Damage on source ${source.id}`);
                    continue;
                }

                const { damageType, minDamage = 0, variance = 0 } = damage.state;
                let damageAmount = damage.value;

                if (variance > 0) {
                    if (!random) {
                        random = getWorldRandom(world);
                    }
                    const variedDamage = random.use(r => r.nextInt(-variance, variance));

                    if (variedDamage > 0) {
                        damageAmount += variedDamage;
                    }
                }

                const crit = source.get(Crit);

                if (crit) {
                    const { chance } = crit.state;
                    if (!random) {
                        random = getWorldRandom(world);
                    }
                    const roll = random.use(r => r.nextFloat());
                    if (roll < chance) {
                        damageAmount *= crit.value;
                    }
                }

                const defense = target.get(Defense, (c) => c.state.damageType === damageType);
                if (defense) {
                    damageAmount -= defense.value;
                }

                damageAmount = Math.max(0, minDamage, damageAmount);

                // Apply on-hit effects from source onto target
                for (const component of source.getAll(EffectTemplate)) {
                    const s = component.state;
                    target.add(
                        new Effect({
                            target: s.target,
                            targetKey: s.targetKey,
                            delta: s.delta,
                            targetField: s.targetField,
                            duration: s.duration ?? undefined,
                            permanent: s.permanent,
                            gradual: s.gradual,
                            condition: s.condition ?? undefined,
                            stacking: s.stacking,
                            tag: s.tag ?? undefined,
                        }),
                        `__hit_${source.id}_${component.key}_${hitEffectCounter++}`,
                    );
                }

                damageSum += damageAmount;
                hitEvents.push({ attackerId, sourceId, amount: damageAmount, damageType });
                lastHit = { attackerId, sourceId };
            }

            if (damageSum === 0) continue;

            const wasAlive = health.value > 0;
            health.update(-damageSum);

            for (const info of hitEvents) {
                target.emit('hit', info);
            }

            if (wasAlive && health.value <= 0 && lastHit) {
                target.emit('kill', lastHit);
            }
        }
    }
}
