/**
 * @typedef {import("./types.js").GameState} GameState
 * @typedef {import("./types.js").RunData} RunData
 * @typedef {import("./types.js").EmbarkEventDetail} EmbarkEventDetail
 * @typedef {import("./types.js").SquadMember} SquadMember
 * @typedef {import("../ui/combat-hud.d.ts").CombatState} CombatState
 */

import { Persistence } from "./Persistence.js";
import { RosterManager } from "../managers/RosterManager.js";
import { MissionManager } from "../managers/MissionManager.js";
import { narrativeManager } from "../managers/NarrativeManager.js";
import { MarketManager } from "../managers/MarketManager.js";
import { ResearchManager } from "../managers/ResearchManager.js";
import { InventoryManager } from "../managers/InventoryManager.js";
import { InventoryContainer } from "../models/InventoryContainer.js";
import { itemRegistry } from "../managers/ItemRegistry.js";

/**
 * Manages the overall game state and transitions between different game modes.
 * @class
 */
class GameStateManagerClass {
  /** @type {Record<string, GameState>} */
  static STATES = {
    INIT: "STATE_INIT",
    MAIN_MENU: "STATE_MAIN_MENU",
    TEAM_BUILDER: "STATE_TEAM_BUILDER",
    DEPLOYMENT: "STATE_DEPLOYMENT",
    COMBAT: "STATE_COMBAT",
  };

  /**
   * @param {GameState} currentState - Current game state
   * @param {import("./GameLoop.js").GameLoop | null} gameLoop - Reference to game loop
   * @param {Persistence} persistence - Persistence manager
   * @param {RunData | null} activeRunData - Current active run data
   * @param {RosterManager} rosterManager - Roster manager instance
   * @param {MissionManager} missionManager - Mission manager instance
   * @param {import("../managers/NarrativeManager.js").NarrativeManager} narrativeManager - Narrative manager instance
   */
  constructor() {
    /** @type {GameState} */
    this.currentState = GameStateManagerClass.STATES.INIT;
    /** @type {import("./GameLoop.js").GameLoop | null} */
    this.gameLoop = null;
    /** @type {Persistence} */
    this.persistence = new Persistence();
    /** @type {RunData | null} */
    this.activeRunData = null;
    /** @type {CombatState | null} */
    this.combatState = null;

    // Integrate Core Managers
    /** @type {RosterManager} */
    this.rosterManager = new RosterManager();
    /** @type {MissionManager} */
    this.missionManager = new MissionManager(this.persistence);
    /** @type {import("../managers/NarrativeManager.js").NarrativeManager} */
    this.narrativeManager = narrativeManager; // Track the singleton instance

    // Create persistent Hub inventory (separate from GameLoop's run stash)
    /** @type {InventoryContainer} */
    this.hubStash = new InventoryContainer("HUB_VAULT");
    /** @type {InventoryManager} */
    this.hubInventoryManager = new InventoryManager(
      itemRegistry,
      null, // No run stash in hub context
      this.hubStash
    );
    /** @type {MarketManager} */
    this.marketManager = new MarketManager(
      this.persistence,
      itemRegistry,
      this.hubInventoryManager,
      this.missionManager
    );
    /** @type {ResearchManager} */
    this.researchManager = new ResearchManager(
      this.persistence,
      this.rosterManager
    );

    this.handleEmbark = this.handleEmbark.bind(this);
  }

  /** @type {PromiseWithResolvers<void>} */
  #gameLoopInitialized = Promise.withResolvers();

  /**
   * @returns {Promise<void>}
   */
  get gameLoopInitialized() {
    return this.#gameLoopInitialized.promise;
  }

  /** @type {PromiseWithResolvers<unknown>} */
  #rosterLoaded = Promise.withResolvers();

  /**
   * @returns {Promise<unknown>}
   */
  get rosterLoaded() {
    return this.#rosterLoaded.promise;
  }

