/**
 * Сцены (перенос схемы v1, без привязки к бэкенду): сцена — экран игры
 * (меню, локация, бой); менеджер держит стек, чаще всего достаточно
 * push/pop/replace. Затемнение при смене — инъекция FadeOverlay (в демке
 * это DOM-слой; в тестах — заглушка): ядро о бэкенде не знает (урок №3).
 * Параллельные переходы глотаются; enter новой сцены не ожидается —
 * проявление идёт параллельно с её инициализацией.
 */

/** Сцена — экран игры. update — фиксированный шаг dt в секундах. */
export interface Scene {
    /** Имя для моста/отладки (необязательно). */
    name?: string;
    /** Вызывается один раз при входе (может быть асинхронным). */
    enter(): void | Promise<void>;
    exit(): void | Promise<void>;
    update(dt: number): void;
    render(): void;
}

/** Затемнение экрана при смене сцены (цвет/стиль — на игре). */
export interface FadeOverlay {
    /** Прозрачность 0..1 (0 — не рисовать). */
    setAlpha(alpha: number): void;
}

/** Параметры перехода: длительность затухание+проявление, секунды. */
export interface SceneTransition {
    duration?: number;
}

type TransitionKind = 'push' | 'pop' | 'replace';

interface PendingTransition {
    kind: TransitionKind;
    scene?: Scene;
    duration: number;
    phase: 'out' | 'in';
    /** Прошедшее время внутри фазы. */
    t: number;
}

const DEFAULT_DURATION = 0.5;

export class SceneManager {
    private stack: Scene[] = [];
    private pending: PendingTransition | null = null;

    constructor(private readonly overlay?: FadeOverlay) {}

    /** Положить сцену на стек (с переходом — по завершении затухания). */
    push(scene: Scene, transition?: SceneTransition): void {
        if (transition) {
            this.begin('push', scene, transition);
            return;
        }
        this.stack.push(scene);
        void scene.enter();
    }

    /** Снять верхнюю сцену. */
    pop(transition?: SceneTransition): void {
        if (transition) {
            this.begin('pop', undefined, transition);
            return;
        }
        this.stack.pop()?.exit();
    }

    /** Заменить верхнюю сцену (например меню → локация). */
    replace(scene: Scene, transition?: SceneTransition): void {
        if (transition) {
            this.begin('replace', scene, transition);
            return;
        }
        this.stack.pop()?.exit();
        this.stack.push(scene);
        void scene.enter();
    }

    get current(): Scene | undefined {
        return this.stack[this.stack.length - 1];
    }

    /** Идёт ли сейчас переход (для блокировки ввода). */
    get transitioning(): boolean {
        return this.pending !== null;
    }

    update(dt: number): void {
        if (this.pending) this.stepTransition(dt);
        this.current?.update(dt);
    }

    /** Рисует весь стек: нижние сцены видны под верхними. */
    render(): void {
        for (const scene of this.stack) scene.render();
    }

    private begin(kind: TransitionKind, scene: Scene | undefined, tr: SceneTransition): void {
        if (this.pending) return; // параллельные переходы запрещены
        this.pending = {
            kind,
            scene,
            duration: tr.duration && tr.duration > 0 ? tr.duration : DEFAULT_DURATION,
            phase: 'out',
            t: 0,
        };
    }

    /** Фазы: затухание → смена сцены → проявление. */
    private stepTransition(dt: number): void {
        const p = this.pending!;
        p.t += dt;
        if (p.phase === 'out') {
            const a = Math.min(p.t / p.duration, 1);
            this.overlay?.setAlpha(a);
            if (a >= 1) {
                this.performSwap(p);
                p.phase = 'in';
                p.t = 0;
            }
            return;
        }
        const a = Math.max(1 - p.t / p.duration, 0);
        this.overlay?.setAlpha(a);
        if (p.t >= p.duration) {
            this.pending = null;
            this.overlay?.setAlpha(0);
        }
    }

    private performSwap(p: PendingTransition): void {
        switch (p.kind) {
            case 'push':
                if (p.scene) this.stack.push(p.scene);
                break;
            case 'pop':
                this.stack.pop()?.exit();
                break;
            case 'replace':
                this.stack.pop()?.exit();
                if (p.scene) this.stack.push(p.scene);
                break;
        }
        void this.stack[this.stack.length - 1]?.enter();
    }
}