/**
 * @typedef {import("../units/Unit.js").Unit} Unit
 * @typedef {import("../managers/UnitManager.js").UnitManager} UnitManager
 * @typedef {import("../core/types.d.ts").CombatPhase} CombatPhase
 * @typedef {import("../grid/VoxelGrid.js").VoxelGrid} VoxelGrid
 * @typedef {import("../systems/EffectProcessor.js").EffectProcessor} EffectProcessor
 */

/**
 * TurnSystem.js
 * Manages the turn-based combat loop using a charge-based tick system.
 * Implements the specifications from Turn-System.spec.md and TurnLifecycle.spec.md
 * @class
 */
export class TurnSystem extends EventTarget {
  /**
   * @param {UnitManager} [unitManager] - Unit manager instance
   */
  constructor(unitManager = null) {
    super();
    /** @type {UnitManager | null} */
    this.unitManager = unitManager;

    /** @type {VoxelGrid | null} */
    this.voxelGrid = null;

    /** @type {EffectProcessor | null} */
    this.effectProcessor = null;

    /** @type {number} */
    this.globalTick = 0;

    /** @type {string | null} */
    this.activeUnitId = null;

    /** @type {CombatPhase} */
    this.phase = "INIT";

    /** @type {number} */
    this.round = 1;

    /** @type {string[]} */
    this.turnQueue = [];

    /** @type {Set<string>} - Track which units have acted in the current round */
    this.unitsActedThisRound = new Set();
  }

  /**
   * Sets the unit manager context.
   * @param {UnitManager} unitManager - Unit manager instance
   */
  setContext(unitManager) {
    this.unitManager = unitManager;
  }

  /**
   * Sets the voxel grid and effect processor context for environmental hazards.
   * @param {VoxelGrid} voxelGrid - Voxel grid instance
   * @param {EffectProcessor} effectProcessor - Effect processor instance
   */
  setHazardContext(voxelGrid, effectProcessor) {
    this.voxelGrid = voxelGrid;
    this.effectProcessor = effectProcessor;
  }

  /**
   * Sets callback for processing ON_TURN_START passive effects.
   * @param {Function} callback - Callback function(unit)
   */
  setTurnStartCallback(callback) {
    this.onTurnStartCallback = callback;
  }

  /**
   * Sets callback for processing ON_TURN_END passive effects.
   * @param {Function} callback - Callback function(unit)
   */
  setTurnEndCallback(callback) {
    this.onTurnEndCallback = callback;
  }

  /**
   * Starts combat with the given units.
   * @param {Unit[]} units - Array of units to include in combat
   */
  startCombat(units) {
    if (!this.unitManager) {
      console.error("TurnSystem: UnitManager not set");
      return;
    }

    this.globalTick = 0;
    this.round = 1;
    this.phase = "TURN_START";
    this.unitsActedThisRound = new Set();

    // Initialize charge meters based on speed
    units.forEach((unit) => {
      const speed = unit.baseStats?.speed || 10;
      // Initial charge based on speed (faster units start higher)
      unit.chargeMeter = Math.min(100, Math.max(0, speed * 5));
    });

    // Dispatch combat-start event
    this.dispatchEvent(
      new CustomEvent("combat-start", {
        detail: { units: units.map((u) => u.id) },
      })
    );

    // Advance to first active unit
    this.advanceToNextTurn();
  }

