/**
 * @typedef {import("./types.js").MissionDefinition} MissionDefinition
 * @typedef {import("./types.js").MissionSaveData} MissionSaveData
 * @typedef {import("./types.js").Objective} Objective
 * @typedef {import("./types.js").GameEventData} GameEventData
 */

import { narrativeManager } from './NarrativeManager.js';

/**
 * MissionManager.js
 * Manages campaign progression, mission selection, narrative triggers, and objective tracking.
 * @class
 */
export class MissionManager {
    /**
     * @param {import("../core/Persistence.js").Persistence} [persistence] - Persistence manager (optional)
     */
    constructor(persistence = null) {
        /** @type {import("../core/Persistence.js").Persistence | null} */
        this.persistence = persistence;
        
        // Campaign State
        /** @type {string | null} */
        this.activeMissionId = null;
        /** @type {Set<string>} */
        this.completedMissions = new Set();
        /** @type {Map<string, MissionDefinition>} */
        this.missionRegistry = new Map();

        // Active Run State
        /** @type {MissionDefinition | null} */
        this.currentMissionDef = null;
        /** @type {Objective[]} */
        this.currentObjectives = [];
        /** @type {Objective[]} */
        this.secondaryObjectives = [];
        /** @type {Array<{type: string; [key: string]: unknown}>} */
        this.failureConditions = [];
        /** @type {UnitManager | null} */
        this.unitManager = null;
        /** @type {TurnSystem | null} */
        this.turnSystem = null;
        /** @type {number} */
        this.currentTurn = 0;

        /** @type {Promise<void> | null} */
        this._missionsLoadPromise = null;
    }

    /**
     * Lazy-loads all mission definitions if not already loaded.
     * @returns {Promise<void>}
     */
    async _ensureMissionsLoaded() {
        if (this._missionsLoadPromise) {
            return this._missionsLoadPromise;
        }

        this._missionsLoadPromise = this._loadMissions();
        return this._missionsLoadPromise;
    }

    /**
     * Loads all mission definitions.
     * @private
     * @returns {Promise<void>}
     */
    async _loadMissions() {
        // Only load if registry is empty (first time)
        if (this.missionRegistry.size > 0) {
            return;
        }

        try {
            const [tutorialMission, story02Mission, story03Mission] = await Promise.all([
                import('../assets/data/missions/mission_tutorial_01.json', { with: { type: 'json' } }).then(m => m.default),
                import('../assets/data/missions/mission_story_02.json', { with: { type: 'json' } }).then(m => m.default),
                import('../assets/data/missions/mission_story_03.json', { with: { type: 'json' } }).then(m => m.default)
            ]);

            this.registerMission(tutorialMission);
            this.registerMission(story02Mission);
            this.registerMission(story03Mission);
        } catch (error) {
            console.error('Failed to load missions:', error);
        }
    }

    /**
     * Registers a mission definition.
     * @param {MissionDefinition} missionDef - Mission definition to register
     */
    registerMission(missionDef) {
        this.missionRegistry.set(missionDef.id, missionDef);
    }

    // --- PERSISTENCE (Campaign) ---

    /**
     * Loads campaign save data.
     * @param {MissionSaveData} saveData - Save data to load
     */
    load(saveData) {
        this.completedMissions = new Set(saveData.completedMissions || []);
        // Default to Tutorial if history is empty
        if (this.completedMissions.size === 0) {
            this.activeMissionId = 'MISSION_TUTORIAL_01';
        }
    }

    /**
     * Saves campaign data.
     * @returns {MissionSaveData} - Serialized campaign data
     */
    save() {
        return {
            completedMissions: Array.from(this.completedMissions)
        };
    }

    // --- MISSION SETUP & NARRATIVE ---

    /**
     * Gets the configuration for the currently selected mission.
     * Ensures missions are loaded before accessing.
     * @returns {Promise<MissionDefinition | undefined>} - Active mission definition
     */
    async getActiveMission() {
        await this._ensureMissionsLoaded();
        if (!this.activeMissionId) return this.missionRegistry.get('MISSION_TUTORIAL_01');
        return this.missionRegistry.get(this.activeMissionId);
    }

