/**
 * InventoryManager.js
 * Manages item equipping, unequipping, and transfers between containers and unit loadouts.
 */

import { InventoryContainer } from "../models/InventoryContainer.js";

/**
 * @typedef {Object} ItemInstance
 * @property {string} uid - Unique Instance ID
 * @property {string} defId - Reference to static registry
 * @property {boolean} isNew - For UI "New!" badges
 * @property {number} quantity - For stackables (Potions/Materials)
 */

/**
 * @typedef {Object} UnitLoadout
 * @property {ItemInstance | null} mainHand
 * @property {ItemInstance | null} offHand
 * @property {ItemInstance | null} body
 * @property {ItemInstance | null} accessory
 * @property {[ItemInstance | null, ItemInstance | null]} belt
 */

/**
 * @typedef {Object} Unit
 * @property {string} id
 * @property {string} activeClassId
 * @property {Object} baseStats
 * @property {UnitLoadout} loadout
 * @property {Function} recalculateStats
 */

export class InventoryManager {
  /**
   * @param {Object} itemRegistry - Registry with get(defId) method returning Item definitions
   * @param {InventoryContainer} runStash - Active run stash
   * @param {InventoryContainer} hubStash - Persistent hub stash
   */
  constructor(itemRegistry, runStash, hubStash) {
    /** @type {Object} */
    this.itemRegistry = itemRegistry;

    /** @type {InventoryContainer} */
    this.runStash = runStash;

    /** @type {InventoryContainer} */
    this.hubStash = hubStash;
  }

  /**
   * Checks if a unit can equip an item.
   * Validates class restrictions and stat requirements.
   * @param {Unit} unit - The unit attempting to equip
   * @param {ItemInstance} itemInstance - The item instance to check
   * @returns {boolean}
   */
  canEquip(unit, itemInstance) {
    if (!unit || !itemInstance) {
      return false;
    }

    const itemDef = this.itemRegistry.get(itemInstance.defId);
    if (!itemDef) {
      return false;
    }

    // Use the Item's canEquip method if available
    if (typeof itemDef.canEquip === "function") {
      return itemDef.canEquip(unit);
    }

    // Fallback validation if Item class doesn't have canEquip
    if (itemDef.requirements) {
      // Check class lock
      if (itemDef.requirements.class_lock) {
        if (!itemDef.requirements.class_lock.includes(unit.activeClassId)) {
          return false;
        }
      }

      // Check min stats
      if (itemDef.requirements.min_stat) {
        for (const [stat, value] of Object.entries(
          itemDef.requirements.min_stat
        )) {
          if (unit.baseStats[stat] < value) {
            return false;
          }
        }
      }
    }

    return true;
  }

  /**
   * Checks if an item is two-handed.
   * @param {Object} itemDef - Item definition
   * @returns {boolean}
   * @private
   */
  _isTwoHanded(itemDef) {
    if (!itemDef) {
      return false;
    }

    // Check tags for TWO_HANDED
    if (itemDef.tags && Array.isArray(itemDef.tags)) {
      return itemDef.tags.includes("TWO_HANDED");
    }

    return false;
  }

  /**
   * Gets the appropriate stash (run or hub) based on where the item currently is.
   * @param {ItemInstance} itemInstance - The item instance
   * @returns {InventoryContainer | null}
   * @private
   */
  _getStashForItem(itemInstance) {
    // Check both stashes (runStash may be null in Hub mode)
    if (this.runStash && this.runStash.findItem(itemInstance.uid)) {
      return this.runStash;
    }
    if (this.hubStash && this.hubStash.findItem(itemInstance.uid)) {
      return this.hubStash;
    }
    return null;
  }

