'use strict';

/* ────────────────────────────────────────────────────────────────
   Save System v2 — Cloud saves, migration, compression
   
   Enhanced save system with:
   - Multiple save slots with metadata
   - LZ77-style compression for save data
   - Schema versioning with automatic migration
   - Cloud sync with conflict resolution
   - Auto-save with configurable interval
   - Save data integrity (checksums)
──────────────────────────────────────────────────────────────── */
const DEFAULT_SAVE_CONFIG = {
    schemaVersion: 1,
    storageBackend: 'indexeddb',
    storageKey: 'nice-game-saves',
    maxSlots: 20,
    compressionEnabled: true,
    autoSave: {
        enabled: true,
        intervalMs: 60000,
        maxAutoSaves: 3,
        requireSafeState: true,
    },
    cloud: {
        enabled: false,
        conflictStrategy: 'newest-wins',
        syncIntervalMs: 300000,
        maxCloudSlots: 10,
    },
};
/* ══════════════════════════════════════════════════════════════
   COMPRESSION — LZ77-style + RLE for save data
   ══════════════════════════════════════════════════════════════ */
/** Simple compression for JSON save data (dictionary-based) */
function compressSaveData(json) {
    const encoder = new TextEncoder();
    const raw = encoder.encode(json);
    // For small data, skip compression
    if (raw.length < 256) {
        const out = new Uint8Array(1 + raw.length);
        out[0] = 0; // flag: uncompressed
        out.set(raw, 1);
        return out;
    }
    // Simple dictionary compression using common JSON patterns
    const dictionary = [
        '":"', '","', '":true', '":false', '":null', '":0', '":1',
        '{"', '"}', '["', '"]', '\\n', '\\t',
    ];
    const chunks = [1]; // flag: compressed
    // Write dictionary length
    chunks.push(dictionary.length);
    let i = 0;
    while (i < raw.length) {
        let matched = false;
        // Try dictionary match
        for (let d = 0; d < dictionary.length; d++) {
            const dictBytes = encoder.encode(dictionary[d]);
            if (i + dictBytes.length <= raw.length) {
                let match = true;
                for (let j = 0; j < dictBytes.length; j++) {
                    if (raw[i + j] !== dictBytes[j]) {
                        match = false;
                        break;
                    }
                }
                if (match) {
                    chunks.push(255); // escape byte = dict reference
                    chunks.push(d); // dict index
                    i += dictBytes.length;
                    matched = true;
                    break;
                }
            }
        }
        // RLE for repeated bytes
        if (!matched && i + 3 < raw.length && raw[i] === raw[i + 1] && raw[i] === raw[i + 2]) {
            let runLen = 1;
            while (i + runLen < raw.length && raw[i + runLen] === raw[i] && runLen < 127)
                runLen++;
            chunks.push(254); // escape byte = RLE
            chunks.push(runLen);
            chunks.push(raw[i]);
            i += runLen;
            matched = true;
        }
        if (!matched) {
            // Literal byte
            if (raw[i] === 254 || raw[i] === 255) {
                chunks.push(253); // escape for literal 254/255
            }
            chunks.push(raw[i]);
            i++;
        }
    }
    return new Uint8Array(chunks);
}
/** Decompress save data */
function decompressSaveData(data) {
    const decoder = new TextDecoder();
    const encoder = new TextEncoder();
    if (data[0] === 0) {
        // Uncompressed
        return decoder.decode(data.slice(1));
    }
    const dictionary = [
        '":"', '","', '":true', '":false', '":null', '":0', '":1',
        '{"', '"}', '["', '"]', '\\n', '\\t',
    ];
    const dictBytes = dictionary.map(s => encoder.encode(s));
    const output = [];
    let i = 2; // skip flag + dict length
    while (i < data.length) {
        if (data[i] === 255) {
            // Dictionary reference
            const dictIdx = data[i + 1];
            if (dictIdx < dictBytes.length) {
                for (const b of dictBytes[dictIdx])
                    output.push(b);
            }
            i += 2;
        }
        else if (data[i] === 254) {
            // RLE
            const runLen = data[i + 1];
            const val = data[i + 2];
            for (let j = 0; j < runLen; j++)
                output.push(val);
            i += 3;
        }
        else if (data[i] === 253) {
            // Escaped literal
            output.push(data[i + 1]);
            i += 2;
        }
        else {
            output.push(data[i]);
            i++;
        }
    }
    return decoder.decode(new Uint8Array(output));
}
/* ══════════════════════════════════════════════════════════════
   CHECKSUM — CRC32 for data integrity
   ══════════════════════════════════════════════════════════════ */