  /**
   * Sets the game loop reference.
   * @param {import("./GameLoop.js").GameLoop} loop - The game loop instance
   */
  setGameLoop(loop) {
    this.gameLoop = loop;
    this.#gameLoopInitialized.resolve();

    // Update debug commands reference
    if (typeof window !== "undefined" && window.debugCommands) {
      window.debugCommands.setGameLoop(loop);
    }
  }

  /**
   * Resets the state manager to initial state.
   */
  reset() {
    // Reset singleton state for testing
    this.currentState = GameStateManagerClass.STATES.INIT;
    this.gameLoop = null;
    this.activeRunData = null;
    this.combatState = null;
    this.rosterManager = new RosterManager();
    this.missionManager = new MissionManager(this.persistence);
    // Recreate hub inventory and market manager
    this.hubStash = new InventoryContainer("HUB_VAULT");
    this.hubInventoryManager = new InventoryManager(
      itemRegistry,
      null,
      this.hubStash
    );
    this.marketManager = new MarketManager(
      this.persistence,
      itemRegistry,
      this.hubInventoryManager,
      this.missionManager
    );
    // Reset promise resolvers
    this.#gameLoopInitialized = Promise.withResolvers();
    this.#rosterLoaded = Promise.withResolvers();
  }

  /**
   * Initializes the game state manager.
   * @returns {Promise<void>}
   */
  async init() {
    console.log("System: Initializing State Manager...");
    await this.persistence.init();

    // 1. Load Roster
    const savedRoster = await this.persistence.loadRoster();
    if (savedRoster) {
      this.rosterManager.load(savedRoster);
      this.#rosterLoaded.resolve(this.rosterManager.roster);
    }

    // 2. Load Hub Stash
    await this._loadHubStash();

    // 3. Initialize Market Manager
    await this.marketManager.init();

    // 4. Initialize Research Manager
    await this.researchManager.load();

    // 5. Load Campaign Progress
    const savedCampaignData = await this.persistence.loadCampaign();
    console.log("Loaded campaign data:", savedCampaignData);
    if (savedCampaignData) {
      this.missionManager.load(savedCampaignData);
      console.log(
        "Campaign data loaded. Completed missions:",
        Array.from(this.missionManager.completedMissions)
      );
    } else {
      console.log("No saved campaign data found");
    }

    // 6. Set up mission rewards listener
    this._setupMissionRewardsListener();

    // 7. Set up campaign data change listener
    this._setupCampaignDataListener();

    this.transitionTo(GameStateManagerClass.STATES.MAIN_MENU);
  }

  /**
   * Transitions to a new game state.
   * @param {GameState} newState - The new state to transition to
   * @param {unknown} [payload] - Optional payload data for the transition
   * @returns {Promise<void>}
   */
  async transitionTo(newState, payload = null) {
    const oldState = this.currentState;
    const stateChanged = oldState !== newState;

    // Only update state and run switch logic if state actually changed
    if (stateChanged) {
      console.log(`State Transition: ${oldState} -> ${newState}`);
      this.currentState = newState;
    }

    window.dispatchEvent(
      new CustomEvent("gamestate-changed", {
        detail: { oldState, newState, payload },
      })
    );

    // Only run state-specific logic if state actually changed
    if (stateChanged) {
      switch (newState) {
        case GameStateManagerClass.STATES.MAIN_MENU:
          if (this.gameLoop) this.gameLoop.stop();
          // Check if market needs refresh when entering hub
          await this.marketManager.checkRefresh();
          await this._checkSaveGame();
          break;

        case GameStateManagerClass.STATES.DEPLOYMENT:
          // Clear combat state when leaving combat
          this.combatState = null;
          break;

        case GameStateManagerClass.STATES.COMBAT:
          // Combat state will be managed by GameLoop via setCombatState()
          break;
      }
    }
  }

  /**
   * Starts a new game session.
   */
  startNewGame() {
    // Clear roster for a fresh start
    this.rosterManager.clear();
    // Clear any active run data
    this.activeRunData = null;
    // Save the cleared roster
    this._saveRoster();
    this.transitionTo(GameStateManagerClass.STATES.TEAM_BUILDER);
  }

