/**
 * Сохранения в JSON-слотах. Хранилище инъекцией (localStorage или любой
 * Storage-подобный объект) — чтобы работать и в тестах без браузера.
 * Метаданные слота (заголовок, время) — в параллельном ключе `meta:<slot>`:
 * формат самих сейвов не меняется, старые сейвы остаются читаемыми.
 */

/** Метаданные слота — читаются без полной загрузки сейва (задаёт игра). */
export interface SaveSlotMeta {
    /** Человекочитаемый заголовок (движок не знает жанра). */
    title?: string;
    /** Момент записи (Date.now()). */
    savedAt?: number;
    /** Версия формата сейва. */
    version?: number;
    /** Жанровые поля игры (область, уровень, часы игры…). */
    extras?: Record<string, string | number | boolean>;
}

export interface StorageLike {
    getItem(key: string): string | null;
    setItem(key: string, value: string): void;
    removeItem(key: string): void;
    key(index: number): string | null;
    readonly length: number;
}

export class SaveManager {
    constructor(
        private storage: StorageLike,
        private prefix = 'save:'
    ) {}

    save(slot: string, data: unknown, meta?: SaveSlotMeta): void {
        this.storage.setItem(this.prefix + slot, JSON.stringify(data));
        if (meta) this.storage.setItem(this.metaKey(slot), JSON.stringify(meta));
    }

    load<T = unknown>(slot: string): T | null {
        const raw = this.storage.getItem(this.prefix + slot);
        if (raw === null) return null;
        try {
            return JSON.parse(raw) as T;
        } catch {
            return null;
        }
    }

    /** Метаданные слота (null — мета не писалась, например у старых сейвов). */
    slotMeta(slot: string): SaveSlotMeta | null {
        const raw = this.storage.getItem(this.metaKey(slot));
        if (raw === null) return null;
        try {
            return JSON.parse(raw) as SaveSlotMeta;
        } catch {
            return null;
        }
    }

    /** Слоты с метой (мета может отсутствовать — у сейвов старых версий). */
    list(): Array<{ slot: string; meta: SaveSlotMeta | null }> {
        return this.listSlots().map((slot) => ({ slot, meta: this.slotMeta(slot) }));
    }

    delete(slot: string): void {
        this.storage.removeItem(this.prefix + slot);
        this.storage.removeItem(this.metaKey(slot));
    }

    /** Есть ли сохранение в слоте. */
    has(slot: string): boolean {
        return this.storage.getItem(this.prefix + slot) !== null;
    }

    /** Список занятых слотов (без префикса; мета-ключи не попадают). */
    listSlots(): string[] {
        const out: string[] = [];
        for (let i = 0; i < this.storage.length; i++) {
            const key = this.storage.key(i);
            if (key && key.startsWith(this.prefix)) {
                const slot = key.slice(this.prefix.length);
                if (!slot.startsWith('meta:')) out.push(slot);
            }
        }
        return out;
    }

    private metaKey(slot: string): string {
        return this.prefix + 'meta:' + slot;
    }
}