/**
 * ResearchManager.js
 * Manages the Research system - tech trees, node unlocking, and passive effects.
 * @class
 */

/**
 * @typedef {Object} ResearchNode
 * @property {string} id - Unique node ID (e.g. "RES_LOGISTICS_01")
 * @property {string} name - Display name
 * @property {string} description - Description text
 * @property {"LOGISTICS" | "INTEL" | "FIELD_OPS"} tree - Which tree this belongs to
 * @property {number} tier - Depth in the tree (1-5)
 * @property {number} cost - Ancient Cores cost
 * @property {string[]} prerequisites - IDs of parent nodes that must be unlocked first
 * @property {Object} effect - The actual effect applied to the game state
 * @property {string} effect.type - Effect type
 * @property {number | string} effect.value - Effect value
 */

/**
 * @typedef {Object} ResearchState
 * @property {string[]} unlockedNodeIds - List of IDs player has bought
 * @property {number} availableCores - Available Ancient Cores (not persisted, read from hubStash)
 */

/**
 * Research node definitions based on Research.spec.md
 * @type {ResearchNode[]}
 */
const RESEARCH_NODES = [
  // LOGISTICS TREE
  {
    id: "RES_LOGISTICS_01",
    name: "Expanded Barracks I",
    description: "Increases Roster Limit by 2",
    tree: "LOGISTICS",
    tier: 1,
    cost: 2,
    prerequisites: [],
    effect: { type: "ROSTER_LIMIT", value: 2 },
  },
  {
    id: "RES_LOGISTICS_02",
    name: "Bulk Contracts",
    description: "Recruiting cost reduced by 10%",
    tree: "LOGISTICS",
    tier: 2,
    cost: 3,
    prerequisites: ["RES_LOGISTICS_01"],
    effect: { type: "RECRUIT_DISCOUNT", value: 0.1 },
  },
  {
    id: "RES_LOGISTICS_03",
    name: "Expanded Barracks II",
    description: "Increases Roster Limit by 4",
    tree: "LOGISTICS",
    tier: 3,
    cost: 5,
    prerequisites: ["RES_LOGISTICS_01"],
    effect: { type: "ROSTER_LIMIT", value: 4 },
  },
  {
    id: "RES_LOGISTICS_04",
    name: "Deep Pockets",
    description: "Market Buyback slots increased by 2",
    tree: "LOGISTICS",
    tier: 4,
    cost: 4,
    prerequisites: ["RES_LOGISTICS_02"],
    effect: { type: "MARKET_BUYBACK_SLOTS", value: 2 },
  },
  {
    id: "RES_LOGISTICS_05",
    name: "Salvage Protocol",
    description: "Sell prices at Market increased by 10%",
    tree: "LOGISTICS",
    tier: 5,
    cost: 6,
    prerequisites: ["RES_LOGISTICS_03", "RES_LOGISTICS_04"],
    effect: { type: "MARKET_SELL_BONUS", value: 0.1 },
  },

  // INTELLIGENCE TREE
  {
    id: "RES_INTEL_01",
    name: "Scout Drone",
    description: "Reveals the biome type of Side Missions before accepting them",
    tree: "INTEL",
    tier: 1,
    cost: 2,
    prerequisites: [],
    effect: { type: "UI_UNLOCK", value: "SCOUT_DRONE" },
  },
  {
    id: "RES_INTEL_02",
    name: "Vital Sensors",
    description: "Enemy Health Bars show exact numbers instead of just bars",
    tree: "INTEL",
    tier: 2,
    cost: 3,
    prerequisites: ["RES_INTEL_01"],
    effect: { type: "UI_UNLOCK", value: "VITAL_SENSORS" },
  },
  {
    id: "RES_INTEL_03",
    name: "Threat Analysis",
    description: "Reveals Enemy Types (e.g. 'Mechanical') on the Mission Board dossier",
    tree: "INTEL",
    tier: 3,
    cost: 4,
    prerequisites: ["RES_INTEL_01"],
    effect: { type: "UI_UNLOCK", value: "THREAT_ANALYSIS" },
  },
  {
    id: "RES_INTEL_04",
    name: "Map Data",
    description: "Reveals the location of the Boss Room on the minimap at start of run",
    tree: "INTEL",
    tier: 4,
    cost: 5,
    prerequisites: ["RES_INTEL_02", "RES_INTEL_03"],
    effect: { type: "UI_UNLOCK", value: "MAP_DATA" },
  },

  // FIELD OPS TREE
  {
    id: "RES_FIELD_OPS_01",
    name: "Supply Drop",
    description: "Start every run with 1 Free Potion in the shared stash",
    tree: "FIELD_OPS",
    tier: 1,
    cost: 2,
    prerequisites: [],
    effect: { type: "STARTING_ITEM", value: "ITEM_POTION" },
  },
  {
    id: "RES_FIELD_OPS_02",
    name: "Fast Deploy",
    description: "First turn of combat grants +1 AP to all units",
    tree: "FIELD_OPS",
    tier: 2,
    cost: 3,
    prerequisites: ["RES_FIELD_OPS_01"],
    effect: { type: "COMBAT_BONUS", value: "FAST_DEPLOY" },
  },
  {
    id: "RES_FIELD_OPS_03",
    name: "Emergency Beacon",
    description: "Retreat option becomes available (Escape with 50% loot retention instead of 0%)",
    tree: "FIELD_OPS",
    tier: 3,
    cost: 4,
    prerequisites: ["RES_FIELD_OPS_01"],
    effect: { type: "UI_UNLOCK", value: "EMERGENCY_BEACON" },
  },
  {
    id: "RES_FIELD_OPS_04",
    name: "Hardened Steel",
    description: "All crafted/bought weapons start with +1 Damage",
    tree: "FIELD_OPS",
    tier: 4,
    cost: 5,
    prerequisites: ["RES_FIELD_OPS_02", "RES_FIELD_OPS_03"],
    effect: { type: "WEAPON_BONUS", value: 1 },
  },
];