  /**
   * Continues a previously saved game.
   * Checks for active run first, then campaign progress (roster/missions).
   * @returns {Promise<void>}
   */
  async continueGame() {
    const save = await this.persistence.loadRun();
    if (save) {
      // If there's an active run, resume it
      this.activeRunData = save;
      // Will transition to DEPLOYMENT after run is initialized
      await this._resumeRun();
    } else {
      // Check if there's campaign progress (roster or completed missions)
      const hasRoster = this.rosterManager.roster.length > 0;
      const hasCompletedMissions =
        this.missionManager.completedMissions.size > 0;

      if (hasRoster || hasCompletedMissions) {
        // Go to Hub to continue campaign
        this.transitionTo(GameStateManagerClass.STATES.MAIN_MENU);
      }
      // Otherwise, stay on main menu (no campaign progress yet)
    }
  }

  /**
   * Handles the embark event from the team builder.
   * @param {CustomEvent<EmbarkEventDetail>} e - The embark event
   * @returns {Promise<void>}
   */
  async handleEmbark(e) {
    // Handle Draft Mode (New Recruits)
    let squadManifest = e.detail.squad;
    if (e.detail.mode === "DRAFT") {
      // Update squad manifest with IDs from recruited units
      squadManifest = await Promise.all(
        e.detail.squad.map(async (unit) => {
          if (unit.isNew) {
            const recruitedUnit = await this.rosterManager.recruitUnit(unit);
            if (recruitedUnit) {
              // Return the recruited unit with its generated ID
              return recruitedUnit;
            }
          } else if (!unit.id) {
            // For existing units without IDs, look them up in the roster
            const rosterUnit = this.rosterManager.roster.find(
              (r) => r.classId === unit.classId && r.name === unit.name
            );
            if (rosterUnit) {
              return { ...unit, id: rosterUnit.id };
            }
          }
          return unit;
        })
      );
      this._saveRoster();
    } else {
      // For non-draft mode, ensure all units have IDs from roster
      squadManifest = e.detail.squad.map((unit) => {
        if (!unit.id) {
          const rosterUnit = this.rosterManager.roster.find(
            (r) => r.classId === unit.classId && r.name === unit.name
          );
          if (rosterUnit) {
            return { ...unit, id: rosterUnit.id };
          }
        }
        return unit;
      });
    }
    // We must transition to deployment before initializing the run so that the game loop gets set.
    this.transitionTo(GameStateManagerClass.STATES.DEPLOYMENT);
    // Will transition to DEPLOYMENT after run is initialized
    await this._initializeRun(squadManifest);
  }

  // --- INTERNAL HELPERS ---

  /**
   * Initializes a new run with the given squad.
   * @param {SquadMember[]} squadManifest - The squad members to deploy
   * @returns {Promise<void>}
   * @private
   */
  async _initializeRun(squadManifest) {
    await this.gameLoopInitialized;

    // 1. Mission Logic: Setup
    // This resets objectives and prepares the logic for the new run
    await this.missionManager.setupActiveMission();
    const missionDef = await this.missionManager.getActiveMission();

    console.log(`Initializing Run for Mission: ${missionDef.config.title}`);

    // 2. Mission Logic: Narrative Intro
    // If the mission has an intro, play it now.
    // The game loop won't start until this promise resolves (or we could start it paused).
    // This relies on the MissionManager internally calling narrativeManager.startSequence()
    await this.missionManager.playIntro();

    // 3. Build Run Data
    this.activeRunData = {
      id: `RUN_${Date.now()}`,
      missionId: missionDef.id,
      seed:
        missionDef.biome.generator_config.seed_type === "FIXED"
          ? missionDef.biome.generator_config.seed
          : Math.floor(Math.random() * 999999),
      depth: 1,
      biome: missionDef.biome, // Pass biome config to GameLoop
      squad: squadManifest,
      objectives: missionDef.objectives, // Pass objectives for UI display
      world_state: {},
      // Include inventory data from run stash
      inventory: this.gameLoop.inventoryManager
        ? {
            runStash: {
              id: this.gameLoop.inventoryManager.runStash.id,
              items: this.gameLoop.inventoryManager.runStash.getAllItems(),
              currency: {
                aetherShards:
                  this.gameLoop.inventoryManager.runStash.currency
                    ?.aetherShards || 0,
                ancientCores:
                  this.gameLoop.inventoryManager.runStash.currency
                    ?.ancientCores || 0,
              },
            },
          }
        : undefined,
    };

    // 4. Save & Start
    await this.persistence.saveRun(this.activeRunData);

    // Notify UI that run data (including squad) has been updated
    window.dispatchEvent(
      new CustomEvent("run-data-updated", {
        detail: { runData: this.activeRunData },
      })
    );

    // Pass the Mission Manager to the Game Loop so it can report events (Deaths, etc)
    this.gameLoop.missionManager = this.missionManager;
    // Give GameLoop a reference to GameStateManager so it can notify about state changes
    this.gameLoop.gameStateManager = this;
    await this.gameLoop.startLevel(this.activeRunData);
  }