  /**
   * Starts a unit's turn (Activation Phase).
   * @param {Unit} unit - The unit whose turn is starting
   */
  startTurn(unit) {
    if (!unit) return;

    // Check if we need to increment the round
    // If this unit has already acted this round, we've completed a full cycle
    if (this.unitsActedThisRound.has(unit.id)) {
      // All units have acted at least once, increment round
      this.round += 1;
      this.unitsActedThisRound.clear();
    }

    // Mark this unit as having acted this round
    this.unitsActedThisRound.add(unit.id);

    this.activeUnitId = unit.id;
    this.phase = "WAITING_FOR_INPUT";

    // A. AP Regeneration: Formula = 3 + floor(speed/5)
    const speed = unit.baseStats?.speed || 10;
    const maxAP = 3 + Math.floor(speed / 5);
    unit.currentAP = maxAP;

    // B. Cooldown Reduction
    if (unit.actions && Array.isArray(unit.actions)) {
      unit.actions.forEach((action) => {
        if (action.cooldown && action.cooldown > 0) {
          action.cooldown -= 1;
        }
      });
    }

    // Check for Stun BEFORE processing status effects
    // (so we can catch stuns that would expire this turn)
    const isStunned =
      unit.statusEffects &&
      unit.statusEffects.some(
        (effect) =>
          effect.id === "STUN" || effect.type === "STUN" || effect.id === "stun"
      );

    if (isStunned) {
      // Process hazards first, then status effects
      this.processEnvironmentalHazards(unit);
      this.processStatusEffects(unit);
      // Skip action phase, immediately end turn
      this.phase = "TURN_END";
      this.endTurn(unit);
      return;
    }

    // C. Environmental Hazard Processing (Integration Point 3)
    // Process hazards BEFORE status effects (hazards are environmental, status effects are on the unit)
    this.processEnvironmentalHazards(unit);

    // D. Status Effect Tick (The "Upkeep" Step)
    this.processStatusEffects(unit);

    // Dispatch turn-start event
    this.dispatchEvent(
      new CustomEvent("turn-start", {
        detail: {
          unitId: unit.id,
          unit: unit,
        },
      })
    );

    // E. Process ON_TURN_START passive effects (if GameLoop is available)
    if (this.onTurnStartCallback) {
      this.onTurnStartCallback(unit);
    }
  }

  /**
   * Processes environmental hazards at the unit's position.
   * Integration Point 3: Environmental Hazard
   * @param {Unit} unit - The unit to check hazards for
   * @private
   */
  processEnvironmentalHazards(unit) {
    if (!this.voxelGrid || !this.effectProcessor || !unit.position) {
      return;
    }

    const hazard = this.voxelGrid.getHazardAt(unit.position);
    if (!hazard) {
      return;
    }

    // Map hazard IDs to effect definitions
    const hazardEffects = {
      HAZARD_FIRE: {
        type: "DAMAGE",
        power: 5,
        element: "FIRE",
      },
      HAZARD_ACID: {
        type: "DAMAGE",
        power: 5,
        element: "TECH",
      },
      HAZARD_ELECTRICITY: {
        type: "DAMAGE",
        power: 5,
        element: "SHOCK",
      },
    };

    let effectDef = hazardEffects[hazard.id];
    if (!effectDef) {
      // Unknown hazard type, use default damage
      effectDef = {
        type: "DAMAGE",
        power: 5,
      };
    }

    // Process hazard damage through EffectProcessor
    // Source is null (environmental), target is the unit
    const result = this.effectProcessor.process(effectDef, null, unit);
    if (result.success && result.data && result.data.type === "DAMAGE") {
      console.log(
        `${unit.name} took ${result.data.amount} ${
          effectDef.element || "damage"
        } from ${hazard.id}`
      );
    }

    // Decrement hazard duration
    hazard.duration -= 1;
    if (hazard.duration <= 0) {
      // Remove expired hazard
      const key = `${unit.position.x},${unit.position.y},${unit.position.z}`;
      this.voxelGrid.hazardMap.delete(key);
    }
  }

  /**
   * Processes status effects for a unit (DoT/HoT, duration decrement, expiration).
   * @param {Unit} unit - The unit to process status effects for
   */
  processStatusEffects(unit) {
    if (!unit.statusEffects || unit.statusEffects.length === 0) return;

    const effectsToRemove = [];

    unit.statusEffects.forEach((effect, index) => {
      // Apply DoT/HoT if applicable
      if (effect.type === "DOT" || effect.damage) {
        const damage = effect.damage || 0;
        if (damage > 0) {
          const previousHP = unit.currentHealth;
          unit.currentHealth = Math.max(0, unit.currentHealth - damage);
          const actualDamage = previousHP - unit.currentHealth;
          if (actualDamage > 0) {
            console.log(
              `${unit.name} took ${actualDamage} ${
                effect.element || ""
              } damage from ${effect.id} (${effect.duration} turns remaining)`
            );
          }
        }
      } else if (effect.type === "HOT" || effect.heal) {
        const heal = effect.heal || 0;
        if (heal > 0) {
          const previousHP = unit.currentHealth;
          unit.currentHealth = Math.min(
            unit.maxHealth,
            unit.currentHealth + heal
          );
          const actualHeal = unit.currentHealth - previousHP;
          if (actualHeal > 0) {
            console.log(
              `${unit.name} healed ${actualHeal} HP from ${effect.id} (${effect.duration} turns remaining)`
            );
          }
        }
      }

      // Decrement duration
      if (effect.duration !== undefined && effect.duration > 0) {
        effect.duration -= 1;

        // Expire if duration reaches 0 (unless permanent)
        if (effect.duration === 0 && effect.permanent !== true) {
          effectsToRemove.push(index);
        }
      }
    });

    // Remove expired effects (in reverse order to maintain indices)
    effectsToRemove.reverse().forEach((index) => {
      unit.statusEffects.splice(index, 1);
    });
  }