    /**
     * Sets the unit manager reference for objective checking.
     * @param {UnitManager} unitManager - Unit manager instance
     */
    setUnitManager(unitManager) {
        this.unitManager = unitManager;
    }

    /**
     * Sets the turn system reference for turn-based objectives.
     * @param {TurnSystem} turnSystem - Turn system instance
     */
    setTurnSystem(turnSystem) {
        this.turnSystem = turnSystem;
    }

    /**
     * Prepares the manager for a new run.
     * Resets objectives and prepares narrative hooks.
     * @returns {Promise<void>}
     */
    async setupActiveMission() {
        await this._ensureMissionsLoaded();
        const mission = await this.getActiveMission();
        this.currentMissionDef = mission;
        this.currentTurn = 0;
        
        // Hydrate primary objectives state
        this.currentObjectives = (mission.objectives.primary || []).map(obj => ({
            ...obj,
            current: 0,
            complete: false
        }));

        // Hydrate secondary objectives state
        this.secondaryObjectives = (mission.objectives.secondary || []).map(obj => ({
            ...obj,
            current: 0,
            complete: false
        }));

        // Store failure conditions
        this.failureConditions = mission.objectives.failure_conditions || [];

        console.log(`Mission Setup: ${mission.config.title} - Primary Objectives:`, this.currentObjectives);
        if (this.secondaryObjectives.length > 0) {
            console.log('Secondary Objectives:', this.secondaryObjectives);
        }
        if (this.failureConditions.length > 0) {
            console.log('Failure Conditions:', this.failureConditions);
        }
    }

    /**
     * Plays the intro narrative if one exists.
     * Returns a Promise that resolves when the game should start.
     */
    async playIntro() {
        if (!this.currentMissionDef || !this.currentMissionDef.narrative || !this.currentMissionDef.narrative.intro_sequence) {
            return Promise.resolve();
        }

        return new Promise(async (resolve) => {
            const introId = this.currentMissionDef.narrative.intro_sequence;
            
            // Map narrative ID to filename
            // NARRATIVE_TUTORIAL_INTRO -> tutorial_intro.json
            const narrativeFileName = this._mapNarrativeIdToFileName(introId);
            
            try {
                // Load the narrative JSON file
                const response = await fetch(`assets/data/narrative/${narrativeFileName}.json`);
                if (!response.ok) {
                    console.error(`Failed to load narrative: ${narrativeFileName}`);
                    resolve();
                    return;
                }
                
                const narrativeData = await response.json();
                
                // Set up listener for narrative end
                const onEnd = () => {
                    narrativeManager.removeEventListener('narrative-end', onEnd);
                    resolve();
                };
                narrativeManager.addEventListener('narrative-end', onEnd);
                
                // Start the narrative sequence
                console.log(`Playing Narrative Intro: ${introId}`);
                narrativeManager.startSequence(narrativeData);
            } catch (error) {
                console.error(`Error loading narrative ${narrativeFileName}:`, error);
                resolve(); // Resolve anyway to not block game start
            }
        });
    }

    /**
     * Maps narrative sequence ID to filename.
     * @param {string} narrativeId - The narrative ID from mission config
     * @returns {string} The filename (without .json extension)
     */
    _mapNarrativeIdToFileName(narrativeId) {
        // Convert NARRATIVE_TUTORIAL_INTRO -> tutorial_intro
        // Remove NARRATIVE_ prefix and convert to lowercase with underscores
        const mapping = {
            'NARRATIVE_TUTORIAL_INTRO': 'tutorial_intro',
            'NARRATIVE_TUTORIAL_SUCCESS': 'tutorial_success',
            'NARRATIVE_ACT1_FINAL_WIN': 'act1_final_win',
            'NARRATIVE_ACT1_FINAL_LOSE': 'act1_final_lose'
        };
        
        return mapping[narrativeId] || narrativeId.toLowerCase().replace('NARRATIVE_', '');
    }

    // --- GAMEPLAY LOGIC (Objectives) ---