export class ResearchManager extends EventTarget {
  /**
   * @param {import("../core/Persistence.js").Persistence} persistence - Persistence manager
   * @param {import("../managers/RosterManager.js").RosterManager} rosterManager - Roster manager (for applying passive effects)
   */
  constructor(persistence, rosterManager) {
    super();
    /** @type {import("../core/Persistence.js").Persistence} */
    this.persistence = persistence;
    /** @type {import("../managers/RosterManager.js").RosterManager} */
    this.rosterManager = rosterManager;

    /** @type {ResearchState} */
    this.state = {
      unlockedNodeIds: [],
      availableCores: 0,
    };

    /** @type {Map<string, ResearchNode>} */
    this.nodeMap = new Map();
    RESEARCH_NODES.forEach((node) => {
      this.nodeMap.set(node.id, node);
    });
  }

  /**
   * Gets all research nodes.
   * @returns {ResearchNode[]}
   */
  getAllNodes() {
    return Array.from(this.nodeMap.values());
  }

  /**
   * Gets a node by ID.
   * @param {string} nodeId - Node ID
   * @returns {ResearchNode | undefined}
   */
  getNode(nodeId) {
    return this.nodeMap.get(nodeId);
  }

  /**
   * Gets nodes by tree type.
   * @param {"LOGISTICS" | "INTEL" | "FIELD_OPS"} treeType - Tree type
   * @returns {ResearchNode[]}
   */
  getNodesByTree(treeType) {
    return RESEARCH_NODES.filter((node) => node.tree === treeType);
  }

  /**
   * Loads research state from persistence.
   * @returns {Promise<void>}
   */
  async load() {
    const savedState = await this.persistence.loadResearchState();
    if (savedState) {
      this.state.unlockedNodeIds = savedState.unlockedNodeIds || [];
    } else {
      this.state.unlockedNodeIds = [];
    }
    // Apply passive effects after loading
    this.applyPassiveEffects();
  }

  /**
   * Saves research state to persistence.
   * @returns {Promise<void>}
   */
  async save() {
    await this.persistence.saveResearchState({
      unlockedNodeIds: this.state.unlockedNodeIds,
    });
  }

  /**
   * Updates available cores from hub stash.
   * @param {number} cores - Available Ancient Cores
   */
  updateAvailableCores(cores) {
    this.state.availableCores = cores;
  }