  /**
   * Ends a unit's turn (Resolution Phase).
   * @param {Unit} unit - The unit whose turn is ending
   * @param {boolean} [skipAdvance=false] - If true, skip advancing to next turn (useful for cleanup)
   */
  endTurn(unit, skipAdvance = false) {
    if (!unit) return;

    this.phase = "TURN_END";

    // A. Charge Meter Consumption: Subtract 100, don't reset to 0
    // This preserves speed advantage for fast units
    unit.chargeMeter = Math.max(0, unit.chargeMeter - 100);

    // B. Action Slot Reset (if needed in future)
    // Reset flags like hasMoved, hasAttacked, etc.

    // Clear active unit before dispatching event (so getActiveUnit() returns null)
    this.activeUnitId = null;

    // Dispatch turn-end event
    this.dispatchEvent(
      new CustomEvent("turn-end", {
        detail: {
          unitId: unit.id,
          unit: unit,
        },
      })
    );

    // Process ON_TURN_END passive effects (if callback is available)
    if (this.onTurnEndCallback) {
      this.onTurnEndCallback(unit);
    }

    // Advance to next turn (unless we're skipping for cleanup)
    if (!skipAdvance) {
      this.advanceToNextTurn();
    }
  }

  /**
   * Advances to the next turn using the tick system.
   * Loops until a unit reaches 100 charge.
   */
  advanceToNextTurn() {
    if (!this.unitManager) {
      console.error("TurnSystem: UnitManager not set");
      // If we're already ending, don't try to advance
      if (this.phase === "COMBAT_END" || this.phase === "INIT") {
        return;
      }
      this.endCombat();
      return;
    }

    // Get all alive units from UnitManager
    const allUnits = this.unitManager.getAllUnits().filter((unit) => {
      // Check if unit is alive
      if (unit.isAlive && typeof unit.isAlive === "function") {
        return unit.isAlive();
      }
      return unit.currentHealth > 0;
    });

    if (allUnits.length === 0) {
      // No units left, end combat
      this.endCombat();
      return;
    }

    // Safety check: if we're already in INIT or COMBAT_END, don't advance
    if (this.phase === "INIT" || this.phase === "COMBAT_END") {
      return;
    }

    // Tick loop: Keep adding speed to charge until someone reaches 100
    // Safety limit to prevent infinite loops (e.g., if all units have 0 speed)
    let tickLimit = 10000; // Max ticks before giving up
    while (tickLimit > 0) {
      tickLimit -= 1;
      this.globalTick += 1;

      // Add speed to each unit's charge
      allUnits.forEach((unit) => {
        const speed = unit.baseStats?.speed || 10;
        unit.chargeMeter = (unit.chargeMeter || 0) + speed;
      });

      // Check if any unit has reached 100 charge
      const readyUnits = allUnits.filter((unit) => unit.chargeMeter >= 100);

      if (readyUnits.length > 0) {
        // Sort by charge (descending), then by team (Player beats Enemy)
        readyUnits.sort((a, b) => {
          if (b.chargeMeter !== a.chargeMeter) {
            return b.chargeMeter - a.chargeMeter;
          }
          // Tie breaking: Player beats Enemy
          if (a.team === "PLAYER" && b.team !== "PLAYER") return -1;
          if (b.team === "PLAYER" && a.team !== "PLAYER") return 1;
          return 0;
        });

        const nextUnit = readyUnits[0];
        this.startTurn(nextUnit);
        break;
      }
    }

    if (tickLimit === 0) {
      console.error(
        "TurnSystem: advanceToNextTurn() hit safety limit - no unit reached 100 charge"
      );
      // End combat if we can't advance
      this.endCombat();
    }

    // Update projected queue for UI
    this.updateProjectedQueue();
  }

