import { Graphics } from 'pixi.js';

/**
 * Сцена — экран игры (меню, локация, бой). Менеджер держит стек,
 * но в 2D-RPG чаще всего достаточно push/pop/replace.
 */
export interface Scene {
    /** Вызывается один раз при входе. */
    enter(): void | Promise<void>;
    exit(): void | Promise<void>;
    /** dt в секундах, фиксированный шаг. */
    update(dt: number): void;
    render(): void;
}

/** Затемнение экрана при смене сцены. */
export interface SceneTransition {
    /** Общая длительность (затухание + проявление), секунды. По умолчанию 0.5. */
    duration?: number;
    /** Цвет затемнения. По умолчанию чёрный. */
    color?: number;
}

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

interface PendingTransition {
    kind: TransitionKind;
    scene?: Scene;
    duration: number;
    color: number;
    phase: 'out' | 'in';
    t: number;
}

export class SceneManager {
    private stack: Scene[] = [];
    private overlay: Graphics | null = null;
    private overlayWidth = 0;
    private overlayHeight = 0;
    private pending: PendingTransition | null = null;

    /** Движок подключает оверлей затемнения (верхний слой uiRoot). */
    setOverlay(g: Graphics, width: number, height: number): void {
        this.overlay = g;
        this.overlayWidth = width;
        this.overlayHeight = height;
    }

    async push(scene: Scene, transition?: SceneTransition): Promise<void> {
        if (transition) {
            this.begin('push', scene, transition);
            return;
        }
        this.stack.push(scene);
        await scene.enter();
    }

    async pop(transition?: SceneTransition): Promise<void> {
        if (transition) {
            this.begin('pop', undefined, transition);
            return;
        }
        const scene = this.stack.pop();
        if (scene) {
            await scene.exit();
        }
    }

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

    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 ?? 0.5,
            color: tr.color ?? 0x000000,
            phase: 'out',
            t: 0
        };
    }

    private stepTransition(dt: number): void {
        const p = this.pending!;
        p.t += dt;

        if (p.phase === 'out') {
            this.drawOverlay(p.color, Math.min(1, p.t / p.duration));
            if (p.t < p.duration) return;
            // экран скрыт — меняем сцену
            this.performSwap(p);
            p.phase = 'in';
            p.t -= p.duration;
        }

        // фаза 'in': проявление
        this.drawOverlay(p.color, Math.max(0, 1 - p.t / p.duration));
        if (p.t >= p.duration) {
            this.pending = null;
            this.overlay?.clear();
        }
    }

    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;
        }
        // enter может быть асинхронным — переход не ждёт его,
        // новая сцена проявляется параллельно со своей инициализацией.
        void this.stack[this.stack.length - 1]?.enter();
    }

    private drawOverlay(color: number, alpha: number): void {
        if (!this.overlay) return;
        const g = this.overlay;
        g.clear();
        if (alpha > 0) {
            g.rect(0, 0, this.overlayWidth, this.overlayHeight).fill({ color, alpha });
        }
    }
}