/**
 * Состояние игры: флаги сюжета и именованные переменные.
 * Жанронезависимо: движок знает только механику, контент (какие флаги есть)
 * определяется игрой. Сериализуется в/из сейва вместе с позицией и прочим.
 */

export interface GameStateData {
    version: number;
    flags: string[];
    vars: Record<string, number | string | boolean>;
}

export type GameStateMigration = (data: GameStateData) => GameStateData;

export class GameState {
    private flags = new Set<string>();
    private vars = new Map<string, number | string | boolean>();
    private migrations: GameStateMigration[] = [];

    /** Текущая версия формата данных (растёт при изменениях структуры сейва). */
    dataVersion = 1;

    /**
     * Зарегистрировать миграцию: применяется к сейвам с dataVersion меньше
     * текущей. Миграции регистрируются по порядку от старых версий к новым.
     */
    addMigration(m: GameStateMigration): void {
        this.migrations.push(m);
    }

    // --- флаги ---

    setFlag(flag: string): void {
        this.flags.add(flag);
    }

    hasFlag(flag: string): boolean {
        return this.flags.has(flag);
    }

    clearFlag(flag: string): void {
        this.flags.delete(flag);
    }

    /** Все флаги (для отладки). */
    get allFlags(): string[] {
        return [...this.flags];
    }

    // --- переменные ---

    setVar(key: string, value: number | string | boolean): void {
        this.vars.set(key, value);
    }

    getVar(key: string): number | string | boolean | undefined {
        return this.vars.get(key);
    }

    getNumber(key: string): number {
        const v = this.vars.get(key);
        return typeof v === 'number' ? v : 0;
    }

    getString(key: string): string {
        const v = this.vars.get(key);
        return typeof v === 'string' ? v : '';
    }

    getBool(key: string): boolean {
        const v = this.vars.get(key);
        return v === true || typeof v === 'number' && v !== 0;
    }

    // --- сериализация ---

    serialize(): GameStateData {
        return {
            version: this.dataVersion,
            flags: [...this.flags],
            vars: Object.fromEntries(this.vars)
        };
    }

    /** Загрузить данные (с применением миграций). Восстанавливает состояние поверх текущего. */
    load(data: GameStateData): void {
        let d = data;
        // Контракт: миграция сама повышает version. Если она забыла —
        // выходим, как только данные догнали текущую версию, чтобы не
        // применить следующую миграцию к уже повышенным данным дважды.
        for (const m of this.migrations) {
            if (d.version >= this.dataVersion) break;
            d = m(d);
        }
        this.dataVersion = d.version;
        this.flags = new Set(d.flags);
        this.vars = new Map(Object.entries(d.vars));
    }

    /** Сбросить всё (новая игра). */
    reset(): void {
        this.flags.clear();
        this.vars.clear();
    }
}