    /**
     * Called by GameLoop whenever a relevant event occurs.
     * @param {string} type - 'ENEMY_DEATH', 'TURN_END', 'PLAYER_DEATH', etc.
     * @param {GameEventData} data - Context data
     */
    onGameEvent(type, data) {
        if (!this.currentMissionDef) return;

        // Check failure conditions first
        this.checkFailureConditions(type, data);

        // Update objectives
        let statusChanged = false;

        // Process primary objectives
        statusChanged = this.updateObjectives(this.currentObjectives, type, data) || statusChanged;
        
        // Process secondary objectives
        this.updateObjectives(this.secondaryObjectives, type, data);

        // Check for ELIMINATE_ALL objective completion (needs active check)
        // Check after enemy death or at turn end
        if (type === 'ENEMY_DEATH') {
            statusChanged = this.checkEliminateAllObjective() || statusChanged;
        } else if (type === 'TURN_END') {
            // Also check on turn end in case all enemies died from status effects
            statusChanged = this.checkEliminateAllObjective() || statusChanged;
        }

        if (statusChanged) {
            this.checkVictory();
        }
    }

    /**
     * Updates objectives based on game events.
     * @param {Objective[]} objectives - Objectives to update
     * @param {string} eventType - Event type
     * @param {GameEventData} data - Event data
     * @returns {boolean} True if any objective status changed
     */
    updateObjectives(objectives, eventType, data) {
        let statusChanged = false;

        objectives.forEach(obj => {
            if (obj.complete) return;

            // ELIMINATE_UNIT: Track specific enemy deaths
            if (eventType === 'ENEMY_DEATH' && obj.type === 'ELIMINATE_UNIT') {
                if (data.unitId === obj.target_def_id || data.defId === obj.target_def_id) {
                    obj.current = (obj.current || 0) + 1;
                    if (obj.target_count && obj.current >= obj.target_count) {
                        obj.complete = true;
                        statusChanged = true;
                    }
                }
            }

            // SURVIVE: Check turn count
            if (eventType === 'TURN_END' && obj.type === 'SURVIVE') {
                if (obj.turn_count && this.currentTurn >= obj.turn_count) {
                    obj.complete = true;
                    statusChanged = true;
                }
            }

            // REACH_ZONE: Check if unit reached target zone
            if (eventType === 'UNIT_MOVE' && obj.type === 'REACH_ZONE') {
                if (data.position && obj.zone_coords) {
                    const reached = obj.zone_coords.some(coord => 
                        coord.x === data.position.x &&
                        coord.y === data.position.y &&
                        coord.z === data.position.z
                    );
                    if (reached) {
                        obj.complete = true;
                        statusChanged = true;
                    }
                }
            }

            // INTERACT: Check if unit interacted with target object
            if (eventType === 'INTERACT' && obj.type === 'INTERACT') {
                if (data.objectId === obj.target_object_id) {
                    obj.complete = true;
                    statusChanged = true;
                }
            }

            // SQUAD_SURVIVAL: Check if minimum units are alive
            if (eventType === 'PLAYER_DEATH' && obj.type === 'SQUAD_SURVIVAL') {
                if (this.unitManager) {
                    const playerUnits = Array.from(this.unitManager.activeUnits.values())
                        .filter(u => u.team === 'PLAYER' && u.currentHealth > 0);
                    if (obj.min_alive && playerUnits.length >= obj.min_alive) {
                        obj.complete = true;
                        statusChanged = true;
                    }
                }
            }
        });

        return statusChanged;
    }

    /**
     * Checks if ELIMINATE_ALL objective is complete.
     * @returns {boolean} True if status changed
     */
    checkEliminateAllObjective() {
        let statusChanged = false;

        [...this.currentObjectives, ...this.secondaryObjectives].forEach(obj => {
            if (obj.complete || obj.type !== 'ELIMINATE_ALL') return;

            if (this.unitManager) {
                const enemies = Array.from(this.unitManager.activeUnits.values())
                    .filter(u => u.team === 'ENEMY' && u.currentHealth > 0);
                
                if (enemies.length === 0) {
                    obj.complete = true;
                    statusChanged = true;
                }
            }
        });

        return statusChanged;
    }

