/**
 * Save Game Editor - Public API
 *
 * High-level functions for managing LEGO Island save files
 */

// Re-export format utilities (from formats/)
export { BinaryReader, BinaryWriter } from '../formats/index.js';
export { SaveGameParser, parseSaveGame } from '../formats/index.js';
export { SaveGameSerializer, createSerializer } from '../formats/index.js';
export { PlayersParser, parsePlayers } from '../formats/index.js';
export { PlayersSerializer, createPlayersSerializer } from '../formats/index.js';
export * from './constants.js';
export * from './colorUtils.js';

// Import dependencies
import { readBinaryFile, writeBinaryFile, fileExists, listFiles } from '../opfs.js';
import { parseSaveGame, parsePlayers, createSerializer, createPlayersSerializer } from '../formats/index.js';
import { getSaveFileName, PLAYERS_FILE, Actor, ActorNames } from './constants.js';

/**
 * @typedef {Object} SaveSlot
 * @property {number} slotNumber - Slot number (0-9)
 * @property {boolean} exists - Whether the save file exists
 * @property {string} fileName - Save file name
 * @property {Object|null} header - Parsed header data
 * @property {Object|null} missions - Mission scores
 * @property {Map|null} variables - Parsed variables (name -> { value, nameOffset, valueOffset, valueLength })
 * @property {string|null} playerName - Player name from Players.gsi
 * @property {ArrayBuffer|null} buffer - Raw file buffer (for editing)
 */

/**
 * Cache for player names (avoids re-parsing Players.gsi)
 */
let playersCache = null;

/**
 * Load and cache player names from Players.gsi
 * @returns {Promise<{ count: number, players: Array<{ letters: number[], name: string }> }|null>}
 */
async function loadPlayers() {
    if (playersCache) return playersCache;

    const buffer = await readBinaryFile(PLAYERS_FILE);
    if (!buffer) return null;

    playersCache = parsePlayers(buffer);
    return playersCache;
}

/**
 * Clear the players cache (call when Players.gsi changes)
 */
export function clearPlayersCache() {
    playersCache = null;
}

/**
 * Get player name by index
 * @param {number} index - Player index in Players.gsi
 * @returns {Promise<string|null>}
 */
async function getPlayerName(index) {
    const players = await loadPlayers();
    if (!players || index < 0 || index >= players.players.length) {
        return null;
    }
    return players.players[index].name;
}

/**
 * List all save slots (0-9) with basic info
 * @returns {Promise<SaveSlot[]>}
 */
export async function listSaveSlots() {
    const slots = [];

    // First load players to resolve names
    await loadPlayers();

    for (let i = 0; i < 10; i++) {
        const fileName = getSaveFileName(i);
        const exists = await fileExists(fileName);

        const slot = {
            slotNumber: i,
            exists,
            fileName,
            header: null,
            missions: null,
            variables: null,
            act1State: null,
            characters: null,
            charactersOffset: null,
            plants: null,
            plantsOffset: null,
            buildings: null,
            buildingsOffset: null,
            nextVariant: null,
            nextVariantOffset: null,
            playerName: null,
            buffer: null
        };

        if (exists) {
            try {
                const buffer = await readBinaryFile(fileName);
                if (buffer) {
                    const parsed = parseSaveGame(buffer);
                    slot.header = parsed.header;
                    slot.missions = parsed.missions;
                    slot.variables = parsed.variables;
                    slot.act1State = parsed.act1State || null;
                    slot.characters = parsed.characters || null;
                    slot.charactersOffset = parsed.charactersOffset || null;
                    slot.plants = parsed.plants || null;
                    slot.plantsOffset = parsed.plantsOffset || null;
                    slot.buildings = parsed.buildings || null;
                    slot.buildingsOffset = parsed.buildingsOffset || null;
                    slot.nextVariant = parsed.nextVariant ?? null;
                    slot.nextVariantOffset = parsed.nextVariantOffset || null;
                    slot.buffer = buffer;

                    // Try to get player name
                    // The playerId in the save file corresponds to an index in Players.gsi
                    // Actually, we need to match by looking at order - player names are stored
                    // in order of most recently played, so we just use the index
                    if (playersCache && i < playersCache.players.length && playersCache.players[i]) {
                        slot.playerName = playersCache.players[i].name;
                    }
                }
            } catch (e) {
                console.error(`Failed to parse save slot ${i}:`, e);
            }
        }

        slots.push(slot);
    }

    return slots;
}

/**
 * Load a specific save slot with full details
 * @param {number} slotNumber - Slot number (0-9)
 * @returns {Promise<SaveSlot|null>}
 */
export async function loadSaveSlot(slotNumber) {
    if (slotNumber < 0 || slotNumber > 9) {
        throw new Error('Invalid slot number');
    }

    const fileName = getSaveFileName(slotNumber);
    const buffer = await readBinaryFile(fileName);

    if (!buffer) {
        return null;
    }

    const parsed = parseSaveGame(buffer);
    const players = await loadPlayers();

    let playerName = null;
    if (players && slotNumber < players.players.length && players.players[slotNumber]) {
        playerName = players.players[slotNumber].name;
    }

    return {
        slotNumber,
        exists: true,
        fileName,
        header: parsed.header,
        missions: parsed.missions,
        variables: parsed.variables,
        act1State: parsed.act1State || null,
        characters: parsed.characters || null,
        charactersOffset: parsed.charactersOffset || null,
        plants: parsed.plants || null,
        plantsOffset: parsed.plantsOffset || null,
        buildings: parsed.buildings || null,
        buildingsOffset: parsed.buildingsOffset || null,
        nextVariant: parsed.nextVariant ?? null,
        nextVariantOffset: parsed.nextVariantOffset || null,
        playerName,
        buffer
    };
}

