import {
    EventBus,
    AudioManager,
    IsometricTileMap,
    World,
    worldToTile,
    worldDist,
    worldNorm,
    inCircleW,
    inConeW,
    type Entity,
    type System,
    type Vec2
} from '@rpg/engine';
import { ENEMY_KINDS, type EnemyKindDef, type EnemyKindId } from '../../data/enemies';
import { EnemyBrain, type EnemyIntent } from './EnemyBrain';
import { PLAYER_COMBAT } from './stats';

/**
 * Боевой мир: сущности (враги, снаряды) в движковом ECS, поведение — в системах.
 * Пиксельные вьюхи держатся отдельно (CombatViews) — в ECS только данные.
 */

export interface EnemyComp {
    kind: EnemyKindDef;
    brain: EnemyBrain;
    pos: Vec2;
    hp: number;
    /** Секунды до исчезновения трупа (после смерти). */
    corpseTimer: number;
}

export interface ProjectileComp {
    pos: Vec2;
    vel: Vec2;
    damage: number;
    life: number;
}

export interface CombatDeps {
    map: IsometricTileMap;
    events: EventBus;
    audio: AudioManager;
    /** Позиция героя (ноги), мировые юниты (1 юнит = 1 тайл). */
    getPlayerPos: () => Vec2;
    /** Герой получил урон (сцена отбросит/шейкнет/проиграет звук). */
    damagePlayer: (damage: number, from: Vec2) => void;
}

/** Движение врага: шаг в мировых юнитах с отбраковкой по проходимости. */
function stepWithCollision(map: IsometricTileMap, pos: Vec2, dir: Vec2, speed: number, dt: number): void {
    const nx = pos.x + dir.x * speed * dt;
    const ny = pos.y + dir.y * speed * dt;
    const tile = worldToTile(nx, ny, map.data.width, map.data.height);
    if (tile && map.isWalkable(tile.x, tile.y)) {
        pos.x = nx;
        pos.y = ny;
    }
}

// ---------- системы ECS ----------

/** ИИ и движение сгустков. */
class EnemySystem implements System {
    constructor(private combat: CombatWorld) {}

    update(_world: World, dt: number): void {
        const playerPos = this.combat.deps.getPlayerPos();
        for (const [e, en] of this.combat.enemies) {
            const world = this.combat.world;
            if (en.brain.dead) {
                en.corpseTimer -= dt;
                if (en.corpseTimer <= 0) {
                    world.destroyEntity(e);
                    this.combat.enemies.delete(e);
                }
                continue;
            }

            const dx = playerPos.x - en.pos.x;
            const dy = playerPos.y - en.pos.y;
            const dist = worldDist(en.pos, playerPos) || 1e-4;
            const intent: EnemyIntent = en.brain.update(dt, {
                dist,
                dirToPlayer: worldNorm(dx, dy)
            });

            switch (intent.type) {
                case 'move':
                    stepWithCollision(this.combat.deps.map, en.pos, intent.dir, en.kind.speed, dt);
                    break;
                case 'strike':
                    // Удар доходит, если герой в радиусе тела + замах
                    if (dist <= en.kind.attackRange + 0.25) {
                        this.combat.deps.damagePlayer(en.kind.attackDamage, en.pos);
                    }
                    break;
                case 'shoot':
                    this.combat.spawnProjectile(en.pos, intent.dir, en.kind.attackDamage);
                    void this.combat.deps.audio.play('sfx/spit', 0.6);
                    break;
                default:
                    break;
            }
        }
    }
}

/** Снаряды: полёт, попадание в героя, срок жизни. */
class ProjectileSystem implements System {
    constructor(private combat: CombatWorld) {}

    update(_world: World, dt: number): void {
        const playerPos = this.combat.deps.getPlayerPos();
        for (const [e, pr] of this.combat.projectiles) {
            pr.life -= dt;
            pr.pos.x += pr.vel.x * dt;
            pr.pos.y += pr.vel.y * dt;

            // Столкновение с героем
            if (inCircleW(playerPos, 0.2, pr.pos)) {
                this.combat.deps.damagePlayer(pr.damage, pr.pos);
                this.combat.killProjectile(e);
                continue;
            }
            // Вне карты или срок истёк
            const tile = worldToTile(pr.pos.x, pr.pos.y, this.combat.deps.map.data.width, this.combat.deps.map.data.height);
            if (pr.life <= 0 || !tile) {
                this.combat.killProjectile(e);
            }
        }
    }
}