    /**
     * Checks failure conditions and triggers failure if met.
     * @param {string} eventType - Event type
     * @param {GameEventData} data - Event data
     */
    checkFailureConditions(eventType, data) {
        if (!this.failureConditions.length) return;

        for (const condition of this.failureConditions) {
            // SQUAD_WIPE: All player units are dead
            if (condition.type === 'SQUAD_WIPE') {
                if (this.unitManager) {
                    const playerUnits = Array.from(this.unitManager.activeUnits.values())
                        .filter(u => u.team === 'PLAYER' && u.currentHealth > 0);
                    if (playerUnits.length === 0) {
                        this.triggerFailure('SQUAD_WIPE');
                        return;
                    }
                }
            }

            // VIP_DEATH: VIP unit died
            if (condition.type === 'VIP_DEATH' && eventType === 'PLAYER_DEATH') {
                if (data.unitId && condition.target_tag) {
                    const unit = this.unitManager?.getUnitById(data.unitId);
                    if (unit && unit.tags && unit.tags.includes(condition.target_tag)) {
                        this.triggerFailure('VIP_DEATH', { unitId: data.unitId });
                        return;
                    }
                }
            }

            // TURN_LIMIT_EXCEEDED: Mission took too long
            if (condition.type === 'TURN_LIMIT_EXCEEDED' && eventType === 'TURN_END') {
                if (condition.turn_limit && this.currentTurn > condition.turn_limit) {
                    this.triggerFailure('TURN_LIMIT_EXCEEDED', { turn: this.currentTurn });
                    return;
                }
            }
        }
    }

    /**
     * Triggers mission failure.
     * @param {string} reason - Failure reason
     * @param {GameEventData} [data] - Additional failure data
     */
    triggerFailure(reason, data = {}) {
        console.log(`MISSION FAILED: ${reason}`, data);
        window.dispatchEvent(new CustomEvent('mission-failure', { 
            detail: { 
                missionId: this.activeMissionId,
                reason: reason,
                ...data
            } 
        }));
    }

    checkVictory() {
        const allPrimaryComplete = this.currentObjectives.length > 0 && 
            this.currentObjectives.every(o => o.complete);
        if (allPrimaryComplete) {
            console.log("VICTORY! Mission Objectives Complete.");
            this.completeActiveMission();
            // Dispatch event for GameLoop to handle Victory Screen
            window.dispatchEvent(new CustomEvent('mission-victory', { 
                detail: { 
                    missionId: this.activeMissionId,
                    primaryObjectives: this.currentObjectives,
                    secondaryObjectives: this.secondaryObjectives
                } 
            }));
        }
    }

    /**
     * Completes the active mission and distributes rewards.
     */
    async completeActiveMission() {
        if (!this.activeMissionId || !this.currentMissionDef) return;

        // Mark mission as completed
        this.completedMissions.add(this.activeMissionId);
        console.log("MissionManager: Mission completed. Active mission ID:", this.activeMissionId);
        console.log("MissionManager: Completed missions now:", Array.from(this.completedMissions));

        // Dispatch event to save campaign data IMMEDIATELY (before outro)
        // This ensures the save happens even if the outro doesn't complete
        console.log("MissionManager: Dispatching campaign-data-changed event");
        window.dispatchEvent(new CustomEvent('campaign-data-changed', {
            detail: { missionCompleted: this.activeMissionId }
        }));
        console.log("MissionManager: campaign-data-changed event dispatched");

        // Distribute rewards
        this.distributeRewards();

        // Play outro narrative if available (after saving)
        if (this.currentMissionDef.narrative?.outro_success) {
            await this.playOutro(this.currentMissionDef.narrative.outro_success);
        }
    }

