/**
 * @typedef {import("./types.js").UnitRegistry} UnitRegistry
 * @typedef {import("../units/types.js").UnitDefinition} UnitDefinition
 * @typedef {import("../units/types.js").Position} Position
 * @typedef {import("../units/types.js").UnitTeam} UnitTeam
 */

import { Unit } from "../units/Unit.js";
import { Explorer } from "../units/Explorer.js";
import { Enemy } from "../units/Enemy.js";

/**
 * UnitManager.js
 * Manages the lifecycle (creation, tracking, death) of all active units.
 * Acts as the Source of Truth for "Who is alive?" and "Where are they relative to me?".
 * @class
 */
export class UnitManager {
  /**
   * @param {UnitRegistry} registry - Map or Object containing Unit Definitions (Stats, Models).
   */
  constructor(registry) {
    /** @type {UnitRegistry} */
    this.registry = registry;
    /** @type {Map<string, Unit>} */
    this.activeUnits = new Map(); // ID -> Unit Instance
    /** @type {number} */
    this.nextInstanceId = 0;
  }

  // --- LIFECYCLE ---

  /**
   * Factory method to spawn a unit from a template ID.
   * @param {string} defId - The definition ID (e.g. 'CLASS_VANGUARD', 'ENEMY_SENTINEL')
   * @param {UnitTeam} team - 'PLAYER', 'ENEMY', 'NEUTRAL'
   * @returns {Unit | null} - Created unit or null if definition not found
   */
  createUnit(defId, team) {
    // Support both Map interface and Object interface for registry
    const def =
      typeof this.registry.get === "function"
        ? this.registry.get(defId)
        : this.registry[defId];

    if (!def) {
      console.error(`Unit Definition not found: ${defId}`);
      return null;
    }

    const instanceId = `${defId}_${this.nextInstanceId++}`;
    let unit;

    // Polymorphic creation based on definition type logic
    // Note: Class IDs usually start with "CLASS_" for Explorers
    if (defId.startsWith("CLASS_") || def.type === "EXPLORER") {
      // For Explorers, the def is the Class Definition
      // We use the Definition Name as the Unit Name for now
      unit = new Explorer(instanceId, def.name, defId, def);
    } else if (def.type === "ENEMY" || defId.startsWith("ENEMY_")) {
      unit = new Enemy(instanceId, def.name, def);
    } else {
      // Generic/Structure
      unit = new Unit(instanceId, def.name, "STRUCTURE", def.model);
      // Hydrate generic stats
      if (def.stats) {
        unit.baseStats = { ...unit.baseStats, ...def.stats };
        unit.currentHealth = unit.baseStats.health;
      }
    }

    unit.team = team;
    this.activeUnits.set(instanceId, unit);

    return unit;
  }

  /**
   * Removes a unit from the active units map.
   * @param {string} unitId - Unit ID to remove
   * @returns {boolean} - True if unit was removed
   */
  removeUnit(unitId) {
    if (this.activeUnits.has(unitId)) {
      const unit = this.activeUnits.get(unitId);
      // Optional: Trigger ON_DEATH events here if we had the EventBus wired up
      this.activeUnits.delete(unitId);
      return true;
    }
    return false;
  }

  // --- QUERIES ---

  /**
   * Gets a unit by ID.
   * @param {string} id - Unit ID
   * @returns {Unit | undefined} - Unit instance or undefined
   */
  getUnitById(id) {
    return this.activeUnits.get(id);
  }

  /**
   * Gets all active units.
   * @returns {Unit[]} - Array of all active units
   */
  getAllUnits() {
    return Array.from(this.activeUnits.values());
  }

  /**
   * Gets all units on a specific team.
   * @param {UnitTeam} team - Team to filter by
   * @returns {Unit[]} - Array of units on the team
   */
  getUnitsByTeam(team) {
    return this.getAllUnits().filter((u) => u.team === team);
  }

  /**
   * Finds all units within 'range' of 'centerPos'.
   * Used for AoE spells, Auras, and AI scanning.
   * @param {Position} centerPos - {x, y, z}
   * @param {number} range - Distance in tiles (Manhattan)
   * @param {UnitTeam | null} [filterTeam] - Optional: Only return units of this team
   * @returns {Unit[]} - Array of units in range
   */
  getUnitsInRange(centerPos, range, filterTeam = null) {
    const result = [];

    for (const unit of this.activeUnits.values()) {
      if (filterTeam && unit.team !== filterTeam) continue;

      // Manhattan Distance Check (Consistent with Grid logic)
      // Note: We assume unit.position is always updated by the Grid/MovementSystem
      const dist =
        Math.abs(unit.position.x - centerPos.x) +
        Math.abs(unit.position.y - centerPos.y) +
        Math.abs(unit.position.z - centerPos.z);

      if (dist <= range) {
        result.push(unit);
      }
    }

    return result;
  }

  // --- STATE UPDATES ---

  /**
   * Called at start of Round or Turn to process dots/cooldowns for all units.
   */
  tickAll() {
    for (const unit of this.activeUnits.values()) {
      // Tick Cooldowns
      if (unit.skillManager) {
        unit.skillManager.tickCooldowns();
      }

      // Future: Process Status Effects (e.g. Poison damage)
      // this.effectProcessor.processTurnStart(unit);
    }
  }
}
