/**
 * Конечный автомат: состояния с enter/exit/update и переходы по событиям.
 * Используется для AI врагов, состояний игрока (idle/run/attack), экранов меню и т.п.
 */

export interface StateDef {
    /** Вызывается при входе в состояние. */
    enter?: () => void;
    exit?: () => void;
    /** dt в секундах, фиксированный шаг. */
    update?: (dt: number) => void;
}

export class StateMachine {
    private states = new Map<string, StateDef>();
    /** (состояние, событие) -> состояние-цель. */
    private transitions = new Map<string, string>();
    /** Переходы «из любого состояния» (кроме self-loop): событие -> цель. */
    private anyTransitions = new Map<string, string>();
    private currentName: string | null = null;
    private timeInState = 0;

    constructor(
        /** Неизвестное событие в текущем состоянии (по умолчанию — просто игнор). */
        private onUnhandled?: (state: string, event: string) => void
    ) {}

    add(name: string, def: StateDef): this {
        this.states.set(name, def);
        return this;
    }

    /** Переход из состояния `from` по событию `event` в состояние `to`. */
    transition(from: string, event: string, to: string): this {
        if (!this.states.has(to)) {
            throw new Error(`Переход в неизвестное состояние: ${to}`);
        }
        this.transitions.set(`${from}::${event}`, to);
        return this;
    }

    /** Перейти в состояние (exit текущего -> enter нового). Повторный вход в то же состояние игнорируется. */
    change(name: string): void {
        if (!this.states.has(name)) {
            throw new Error(`Неизвестное состояние: ${name}`);
        }
        if (this.currentName === name) return;
        if (this.currentName) this.states.get(this.currentName)!.exit?.();
        this.currentName = name;
        this.timeInState = 0;
        this.states.get(name)!.enter?.();
    }

    /**
     * Переход по событию из ЛЮБОГО состояния (кроме повторного входа в цель).
     * Удобно для hurt/die в бою: не регистрировать переход из каждого состояния.
     * Обычные переходы имеют приоритет над transitionAny.
     */
    transitionAny(event: string, to: string): this {
        if (!this.states.has(to)) {
            throw new Error(`Переход в неизвестное состояние: ${to}`);
        }
        this.anyTransitions.set(event, to);
        return this;
    }

    /** Обработать событие: обычный переход текущего состояния, затем «из любого». */
    handleEvent(event: string): void {
        if (!this.currentName) return;
        const to = this.transitions.get(`${this.currentName}::${event}`) ?? this.anyTransitions.get(event);
        if (to) {
            this.change(to);
        } else {
            this.onUnhandled?.(this.currentName, event);
        }
    }

    get current(): string | null {
        return this.currentName;
    }

    /** Секунд с момента входа в текущее состояние. */
    get time(): number {
        return this.timeInState;
    }

    update(dt: number): void {
        if (!this.currentName) return;
        this.timeInState += dt;
        this.states.get(this.currentName)!.update?.(dt);
    }

    reset(): void {
        this.currentName = null;
        this.timeInState = 0;
    }
}