const CRC32_TABLE = [];
for (let i = 0; i < 256; i++) {
    let c = i;
    for (let j = 0; j < 8; j++) {
        c = c & 1 ? 0xEDB88320 ^ (c >>> 1) : c >>> 1;
    }
    CRC32_TABLE[i] = c;
}
function crc32(data) {
    let crc = 0xFFFFFFFF;
    for (let i = 0; i < data.length; i++) {
        crc = CRC32_TABLE[(crc ^ data[i]) & 0xFF] ^ (crc >>> 8);
    }
    return (crc ^ 0xFFFFFFFF) >>> 0;
}
/* ══════════════════════════════════════════════════════════════
   MIGRATION ENGINE
   ══════════════════════════════════════════════════════════════ */
class MigrationEngine {
    constructor() {
        this.migrations = [];
    }
    register(migration) {
        this.migrations.push(migration);
        this.migrations.sort((a, b) => a.fromVersion - b.fromVersion);
    }
    /** Migrate payload from fromVersion to toVersion */
    migrate(payload, fromVersion, toVersion) {
        const applied = [];
        let current = { ...payload };
        let version = fromVersion;
        if (fromVersion < toVersion) {
            // Forward migration
            while (version < toVersion) {
                const migration = this.migrations.find(m => m.fromVersion === version);
                if (!migration) {
                    throw new Error(`No migration found for version ${version} → ${version + 1}`);
                }
                current = migration.migrate(current);
                applied.push(migration.description);
                version = migration.toVersion;
            }
        }
        else if (fromVersion > toVersion) {
            // Rollback (reverse order)
            while (version > toVersion) {
                const migration = this.migrations.find(m => m.toVersion === version);
                if (!migration || !migration.rollback) {
                    throw new Error(`No rollback found for version ${version} → ${version - 1}`);
                }
                current = migration.rollback(current);
                applied.push(`Rollback: ${migration.description}`);
                version = migration.fromVersion;
            }
        }
        return { payload: current, applied };
    }
    getRegistered() {
        return this.migrations;
    }
    canMigrate(from, to) {
        let v = from;
        if (from < to) {
            while (v < to) {
                const m = this.migrations.find(mm => mm.fromVersion === v);
                if (!m)
                    return false;
                v = m.toVersion;
            }
        }
        else {
            while (v > to) {
                const m = this.migrations.find(mm => mm.toVersion === v);
                if (!m || !m.rollback)
                    return false;
                v = m.fromVersion;
            }
        }
        return true;
    }
}
/* ══════════════════════════════════════════════════════════════
   SAVE SYSTEM V2
   ══════════════════════════════════════════════════════════════ */