    /**
     * Distributes mission rewards (XP, currency, items, unlocks).
     */
    distributeRewards() {
        if (!this.currentMissionDef || !this.currentMissionDef.rewards) return;

        const rewards = this.currentMissionDef.rewards;
        const rewardData = {
            xp: 0,
            currency: {},
            items: [],
            unlocks: [],
            factionReputation: {}
        };

        // Guaranteed rewards
        if (rewards.guaranteed) {
            if (rewards.guaranteed.xp) {
                rewardData.xp += rewards.guaranteed.xp;
            }
            if (rewards.guaranteed.currency) {
                Object.assign(rewardData.currency, rewards.guaranteed.currency);
            }
            if (rewards.guaranteed.items) {
                rewardData.items.push(...rewards.guaranteed.items);
            }
            if (rewards.guaranteed.unlocks) {
                rewardData.unlocks.push(...rewards.guaranteed.unlocks);
            }
        }

        // Conditional rewards (based on secondary objectives)
        if (rewards.conditional) {
            rewards.conditional.forEach(conditional => {
                const objective = this.secondaryObjectives.find(obj => obj.id === conditional.objective_id);
                if (objective && objective.complete && conditional.reward) {
                    if (conditional.reward.xp) {
                        rewardData.xp += conditional.reward.xp;
                    }
                    if (conditional.reward.currency) {
                        Object.assign(rewardData.currency, conditional.reward.currency);
                    }
                    if (conditional.reward.items) {
                        rewardData.items.push(...conditional.reward.items);
                    }
                }
            });
        }

        // Faction reputation
        if (rewards.faction_reputation) {
            Object.assign(rewardData.factionReputation, rewards.faction_reputation);
        }

        // Dispatch reward event
        window.dispatchEvent(new CustomEvent('mission-rewards', { 
            detail: rewardData 
        }));

        // Handle unlocks (store in localStorage)
        if (rewardData.unlocks.length > 0) {
            this.unlockClasses(rewardData.unlocks);
        }

        console.log('Mission Rewards Distributed:', rewardData);
    }

    /**
     * Unlocks classes and stores them in IndexedDB via Persistence.
     * @param {string[]} classIds - Array of class IDs to unlock
     */
    async unlockClasses(classIds) {
        let unlocks = [];
        
        try {
            // Load from IndexedDB
            if (this.persistence) {
                unlocks = await this.persistence.loadUnlocks();
            } else {
                // Fallback: try localStorage migration
                const stored = localStorage.getItem('aether_shards_unlocks');
                if (stored) {
                    unlocks = JSON.parse(stored);
                }
            }
        } catch (e) {
            console.error('Failed to load unlocks from storage:', e);
        }

        // Add new unlocks
        classIds.forEach(classId => {
            if (!unlocks.includes(classId)) {
                unlocks.push(classId);
            }
        });

        // Save back to IndexedDB
        try {
            if (this.persistence) {
                await this.persistence.saveUnlocks(unlocks);
                console.log('Unlocked classes:', classIds);
                
                // Migrate from localStorage if it exists
                if (localStorage.getItem('aether_shards_unlocks')) {
                    localStorage.removeItem('aether_shards_unlocks');
                    console.log('Migrated unlocks from localStorage to IndexedDB');
                }
            } else {
                // Fallback to localStorage if persistence not available
                localStorage.setItem('aether_shards_unlocks', JSON.stringify(unlocks));
                console.log('Unlocked classes (localStorage fallback):', classIds);
            }
            
            // Dispatch event so UI components can refresh
            window.dispatchEvent(new CustomEvent('classes-unlocked', {
                detail: { unlockedClasses: classIds, allUnlocks: unlocks }
            }));
        } catch (e) {
            console.error('Failed to save unlocks to storage:', e);
        }
    }

    /**
     * Plays the outro narrative if one exists.
     * @param {string} outroId - Narrative sequence ID
     * @returns {Promise<void>}
     */
    async playOutro(outroId) {
        return new Promise(async (resolve) => {
            const narrativeFileName = this._mapNarrativeIdToFileName(outroId);
            
            try {
                const response = await fetch(`assets/data/narrative/${narrativeFileName}.json`);
                if (!response.ok) {
                    console.error(`Failed to load outro narrative: ${narrativeFileName}`);
                    resolve();
                    return;
                }
                
                const narrativeData = await response.json();
                
                const onEnd = () => {
                    narrativeManager.removeEventListener('narrative-end', onEnd);
                    resolve();
                };
                narrativeManager.addEventListener('narrative-end', onEnd);
                
                console.log(`Playing Narrative Outro: ${outroId}`);
                narrativeManager.startSequence(narrativeData);
            } catch (error) {
                console.error(`Error loading outro narrative ${narrativeFileName}:`, error);
                resolve();
            }
        });
    }

    /**
     * Updates the current turn count.
     * @param {number} turn - Current turn number
     */
    updateTurn(turn) {
        this.currentTurn = turn;
    }
}