/**
 * HistoryManager — Undo/Redo через snapshot-историю.
 *
 * Каждое действие пользователя (поставил блок, сдвинул модель, удалил и т.д.)
 * создаёт снимок состояния сцены через scene.serialize(). История хранит до
 * MAX_HISTORY последних снимков. Ctrl+Z откатывает на предыдущий, Ctrl+Y возвращает.
 *
 * Простая реализация (не diff-based):
 *   - snapshot = JSON.stringify(scene.serialize())
 *   - размер на сцену ~10-50 КБ
 *   - 50 снапшотов = ~1-2 МБ памяти максимум
 *
 * Запись:
 *   markChange()  — снять текущий snapshot (вызывается debounced при изменениях)
 * Откат:
 *   undo()        — применить предыдущий snapshot
 *   redo()        — применить следующий
 */

const MAX_HISTORY = 50;
const DEBOUNCE_MS = 250;   // не снимаем snapshot чаще чем раз в 250мс

export class HistoryManager {
    /**
     * @param {() => string} serializer — возвращает текущее состояние как строку
     * @param {(state: object) => Promise<void>} restorer — восстанавливает состояние
     */
    constructor(serializer, restorer) {
        this.serializer = serializer;
        this.restorer = restorer;

        this._past = [];          // [snapshot, ...] — история до текущего
        this._future = [];        // [snapshot, ...] — для redo (заполняется при undo)
        this._current = null;     // snapshot текущего состояния
        this._debounceTimer = null;
        this._isApplying = false; // защита от рекурсии при restoreFromState → markChange
    }

    /**
     * Сделать snapshot текущего состояния. Debounced — частые изменения
     * (например drag-постановка 30 блоков подряд) сольются в один snapshot.
     */
    markChange() {
        if (this._isApplying) return;
        if (this._debounceTimer) clearTimeout(this._debounceTimer);
        this._debounceTimer = setTimeout(() => {
            this._debounceTimer = null;
            this._takeSnapshot();
        }, DEBOUNCE_MS);
    }

    /** Принудительный snapshot прямо сейчас (без debounce). */
    flushPending() {
        if (this._debounceTimer) {
            clearTimeout(this._debounceTimer);
            this._debounceTimer = null;
            this._takeSnapshot();
        }
    }

    _takeSnapshot() {
        const snap = this.serializer();
        if (!snap) return;
        // Если ничего не изменилось — пропускаем
        if (snap === this._current) return;
        // Любая новая запись стирает future (классическое поведение)
        if (this._current !== null) {
            this._past.push(this._current);
            if (this._past.length > MAX_HISTORY) this._past.shift();
        }
        this._current = snap;
        this._future = [];
    }

    /** Инициализация: запомнить начальное состояние (вызвать после loadFromState). */
    initialize() {
        this._past = [];
        this._future = [];
        this._current = this.serializer();
    }

    /** Можно ли откатиться. */
    canUndo() {
        return this._past.length > 0;
    }

    /** Можно ли вернуть. */
    canRedo() {
        return this._future.length > 0;
    }

    async undo() {
        this.flushPending();
        if (!this.canUndo()) return false;
        const prev = this._past.pop();
        if (this._current !== null) this._future.push(this._current);
        this._current = prev;
        await this._apply(prev);
        return true;
    }

    async redo() {
        this.flushPending();
        if (!this.canRedo()) return false;
        const next = this._future.pop();
        if (this._current !== null) this._past.push(this._current);
        this._current = next;
        await this._apply(next);
        return true;
    }

    async _apply(snap) {
        this._isApplying = true;
        try {
            const obj = JSON.parse(snap);
            await this.restorer(obj);
        } catch (e) {
            // eslint-disable-next-line no-console
            console.error('[HistoryManager] restore error', e);
        } finally {
            this._isApplying = false;
        }
    }

    /** Полная очистка (при закрытии сцены). */
    dispose() {
        if (this._debounceTimer) clearTimeout(this._debounceTimer);
        this._past = [];
        this._future = [];
        this._current = null;
    }
}