  /**
   * Equips an item to a unit's loadout.
   * @param {Unit} unit - The unit to equip the item to
   * @param {ItemInstance} itemInstance - The item instance to equip
   * @param {string} slot - Slot type: "MAIN_HAND", "OFF_HAND", "BODY", "ACCESSORY", "BELT"
   * @param {number} [beltIndex] - Index for belt slot (0 or 1)
   * @returns {boolean} - True if successful, false otherwise
   */
  equipItem(unit, itemInstance, slot, beltIndex = 0) {
    if (!unit || !itemInstance || !slot) {
      return false;
    }

    // Validate that unit can equip this item
    if (!this.canEquip(unit, itemInstance)) {
      return false;
    }

    // Get item definition
    const itemDef = this.itemRegistry.get(itemInstance.defId);
    if (!itemDef) {
      return false;
    }

    // Find which stash contains this item
    const sourceStash = this._getStashForItem(itemInstance);
    if (!sourceStash) {
      console.warn(
        `Item ${itemInstance.uid} not found in any stash before equipping`
      );
      return false;
    }

    // Handle two-handed weapons
    if (slot === "MAIN_HAND" && this._isTwoHanded(itemDef)) {
      // Unequip off-hand if occupied
      if (unit.loadout.offHand) {
        this.transferToStash(unit, "OFF_HAND");
      }
    }

    // Handle belt slot
    if (slot === "BELT") {
      if (beltIndex !== 0 && beltIndex !== 1) {
        console.warn("Invalid belt index, must be 0 or 1");
        return false;
      }

      // Swap if slot is occupied
      const existingItem = unit.loadout.belt[beltIndex];
      if (existingItem) {
        sourceStash.addItem(existingItem);
      }

      // Remove from stash and equip
      sourceStash.removeItem(itemInstance.uid);
      unit.loadout.belt[beltIndex] = itemInstance;

      // Trigger stat recalculation
      if (typeof unit.recalculateStats === "function") {
        unit.recalculateStats(this.itemRegistry);
      }

      return true;
    }

    // Handle other slots
    const slotMap = {
      MAIN_HAND: "mainHand",
      OFF_HAND: "offHand",
      BODY: "body",
      ACCESSORY: "accessory",
    };

    const slotProperty = slotMap[slot];
    if (!slotProperty) {
      console.warn(`Invalid slot type: ${slot}`);
      return false;
    }

    // Swap if slot is occupied
    const existingItem = unit.loadout[slotProperty];
    if (existingItem) {
      sourceStash.addItem(existingItem);
    }

    // Remove from stash and equip
    sourceStash.removeItem(itemInstance.uid);
    unit.loadout[slotProperty] = itemInstance;

    // Trigger stat recalculation
    if (typeof unit.recalculateStats === "function") {
      unit.recalculateStats();
    }

    return true;
  }

  /**
   * Unequips an item from a unit's loadout and moves it to the hub stash.
   * @param {Unit} unit - The unit to unequip from
   * @param {string} slot - Slot type: "MAIN_HAND", "OFF_HAND", "BODY", "ACCESSORY", "BELT"
   * @param {number} [beltIndex] - Index for belt slot (0 or 1)
   * @returns {boolean} - True if successful, false otherwise
   */
  unequipItem(unit, slot, beltIndex = 0) {
    if (!unit || !slot) {
      return false;
    }

    // Handle belt slot
    if (slot === "BELT") {
      if (beltIndex !== 0 && beltIndex !== 1) {
        console.warn("Invalid belt index, must be 0 or 1");
        return false;
      }

      const item = unit.loadout.belt[beltIndex];
      if (!item) {
        return false;
      }

      // Move to hub stash
      this.hubStash.addItem(item);
      unit.loadout.belt[beltIndex] = null;

      // Trigger stat recalculation
      if (typeof unit.recalculateStats === "function") {
        unit.recalculateStats(this.itemRegistry);
      }

      return true;
    }

    // Handle other slots
    const slotMap = {
      MAIN_HAND: "mainHand",
      OFF_HAND: "offHand",
      BODY: "body",
      ACCESSORY: "accessory",
    };

    const slotProperty = slotMap[slot];
    if (!slotProperty) {
      console.warn(`Invalid slot type: ${slot}`);
      return false;
    }

    const item = unit.loadout[slotProperty];
    if (!item) {
      return false;
    }

    // Move to hub stash
    this.hubStash.addItem(item);
    unit.loadout[slotProperty] = null;

    // Trigger stat recalculation
    if (typeof unit.recalculateStats === "function") {
      unit.recalculateStats();
    }

    return true;
  }

  /**
   * Transfers an item from a unit's loadout to the hub stash.
   * Alias for unequipItem, but kept for API clarity.
   * @param {Unit} unit - The unit to transfer from
   * @param {string} slot - Slot type
   * @param {number} [beltIndex] - Index for belt slot (0 or 1)
   * @returns {boolean} - True if successful, false otherwise
   */
  transferToStash(unit, slot, beltIndex = 0) {
    return this.unequipItem(unit, slot, beltIndex);
  }
}

