/**
 * Пейзажная фауна (перенос v1 FaunaSystem, воксельная версия): безгласные
 * олени. Стоят, медленно бродят по соседним тайлам; на громкий звон рядом
 * подходят к источнику и стоят у него. В v1 звон — боевой удар героя
 * (колокольчик); боёвки в игре v2 пока нет — звон подаёт startleAt
 * (гейт зовёт мостом startleFauna, при переносе боя проводим от удара).
 */
import { Rng, type VoxelCloud } from '@rpg/engine';
import { UNIT, tileCenter } from './areas';
import { deerVoxels } from './deer';

/** Радиус слуха на звон (v1: 7 юнитов = 7 тайлов → воксели). */
const HEAR_RADIUS = 7 * UNIT;
/** Скорость брожения / подхода (v1: тайлов/сек → вокселей/сек). */
const WANDER_SPEED = 0.35 * UNIT;
const APPROACH_SPEED = 0.7 * UNIT;
/** Пауза между шагами брожения (сек), как в v1: 1.5 + rng*3. */
const PAUSE_BASE = 1.5;
const PAUSE_RANGE = 3;

/** Состояние автомата оленя (снапшот моста). */
export type DeerState = 'idle' | 'wander' | 'approach';

export interface DeerInfo {
    /** Позиция ног (мир, воксели). */
    x: number;
    z: number;
    /** Тайл, на котором стоит (в тайлах v1). */
    tile: { x: number; y: number };
    state: DeerState;
}

interface Deer {
    pos: { x: number; z: number };
    dest: { x: number; z: number } | null;
    state: DeerState;
    /** Пауза до следующего шага брожения (сек). */
    pause: number;
    /** Ключ последнего отрисованного вида (поза/позиция) — без лишних апдейтов. */
    drawn: string;
    cloud: VoxelCloud;
}

export interface FaunaDeps {
    /** Спавны в тайлах v1 (олень стоит в центре тайла). */
    spawns: readonly { x: number; y: number }[];
    /** Непроходимость тайла (вода/пропы) — туда олень не идёт. */
    blocked: (tx: number, ty: number) => boolean;
    /** Фабрика облаков вокселей (renderer.addVoxelCloud). */
    addCloud: (voxels: ReturnType<typeof deerVoxels>) => VoxelCloud;
}

/** Движение к точке с шагом не больше step; true — дошли (снап к точке). */
function stepTowards(
    pos: { x: number; z: number },
    to: { x: number; z: number },
    step: number,
): boolean {
    const dx = to.x - pos.x;
    const dz = to.z - pos.z;
    const d = Math.hypot(dx, dz);
    if (d <= step) {
        pos.x = to.x;
        pos.z = to.z;
        return true;
    }
    pos.x += (dx / d) * step;
    pos.z += (dz / d) * step;
    return false;
}

export class FaunaSystem {
    private deer: Deer[] = [];
    /** Детерминированный ГПСЧ — брожение одинаково между прогонами (v1). */
    private rng = new Rng(20260908);

    constructor(private deps: FaunaDeps) {
        for (const s of deps.spawns) this.spawn(s);
    }

    /** Заспавнить оленя в центре тайла (стоя, ноги на земле). */
    private spawn(s: { x: number; y: number }): void {
        const c = tileCenter(s.x, s.y);
        this.deer.push({
            pos: { x: c.x, z: c.z },
            dest: null,
            state: 'idle',
            pause: PAUSE_BASE + this.rng.next() * PAUSE_RANGE,
            drawn: '',
            cloud: this.deps.addCloud(deerVoxels(c.x, c.z, false)),
        });
    }

    /** Громкий звон рядом: олень подходит к источнику и стоит (v1). */
    startleAt(x: number, z: number): void {
        for (const d of this.deer) {
            if (Math.hypot(d.pos.x - x, d.pos.z - z) > HEAR_RADIUS) continue;
            d.dest = { x, z };
            d.state = 'approach';
        }
    }

    /** Шаг брожения: случайный соседний проходимый тайл (v1). */
    private startWander(d: Deer): void {
        const tx = Math.floor(d.pos.x / UNIT);
        const ty = Math.floor(d.pos.z / UNIT);
        const options = [[1, 0], [-1, 0], [0, 1], [0, -1]]
            .map(([dx, dy]) => ({ x: tx + dx, y: ty + dy }))
            .filter((t) => !this.deps.blocked(t.x, t.y));
        if (options.length === 0) {
            d.pause = 2;
            return;
        }
        const t = this.rng.pick(options);
        d.dest = tileCenter(t.x, t.y);
        d.state = 'wander';
    }

    /**
     * Тик фауны: автомат (idle→wander→idle, approach по звону) + отрисовка.
     * step — фаза шага (2 шага/сек на ходу, стоя — стоп-кадр), как в v1.
     */
    update(dt: number, t: number): void {
        for (const d of this.deer) {
            if (d.state === 'idle') {
                d.pause -= dt;
                if (d.pause <= 0) this.startWander(d);
            } else if (d.dest && stepTowards(d.pos, d.dest, (d.state === 'approach' ? APPROACH_SPEED : WANDER_SPEED) * dt)) {
                d.dest = null;
                d.state = 'idle';
                d.pause = PAUSE_BASE + this.rng.next() * PAUSE_RANGE;
            }
            this.draw(d, t);
        }
    }

    /** Перерисовать облако, если позиция или фаза шага изменились. */
    private draw(d: Deer, t: number): void {
        const step = d.dest !== null && Math.floor(t * 2) % 2 === 0;
        const key = `${d.pos.x.toFixed(2)},${d.pos.z.toFixed(2)},${step ? 1 : 0}`;
        if (key === d.drawn) return;
        d.drawn = key;
        d.cloud.update(deerVoxels(d.pos.x, d.pos.z, step));
    }

    /** Снапшот для моста: олени области. */
    info(): DeerInfo[] {
        return this.deer.map((d) => ({
            x: d.pos.x,
            z: d.pos.z,
            tile: { x: Math.floor(d.pos.x / UNIT), y: Math.floor(d.pos.z / UNIT) },
            state: d.state,
        }));
    }

    dispose(): void {
        for (const d of this.deer) d.cloud.dispose();
        this.deer.length = 0;
    }
}