/**
 * Save changes to a save slot
 * @param {number} slotNumber - Slot number (0-9)
 * @param {ArrayBuffer} buffer - Modified buffer to save
 * @param {boolean} [silent=false] - If true, don't show toast
 * @returns {Promise<boolean>}
 */
export async function saveSaveSlot(slotNumber, buffer, silent = false) {
    if (slotNumber < 0 || slotNumber > 9) {
        throw new Error('Invalid slot number');
    }

    const fileName = getSaveFileName(slotNumber);
    const success = await writeBinaryFile(fileName, buffer, silent, 'Save updated');
    if (success) {
        window.dispatchEvent(new CustomEvent('opfs-save-slot-written', {
            detail: { slot: slotNumber }
        }));
    }
    return success;
}

/**
 * Update a save slot with specific changes
 * @param {number} slotNumber - Slot number (0-9)
 * @param {Object} updates - Updates to apply
 * @param {Object} [updates.header] - Header updates (currentAct, actorId)
 * @param {Object} [updates.missionScore] - Single mission score update
 * @returns {Promise<SaveSlot|null>} - Updated slot data
 */
export async function updateSaveSlot(slotNumber, updates) {
    const slot = await loadSaveSlot(slotNumber);
    if (!slot || !slot.buffer) {
        console.error('Failed to load save slot');
        return null;
    }

    const serializer = createSerializer(slot.buffer);
    let newBuffer = slot.buffer;
    let modified = false;

    // Apply header updates
    if (updates.header) {
        newBuffer = serializer.updateHeader(updates.header);
        modified = true;
    }

    // Apply mission score update
    if (updates.missionScore) {
        const { missionType, actorId, scoreType, value } = updates.missionScore;
        // Create new serializer with current buffer state
        const scoreSerializer = createSerializer(newBuffer);
        // updateMissionScore will add missing states automatically
        const result = scoreSerializer.updateMissionScore(missionType, actorId, scoreType, value);
        if (result) {
            newBuffer = result;
            modified = true;
        }
    }

    // Apply variable update
    if (updates.variable) {
        const { name, value } = updates.variable;
        const varSerializer = createSerializer(newBuffer);
        const result = varSerializer.updateVariable(name, value);
        if (result) {
            newBuffer = result;
            modified = true;
        }
    }

    // Apply character update(s)
    if (updates.character) {
        const entries = Array.isArray(updates.character) ? updates.character : [updates.character];
        for (const { characterIndex, field, value } of entries) {
            const charSerializer = createSerializer(newBuffer);
            const result = charSerializer.updateCharacter(characterIndex, field, value);
            if (result) {
                newBuffer = result;
                modified = true;
            }
        }
    }

    // Apply plant update(s)
    if (updates.plant) {
        const entries = Array.isArray(updates.plant) ? updates.plant : [updates.plant];
        for (const { plantIndex, field, value } of entries) {
            const plantSerializer = createSerializer(newBuffer);
            const result = plantSerializer.updatePlant(plantIndex, field, value);
            if (result) {
                newBuffer = result;
                modified = true;
            }
        }
    }

    // Apply building update(s)
    if (updates.building) {
        const entries = Array.isArray(updates.building) ? updates.building : [updates.building];
        for (const { buildingIndex, field, value } of entries) {
            const buildingSerializer = createSerializer(newBuffer);
            const result = buildingSerializer.updateBuilding(buildingIndex, field, value);
            if (result) {
                newBuffer = result;
                modified = true;
            }
        }
    }

    // Apply nextVariant update
    if (updates.nextVariant !== undefined) {
        const variantSerializer = createSerializer(newBuffer);
        const result = variantSerializer.updateNextVariant(updates.nextVariant);
        if (result) {
            newBuffer = result;
            modified = true;
        }
    }

    // Apply texture update
    if (updates.texture) {
        const { textureName, textureData } = updates.texture;
        const texSerializer = createSerializer(newBuffer);
        const result = texSerializer.updateAct1Texture(textureName, textureData);
        if (result) {
            newBuffer = result;
            modified = true;
        }
    }

    // Only save if something was actually modified
    if (!modified) {
        return slot;
    }

    // Save the modified buffer
    const success = await saveSaveSlot(slotNumber, newBuffer);
    if (!success) {
        console.error('Failed to save slot');
        return null;
    }

    // Re-load and return updated slot
    return await loadSaveSlot(slotNumber);
}

/**
 * Get all existing save slots (convenience function)
 * @returns {Promise<SaveSlot[]>}
 */
export async function getExistingSaveSlots() {
    const allSlots = await listSaveSlots();
    return allSlots.filter(slot => slot.exists);
}

/**
 * Update a player's name in Players.gsi
 * @param {number} playerIndex - Index in Players.gsi (0-8)
 * @param {string} newName - New name (max 7 chars, A-Z only)
 * @returns {Promise<boolean>} - True if successful
 */
export async function updatePlayerName(playerIndex, newName) {
    // Load current players data
    const players = await loadPlayers();
    if (!players) {
        console.error('Failed to load Players.gsi');
        return false;
    }

    if (playerIndex < 0 || playerIndex >= players.players.length) {
        console.error('Invalid player index');
        return false;
    }

    // Create serializer and update name
    const serializer = createPlayersSerializer(players);
    serializer.updateName(playerIndex, newName);

    // Serialize and save
    const buffer = serializer.serialize();
    const success = await writeBinaryFile(PLAYERS_FILE, buffer, false, 'Save updated');

    if (success) {
        // Clear cache so next load gets fresh data
        clearPlayersCache();
        window.dispatchEvent(new CustomEvent('opfs-save-file-written', {
            detail: { filename: PLAYERS_FILE }
        }));
    }

    return success;
}