  /**
   * Checks if a node is unlocked.
   * @param {string} nodeId - Node ID
   * @returns {boolean}
   */
  isUnlocked(nodeId) {
    return this.state.unlockedNodeIds.includes(nodeId);
  }

  /**
   * Checks if a node is available (prerequisites met).
   * @param {string} nodeId - Node ID
   * @returns {boolean}
   */
  isAvailable(nodeId) {
    const node = this.getNode(nodeId);
    if (!node) return false;
    if (this.isUnlocked(nodeId)) return false; // Already unlocked

    // Check all prerequisites are unlocked
    return node.prerequisites.every((prereqId) => this.isUnlocked(prereqId));
  }

  /**
   * Gets the status of a node: "LOCKED", "AVAILABLE", or "RESEARCHED"
   * @param {string} nodeId - Node ID
   * @returns {"LOCKED" | "AVAILABLE" | "RESEARCHED"}
   */
  getNodeStatus(nodeId) {
    if (this.isUnlocked(nodeId)) return "RESEARCHED";
    if (this.isAvailable(nodeId)) return "AVAILABLE";
    return "LOCKED";
  }

  /**
   * Unlocks a research node.
   * Validates cost, prerequisites, and deducts cores.
   * @param {string} nodeId - Node ID to unlock
   * @param {number} availableCores - Current available Ancient Cores (from hubStash)
   * @returns {Promise<{ success: boolean; error?: string }>}
   */
  async unlockNode(nodeId, availableCores) {
    const node = this.getNode(nodeId);
    if (!node) {
      return { success: false, error: "Node not found" };
    }

    // Check if already unlocked
    if (this.isUnlocked(nodeId)) {
      return { success: false, error: "Node already researched" };
    }

    // Check prerequisites
    if (!this.isAvailable(nodeId)) {
      return { success: false, error: "Prerequisites not met" };
    }

    // Check cost
    if (availableCores < node.cost) {
      return { success: false, error: "Insufficient Ancient Cores" };
    }

    // Unlock the node
    this.state.unlockedNodeIds.push(nodeId);
    await this.save();

    // Apply passive effects immediately
    this.applyPassiveEffects();

    // Dispatch event
    this.dispatchEvent(
      new CustomEvent("research-complete", {
        detail: { nodeId, node },
      })
    );

    return { success: true };
  }

  /**
   * Applies passive effects from unlocked nodes.
   * Updates global constants like Roster Limit.
   */
  applyPassiveEffects() {
    // Reset roster limit to base
    this.rosterManager.rosterLimit = 12;

    // Apply all unlocked node effects
    for (const nodeId of this.state.unlockedNodeIds) {
      const node = this.getNode(nodeId);
      if (!node) continue;

      const { type, value } = node.effect;

      switch (type) {
        case "ROSTER_LIMIT":
          this.rosterManager.rosterLimit += Number(value);
          break;
        case "RECRUIT_DISCOUNT":
          // This will be applied in recruitment logic
          break;
        case "MARKET_BUYBACK_SLOTS":
          // This will be applied in MarketManager
          break;
        case "MARKET_SELL_BONUS":
          // This will be applied in MarketManager
          break;
        case "UI_UNLOCK":
        case "STARTING_ITEM":
        case "COMBAT_BONUS":
        case "WEAPON_BONUS":
          // These are runtime effects, handled elsewhere
          break;
      }
    }
  }

  /**
   * Checks if a research unlock is active (for runtime effects).
   * @param {string} unlockId - Unlock ID (e.g. "SCOUT_DRONE")
   * @returns {boolean}
   */
  hasUnlock(unlockId) {
    return this.state.unlockedNodeIds.some((nodeId) => {
      const node = this.getNode(nodeId);
      return node && node.effect.type === "UI_UNLOCK" && node.effect.value === unlockId;
    });
  }

  /**
   * Gets all active starting items.
   * @returns {string[]} - Array of item IDs
   */
  getStartingItems() {
    const items = [];
    for (const nodeId of this.state.unlockedNodeIds) {
      const node = this.getNode(nodeId);
      if (node && node.effect.type === "STARTING_ITEM") {
        items.push(String(node.effect.value));
      }
    }
    return items;
  }
}