class SaveSystemV2 {
    constructor(eventBus, config = {}) {
        this.migrationEngine = new MigrationEngine();
        this.slots = new Map();
        this.autoSaveTimer = null;
        this.autoSaveCounter = 0;
        this.safeToSave = true;
        this.config = { ...DEFAULT_SAVE_CONFIG, ...config };
        this.eventBus = eventBus;
        this.setupEventHandlers();
    }
    setupEventHandlers() {
        this.eventBus.on('game:safe-state', (data) => {
            this.safeToSave = data.safe;
        });
    }
    /** Register a schema migration */
    registerMigration(migration) {
        this.migrationEngine.register(migration);
    }
    /** Save game state to a slot */
    save(slotId, payload, options = {}) {
        var _a, _b, _c, _d, _e, _f;
        const encoder = new TextEncoder();
        const json = JSON.stringify(payload);
        const compressed = this.config.compressionEnabled
            ? compressSaveData(json)
            : encoder.encode(json);
        const checksum = crc32(compressed);
        const now = Date.now();
        const existingMeta = (_a = this.slots.get(slotId)) === null || _a === void 0 ? void 0 : _a.meta;
        const meta = {
            slotId,
            type: (_b = options.type) !== null && _b !== void 0 ? _b : 'manual',
            label: (_c = options.label) !== null && _c !== void 0 ? _c : `Save ${slotId}`,
            schemaVersion: this.config.schemaVersion,
            createdAt: (_d = existingMeta === null || existingMeta === void 0 ? void 0 : existingMeta.createdAt) !== null && _d !== void 0 ? _d : now,
            updatedAt: now,
            playtimeMs: existingMeta ? existingMeta.playtimeMs + (now - existingMeta.updatedAt) : 0,
            location: (_e = options.location) !== null && _e !== void 0 ? _e : '',
            thumbnail: options.thumbnail,
            sizeBytes: compressed.length,
            checksum,
            cloudSynced: false,
            tags: (_f = options.tags) !== null && _f !== void 0 ? _f : [],
        };
        this.slots.set(slotId, { meta, payload });
        // Persist to storage backend
        this.persistSlot(slotId, compressed, meta);
        this.eventBus.emit('save:saved', { slotId, sizeBytes: compressed.length });
        return {
            success: true,
            slotId,
            sizeBytes: compressed.length,
            compressed: this.config.compressionEnabled,
        };
    }
    /** Load game state from a slot */
    load(slotId) {
        const data = this.slots.get(slotId);
        if (!data)
            return null;
        let payload = { ...data.payload };
        let migrated = false;
        // Check if migration is needed
        if (data.meta.schemaVersion !== this.config.schemaVersion) {
            if (this.migrationEngine.canMigrate(data.meta.schemaVersion, this.config.schemaVersion)) {
                const result = this.migrationEngine.migrate(payload, data.meta.schemaVersion, this.config.schemaVersion);
                payload = result.payload;
                migrated = true;
                this.eventBus.emit('save:migrated', {
                    slotId,
                    from: data.meta.schemaVersion,
                    to: this.config.schemaVersion,
                    steps: result.applied,
                });
            }
        }
        this.eventBus.emit('save:loaded', { slotId });
        return { payload, meta: { ...data.meta }, migrated };
    }
    /** Delete a save slot */
    delete(slotId) {
        const existed = this.slots.delete(slotId);
        if (existed) {
            this.removeFromStorage(slotId);
            this.eventBus.emit('save:deleted', { slotId });
        }
        return existed;
    }
    /** List all save slot metadata (sorted by updatedAt desc) */
    listSlots() {
        return Array.from(this.slots.values())
            .map(s => ({ ...s.meta }))
            .sort((a, b) => b.updatedAt - a.updatedAt);
    }
    /** Get slot metadata without loading payload */
    getSlotMeta(slotId) {
        const data = this.slots.get(slotId);
        return data ? { ...data.meta } : null;
    }
    /** Check if a slot exists */
    hasSlot(slotId) {
        return this.slots.has(slotId);
    }
    /** Start auto-save timer */
    startAutoSave(getPayload, getLocation) {
        if (!this.config.autoSave.enabled)
            return;
        this.stopAutoSave();
        this.autoSaveTimer = setInterval(() => {
            if (this.config.autoSave.requireSafeState && !this.safeToSave)
                return;
            const slotId = `auto-${this.autoSaveCounter % this.config.autoSave.maxAutoSaves}`;
            this.autoSaveCounter++;
            this.save(slotId, getPayload(), {
                type: 'auto',
                label: `Auto Save ${this.autoSaveCounter}`,
                location: getLocation(),
            });
        }, this.config.autoSave.intervalMs);
    }
    /** Stop auto-save */
    stopAutoSave() {
        if (this.autoSaveTimer) {
            clearInterval(this.autoSaveTimer);
            this.autoSaveTimer = null;
        }
    }
    /** Quick save (overwriting the single quick save slot) */
    quickSave(payload, location) {
        return this.save('quicksave', payload, { type: 'quick', label: 'Quick Save', location });
    }
    /** Quick load */
    quickLoad() {
        return this.load('quicksave');
    }
    /* ── Cloud Sync ─────────────────────────────────────────────── */
    /** Sync local saves to cloud (stub — needs actual endpoint) */
    async syncToCloud() {
        if (!this.config.cloud.enabled) {
            return { synced: [], conflicts: [] };
        }
        const synced = [];
        const conflicts = [];
        for (const [slotId, data] of this.slots) {
            if (!data.meta.cloudSynced) {
                // In production, this would POST to the cloud endpoint
                // Here we simulate a successful sync
                data.meta.cloudSynced = true;
                data.meta.cloudTimestamp = Date.now();
                synced.push(slotId);
            }
        }
        this.eventBus.emit('save:cloud-sync', { synced, conflicts });
        return { synced, conflicts };
    }
    /** Resolve a cloud sync conflict */
    resolveConflict(conflict, strategy) {
        conflict.resolvedWith = strategy;
        switch (strategy) {
            case 'local-wins':
                return conflict.localMeta;
            case 'remote-wins':
                return conflict.remoteMeta;
            case 'newest-wins':
                return conflict.localMeta.updatedAt >= conflict.remoteMeta.updatedAt
                    ? conflict.localMeta : conflict.remoteMeta;
            default:
                return conflict.localMeta; // merge/prompt fallback
        }
    }
    /* ── Storage Backend ────────────────────────────────────────── */
    persistSlot(slotId, _data, _meta) {
        var _a;
        switch (this.config.storageBackend) {
            case 'localstorage': {
                const key = `${this.config.storageKey}:${slotId}`;
                try {
                    const serialized = JSON.stringify({ meta: _meta, payload: (_a = this.slots.get(slotId)) === null || _a === void 0 ? void 0 : _a.payload });
                    localStorage.setItem(key, serialized);
                }
                catch ( /* quota exceeded — emit event */_b) { /* quota exceeded — emit event */
                    this.eventBus.emit('save:error', { slotId, error: 'Storage quota exceeded' });
                }
                break;
            }
        }
    }
    removeFromStorage(slotId) {
        if (this.config.storageBackend === 'localstorage') {
            localStorage.removeItem(`${this.config.storageKey}:${slotId}`);
        }
    }
    /** Load all slots from storage backend */
    async loadFromStorage() {
        if (this.config.storageBackend === 'localstorage') {
            let count = 0;
            const prefix = `${this.config.storageKey}:`;
            for (let i = 0; i < localStorage.length; i++) {
                const key = localStorage.key(i);
                if (key === null || key === void 0 ? void 0 : key.startsWith(prefix)) {
                    try {
                        const raw = localStorage.getItem(key);
                        if (raw) {
                            const parsed = JSON.parse(raw);
                            this.slots.set(parsed.meta.slotId, parsed);
                            count++;
                        }
                    }
                    catch ( /* corrupt entry — skip */_a) { /* corrupt entry — skip */ }
                }
            }
            return count;
        }
        return 0;
    }
    /** Export all saves as a downloadable blob */
    exportAll() {
        const all = Array.from(this.slots.values());
        const json = JSON.stringify(all);
        return new Blob([json], { type: 'application/json' });
    }
    /** Import saves from a blob */
    async importAll(blob) {
        const text = await blob.text();
        let parsed;
        try {
            parsed = JSON.parse(text);
        }
        catch (_a) {
            return 0;
        }
        if (!Array.isArray(parsed))
            return 0;
        let count = 0;
        for (const entry of parsed) {
            if (entry && typeof entry === 'object' && 'meta' in entry && 'payload' in entry) {
                const saveData = entry;
                this.slots.set(saveData.meta.slotId, saveData);
                count++;
            }
        }
        return count;
    }
    getConfig() {
        return this.config;
    }
    getMigrationEngine() {
        return this.migrationEngine;
    }
    destroy() {
        this.stopAutoSave();
    }
}

exports.DEFAULT_SAVE_CONFIG = DEFAULT_SAVE_CONFIG;
exports.MigrationEngine = MigrationEngine;
exports.SaveSystemV2 = SaveSystemV2;
exports.compressSaveData = compressSaveData;
exports.crc32 = crc32;
exports.decompressSaveData = decompressSaveData;
//# sourceMappingURL=SaveSystemV2.js.map