  /**
   * Resumes a previously saved run.
   * @returns {Promise<void>}
   * @private
   */
  async _resumeRun() {
    await this.gameLoopInitialized;
    if (this.activeRunData) {
      // Re-hook the mission manager
      this.gameLoop.missionManager = this.missionManager;
      // Give GameLoop a reference to GameStateManager so it can notify about state changes
      this.gameLoop.gameStateManager = this;
      // TODO: Ideally we reload the mission state from the save file here
      await this.gameLoop.startLevel(this.activeRunData);
      // Transition to appropriate state based on save data
      // For now, always go to deployment
      this.transitionTo(GameStateManagerClass.STATES.DEPLOYMENT);
    }
  }

  /**
   * Checks if a save game exists and dispatches an event.
   * @returns {Promise<void>}
   * @private
   */
  async _checkSaveGame() {
    const save = await this.persistence.loadRun();
    window.dispatchEvent(
      new CustomEvent("save-check-complete", { detail: { hasSave: !!save } })
    );
  }

  /**
   * Saves the current roster.
   * @returns {Promise<void>}
   * @private
   */
  async _saveRoster() {
    const data = this.rosterManager.save();
    await this.persistence.saveRoster(data);
  }

  /**
   * Saves campaign data (completed missions) to persistence.
   * @private
   */
  async _saveCampaign() {
    try {
      const data = this.missionManager.save();
      console.log("GameStateManager: Saving campaign data:", data);
      console.log(
        "GameStateManager: Completed missions count:",
        this.missionManager.completedMissions.size
      );
      console.log(
        "GameStateManager: Completed missions array:",
        Array.from(this.missionManager.completedMissions)
      );
      if (
        !data ||
        !data.completedMissions ||
        data.completedMissions.length === 0
      ) {
        console.warn(
          "GameStateManager: Warning - no completed missions to save!"
        );
      }
      await this.persistence.saveCampaign(data);
      console.log("GameStateManager: Campaign data saved successfully");
    } catch (error) {
      console.error("GameStateManager: Error saving campaign data:", error);
      throw error;
    }
  }

  /**
   * Clears the active run from memory and persistence.
   * Called when a mission is completed or failed.
   * @returns {Promise<void>}
   */
  async clearActiveRun() {
    this.activeRunData = null;
    await this.persistence.clearRun();
  }

  /**
   * Sets the current combat state.
   * Called by GameLoop when combat state changes.
   * @param {CombatState | null} combatState - The new combat state
   */
  setCombatState(combatState) {
    this.combatState = combatState;
    // Dispatch event so UI components can react to combat state changes
    window.dispatchEvent(
      new CustomEvent("combat-state-changed", {
        detail: { combatState },
      })
    );
  }

  /**
   * Gets the current combat state.
   * @returns {CombatState | null} The current combat state
   */
  getCombatState() {
    return this.combatState;
  }

  /**
   * Sets up listener for mission rewards and applies them to hub stash.
   * @private
   */
  _setupMissionRewardsListener() {
    window.addEventListener("mission-rewards", (event) => {
      this._handleMissionRewards(event.detail);
    });
  }