// ---------- фасад ----------

export class CombatWorld {
    readonly world = new World();
    readonly enemies = new Map<Entity, EnemyComp>();
    readonly projectiles = new Map<Entity, ProjectileComp>();

    constructor(
        readonly deps: CombatDeps,
        private onEnemyKilled?: (kind: EnemyKindDef) => void
    ) {
        this.world.addSystem(new EnemySystem(this));
        this.world.addSystem(new ProjectileSystem(this));
    }

    /** Заспавнить сгусток (спит в пепле, пока не разбудит громкий звук). */
    spawnEnemy(kindId: EnemyKindId, pos: Vec2): Entity {
        const kind = ENEMY_KINDS[kindId];
        const e = this.world.createEntity();
        this.enemies.set(e, {
            kind,
            brain: new EnemyBrain(kind),
            pos: { ...pos },
            hp: kind.hp,
            corpseTimer: 0.5
        });
        return e;
    }

    spawnProjectile(from: Vec2, dir: Vec2, damage: number): Entity {
        const e = this.world.createEntity();
        this.projectiles.set(e, {
            pos: { x: from.x + dir.x * 0.3, y: from.y + dir.y * 0.3 },
            vel: { x: dir.x * 2.2, y: dir.y * 2.2 },
            damage,
            life: 1.5
        });
        return e;
    }

    killProjectile(e: Entity): void {
        if (!this.world.isAlive(e)) return;
        this.world.destroyEntity(e);
        this.projectiles.delete(e);
    }

    /**
     * Короткий удар конусом. Возвращает число поражённых сгустков.
     * Побочный эффект: гулкий звук будит спящих в wakeRadius.
     */
    playerConeAttack(from: Vec2, dir: Vec2): number {
        void this.deps.audio.play('sfx/bell_hit', 0.8);
        this.loudSound(from, PLAYER_COMBAT.wakeRadius);
        this.deps.events.emit('combat:attack', { mode: 'cone', origin: { ...from } });

        let hits = 0;
        for (const [e, en] of this.enemies) {
            if (en.brain.dead) continue;
            if (!inConeW(from, dir, PLAYER_COMBAT.coneRange, PLAYER_COMBAT.coneHalfAngle, en.pos)) continue;
            this.damageEnemy(e, en, PLAYER_COMBAT.coneDamage, from);
            hits++;
        }
        return hits;
    }

    /** Низкий резонанс: усыпляет сгустков в радиусе. Возвращает число усыпленных. */
    resonancePulse(from: Vec2): number {
        void this.deps.audio.play('sfx/bell_low', 0.9);
        this.deps.events.emit('combat:attack', { mode: 'resonance', origin: { ...from } });

        let slept = 0;
        for (const [, en] of this.enemies) {
            if (en.brain.dead) continue;
            if (inCircleW(from, PLAYER_COMBAT.resonanceRange, en.pos)) {
                en.brain.putToSleep(PLAYER_COMBAT.resonanceSleep);
                slept++;
            }
        }
        return slept;
    }

    /** Гулкий звук в точке: будит спящих сгустков в радиусе. */
    loudSound(pos: Vec2, radius: number): void {
        for (const [, en] of this.enemies) {
            if (inCircleW(pos, radius, en.pos) && en.brain.asleep) {
                en.brain.hearLoud();
                void this.deps.audio.play('sfx/ash_hiss', 0.5);
                this.deps.events.emit('combat:awake', { kind: en.kind.id });
            }
        }
    }

    /** Урон сгустку; смерть — дроп и событие. */
    damageEnemy(e: Entity, en: EnemyComp, damage: number, from: Vec2): void {
        en.hp -= damage;
        en.brain.hurt();
        this.deps.events.emit('combat:hurt', { entity: e, died: en.hp <= 0 });
        if (en.hp <= 0) {
            if (en.brain.die()) {
                void this.deps.audio.play('sfx/ash_die', 0.8);
                this.deps.events.emit('combat:kill', { kind: en.kind.id, tile: { ...en.pos } });
                this.onEnemyKilled?.(en.kind);
                // Отброс трупа от героя — лёгкий визуальный эффект делает CombatViews.
                en.pos.x += Math.sign(en.pos.x - from.x) * 0.0625;
                en.pos.y += Math.sign(en.pos.y - from.y) * 0.0625;
            }
        }
    }

    update(dt: number): void {
        this.world.update(dt);
    }
}