  /**
   * Updates the projected turn queue for UI display.
   */
  updateProjectedQueue() {
    if (!this.unitManager) return;

    const allUnits = this.unitManager.getAllUnits().filter((unit) => {
      if (unit.isAlive && typeof unit.isAlive === "function") {
        return unit.isAlive();
      }
      return unit.currentHealth > 0;
    });

    // Sort by charge (descending) to show who acts next
    const sorted = [...allUnits].sort((a, b) => {
      if (b.chargeMeter !== a.chargeMeter) {
        return b.chargeMeter - a.chargeMeter;
      }
      if (a.team === "PLAYER" && b.team !== "PLAYER") return -1;
      if (b.team === "PLAYER" && a.team !== "PLAYER") return 1;
      return 0;
    });

    this.turnQueue = sorted.map((unit) => unit.id);
  }

  /**
   * Gets the predicted queue by simulating future ticks without applying them.
   * @param {number} depth - How many units to predict
   * @returns {string[]} - Array of unit IDs in predicted order
   */
  getPredictedQueue(depth = 5) {
    if (!this.unitManager) return [];

    // Clone current charge states
    const allUnits = this.unitManager.getAllUnits().filter((unit) => {
      if (unit.isAlive && typeof unit.isAlive === "function") {
        return unit.isAlive();
      }
      return unit.currentHealth > 0;
    });

    const chargeSnapshot = new Map();
    allUnits.forEach((unit) => {
      chargeSnapshot.set(unit.id, unit.chargeMeter || 0);
    });

    const predicted = [];
    let tickCount = 0;
    const maxTicks = 1000; // Safety limit

    // Simulate ticks until we have enough units
    while (predicted.length < depth && tickCount < maxTicks) {
      tickCount += 1;

      // Add speed to each unit's charge
      allUnits.forEach((unit) => {
        const currentCharge = chargeSnapshot.get(unit.id) || 0;
        const speed = unit.baseStats?.speed || 10;
        chargeSnapshot.set(unit.id, currentCharge + speed);
      });

      // Check for units that would be ready
      const readyUnits = allUnits.filter(
        (unit) => (chargeSnapshot.get(unit.id) || 0) >= 100
      );

      if (readyUnits.length > 0) {
        // Sort and add the highest charge unit
        readyUnits.sort((a, b) => {
          const chargeA = chargeSnapshot.get(a.id) || 0;
          const chargeB = chargeSnapshot.get(b.id) || 0;
          if (chargeB !== chargeA) {
            return chargeB - chargeA;
          }
          if (a.team === "PLAYER" && b.team !== "PLAYER") return -1;
          if (b.team === "PLAYER" && a.team !== "PLAYER") return 1;
          return 0;
        });

        const nextUnit = readyUnits[0];
        if (!predicted.includes(nextUnit.id)) {
          predicted.push(nextUnit.id);
          // Subtract 100 from this unit's charge for next prediction
          const currentCharge = chargeSnapshot.get(nextUnit.id) || 0;
          chargeSnapshot.set(nextUnit.id, Math.max(0, currentCharge - 100));
        }
      }
    }

    return predicted;
  }

  /**
   * Gets the currently active unit.
   * @returns {Unit | null} - The active unit or null
   */
  getActiveUnit() {
    if (!this.activeUnitId || !this.unitManager) return null;
    return this.unitManager.getUnitById(this.activeUnitId) || null;
  }

  /**
   * Gets the current combat state matching the spec interface.
   * @returns {import("../core/types.d.ts").CombatState} - Combat state object
   */
  getCombatState() {
    return {
      isActive: this.phase !== "INIT" && this.phase !== "COMBAT_END",
      round: this.round,
      turnQueue: this.turnQueue,
      activeUnitId: this.activeUnitId,
      phase: this.phase,
    };
  }

  /**
   * Resets the turn system to initial state.
   * Note: Event listeners should be removed by the caller (e.g., GameLoop.stop()).
   * Useful for cleanup between tests or when restarting combat.
   */
  reset() {
    this.globalTick = 0;
    this.activeUnitId = null;
    this.phase = "INIT";
    this.round = 1;
    this.turnQueue = [];
    this.unitsActedThisRound = new Set();
  }

  /**
   * Safely ends combat and cleans up state.
   */
  endCombat() {
    this.phase = "COMBAT_END";
    this.activeUnitId = null;
    this.dispatchEvent(new CustomEvent("combat-end"));
  }
}