  /**
   * Sets up listener for campaign data changes (mission completion).
   * @private
   */
  _setupCampaignDataListener() {
    window.addEventListener("campaign-data-changed", async (event) => {
      console.log(
        "GameStateManager: Received campaign-data-changed event:",
        event.detail
      );
      await this._saveCampaign();
    });
  }

  /**
   * Handles mission rewards by applying them to the hub stash.
   * @param {Object} rewardData - Reward data from mission
   * @private
   */
  _handleMissionRewards(rewardData) {
    console.log("Applying mission rewards:", rewardData);

    // Apply currency
    if (rewardData.currency) {
      // Handle both snake_case (from JSON) and camelCase (from code)
      const shards =
        rewardData.currency.aether_shards ||
        rewardData.currency.aetherShards ||
        0;
      const cores =
        rewardData.currency.ancient_cores ||
        rewardData.currency.ancientCores ||
        0;

      if (shards > 0) {
        this.hubStash.currency.aetherShards += shards;
        console.log(`Added ${shards} Aether Shards to hub stash`);
      }
      if (cores > 0) {
        this.hubStash.currency.ancientCores += cores;
        console.log(`Added ${cores} Ancient Cores to hub stash`);
      }
    }

    // Apply items
    if (rewardData.items && Array.isArray(rewardData.items)) {
      rewardData.items.forEach((itemDefId) => {
        // Create item instance from definition ID
        const itemInstance = {
          uid: `ITEM_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
          defId: itemDefId,
          isNew: true,
          quantity: 1,
        };
        this.hubStash.addItem(itemInstance);
        console.log(`Added item ${itemDefId} to hub stash`);
      });
    }

    // XP is handled elsewhere (if needed for progression system)
    if (rewardData.xp && rewardData.xp > 0) {
      console.log(`Mission awarded ${rewardData.xp} XP`);
      // TODO: Apply XP to player progression when that system is implemented
    }

    // Unlocks are already handled by MissionManager.unlockClasses()
    // which stores them in localStorage

    // Save hub stash state
    this._saveHubStash();
  }

  /**
   * Loads the hub stash from persistence.
   * @private
   */
  async _loadHubStash() {
    try {
      // Try IndexedDB first
      const hubData = await this.persistence.loadHubStash();
      if (hubData) {
        if (hubData.currency) {
          this.hubStash.currency.aetherShards =
            hubData.currency.aetherShards || 0;
          this.hubStash.currency.ancientCores =
            hubData.currency.ancientCores || 0;
        }
        if (hubData.items && Array.isArray(hubData.items)) {
          hubData.items.forEach((item) => {
            this.hubStash.addItem(item);
          });
        }
        console.log("Loaded hub stash from IndexedDB");
        return;
      }

      // Fallback: migrate from localStorage if it exists
      const saved = localStorage.getItem("aether_shards_hub_stash");
      if (saved) {
        const legacyData = JSON.parse(saved);
        if (legacyData.currency || legacyData.items) {
          // Migrate to IndexedDB
          await this.persistence.saveHubStash(legacyData);
          // Load from IndexedDB
          await this._loadHubStash();
          // Remove legacy localStorage entry
          localStorage.removeItem("aether_shards_hub_stash");
          console.log("Migrated hub stash from localStorage to IndexedDB");
        }
      }
    } catch (error) {
      console.warn("Failed to load hub stash:", error);
    }
  }

  /**
   * Saves the hub stash to persistence.
   * @private
   */
  async _saveHubStash() {
    // Save hub stash data to IndexedDB
    // This ensures rewards persist across sessions
    try {
      const hubData = {
        currency: this.hubStash.currency,
        items: this.hubStash.getAllItems(),
      };
      await this.persistence.saveHubStash(hubData);
    } catch (error) {
      console.warn("Failed to save hub stash:", error);
    }
  }
}

// Export the Singleton Instance
/** @type {GameStateManagerClass} */
export const gameStateManager = new GameStateManagerClass();
export const GameStateManager = GameStateManagerClass;
