/**
 * @typedef {import("../grid/VoxelGrid.js").VoxelGrid} VoxelGrid
 * @typedef {import("../managers/UnitManager.js").UnitManager} UnitManager
 * @typedef {import("../units/Unit.js").Unit} Unit
 * @typedef {import("../grid/types.js").Position} Position
 * @typedef {import("../utils/SeededRandom.js").SeededRandom} SeededRandom
 * @typedef {import("./Effects.d.ts").EffectType} EffectType
 * @typedef {import("./Effects.d.ts").EffectParams} EffectParams
 * @typedef {import("./Effects.d.ts").EffectDefinition} EffectDefinition
 * @typedef {import("./Effects.d.ts").ConditionDefinition} ConditionDefinition
 */

/**
 * EffectProcessor.js
 * The central system responsible for executing all changes to the game state.
 * Implements the specifications from EffectProcessor.spec.md
 * @class
 */
export class EffectProcessor {
  /**
   * @param {VoxelGrid} voxelGrid - Voxel grid instance
   * @param {UnitManager} unitManager - Unit manager instance
   * @param {SeededRandom} [rng] - Optional seeded random number generator
   */
  constructor(voxelGrid, unitManager, rng = null) {
    /** @type {VoxelGrid} */
    this.voxelGrid = voxelGrid;
    /** @type {UnitManager} */
    this.unitManager = unitManager;
    /** @type {SeededRandom | null} */
    this.rng = rng;

    /**
     * Map of effect type to handler function
     * @type {Map<string, (def: EffectDefinition, source: Unit, target: Unit | Position) => EffectResult>}
     */
    this.handlers = new Map();

    // Register handlers
    this.handlers.set("DAMAGE", this.handleDamage.bind(this));
    this.handlers.set("HEAL", this.handleHeal.bind(this));
    this.handlers.set("APPLY_STATUS", this.handleStatus.bind(this));
    this.handlers.set("REMOVE_STATUS", this.handleStatus.bind(this));
    this.handlers.set("PUSH", this.handleMove.bind(this));
    this.handlers.set("PULL", this.handleMove.bind(this));
    this.handlers.set("TELEPORT", this.handleMove.bind(this));
    this.handlers.set("CHAIN_DAMAGE", this.handleChainDamage.bind(this));
    this.handlers.set("GIVE_AP", this.handleGiveAP.bind(this));
    this.handlers.set("HEAL_SELF", this.handleHealSelf.bind(this));
    this.handlers.set("APPLY_BUFF", this.handleApplyBuff.bind(this));
  }

  /**
   * Processes an effect definition and applies it to the target.
   * @param {EffectDefinition} effectDef - Effect definition (matches Effects.d.ts)
   * @param {Unit | null} source - Source unit (null for environmental effects)
   * @param {Unit | Position} target - Target unit or position
   * @returns {EffectResult} - Result object with success status and data
   */
  process(effectDef, source, target) {
    // Validate inputs
    if (!effectDef || !effectDef.type) {
      return {
        success: false,
        error: "Invalid effect definition",
        data: null,
      };
    }

    // Check conditions before processing
    if (!this.checkConditions(effectDef, source, target)) {
      return {
        success: false,
        error: "Conditions not met",
        data: null,
      };
    }

    // Look up handler
    const handler = this.handlers.get(effectDef.type);
    if (!handler) {
      return {
        success: false,
        error: `Unknown effect type: ${effectDef.type}`,
        data: null,
      };
    }

    // Execute handler
    try {
      return handler(effectDef, source, target);
    } catch (error) {
      return {
        success: false,
        error: error.message || "Handler execution failed",
        data: null,
      };
    }
  }

  /**
   * Checks if conditions are met for the effect to execute.
   * Uses ConditionDefinition structure: { type: ConditionType, value?: string | number | boolean }
   * @param {EffectDefinition} effectDef - Effect definition
   * @param {Unit | null} source - Source unit
   * @param {Unit | Position} target - Target unit or position
   * @returns {boolean} - True if conditions are met
   */
  checkConditions(effectDef, source, target) {
    if (!effectDef.condition) {
      return true; // No conditions means always execute
    }

    const condition = /** @type {ConditionDefinition} */ (effectDef.condition);

    // Handle condition based on type (matching ConditionDefinition structure)
    switch (condition.type) {
      case "TARGET_TAG":
        if (target && typeof target === "object" && "tags" in target) {
          const unit = /** @type {Unit} */ (target);
          const tagValue =
            typeof condition.value === "string" ? condition.value : null;
          if (!tagValue || !unit.tags || !unit.tags.includes(tagValue)) {
            return false;
          }
        }
        break;

      case "TARGET_HP_LOW":
        if (target && typeof target === "object" && "currentHealth" in target) {
          const unit = /** @type {Unit} */ (target);
          const threshold =
            typeof condition.value === "number" ? condition.value : 0.5;
          const hpPercentage = unit.currentHealth / (unit.maxHealth || 1);
          if (hpPercentage > threshold) {
            return false;
          }
        }
        break;

      case "IS_MECHANICAL":
        if (target && typeof target === "object" && "tags" in target) {
          const unit = /** @type {Unit} */ (target);
          if (!unit.tags || !unit.tags.includes("MECHANICAL")) {
            return false;
          }
        }
        break;

      // Legacy support: old format with direct properties (for backward compatibility)
      default:
        // Check target_tag (legacy format)
        if (
          condition.target_tag &&
          target &&
          typeof target === "object" &&
          "tags" in target
        ) {
          const unit = /** @type {Unit} */ (target);
          if (unit.tags && !unit.tags.includes(condition.target_tag)) {
            return false;
          }
        }

        // Check target_status (legacy format - could be condition.type === "TARGET_STATUS" in future)
        if (
          condition.target_status &&
          target &&
          typeof target === "object" &&
          "statusEffects" in target
        ) {
          const unit = /** @type {Unit} */ (target);
          const hasStatus = unit.statusEffects?.some(
            (effect) => effect.id === condition.target_status
          );
          if (!hasStatus) {
            return false;
          }
        }

        // Check hp_threshold (legacy format)
        if (
          condition.hp_threshold !== undefined &&
          target &&
          typeof target === "object" &&
          "currentHealth" in target
        ) {
          const unit = /** @type {Unit} */ (target);
          const hpPercentage = unit.currentHealth / (unit.maxHealth || 1);
          if (hpPercentage > condition.hp_threshold) {
            return false;
          }
        }
        break;
    }

    return true;
  }

  /**
   * Calculates the final power value based on base power and attribute scaling.
   * @param {EffectDefinition} effectDef - Effect definition
   * @param {Unit} source - Source unit
   * @returns {number} - Calculated power value
   */
  calculatePower(effectDef, source) {
    const basePower = effectDef.power || 0;
    const attribute = effectDef.attribute;
    const scaling = effectDef.scaling || 1.0;

    if (!attribute || !source.baseStats) {
      return basePower;
    }

    // Get attribute value from source's baseStats
    const attributeValue = source.baseStats[attribute] || 0;
    const scaledValue = attributeValue * scaling;

    return basePower + scaledValue;
  }

  /**
   * Handles DAMAGE effect.
   * @param {EffectDefinition} effectDef - Effect definition
   * @param {Unit} source - Source unit
   * @param {Unit | Position} target - Target unit or position
   * @returns {EffectResult} - Result object
   */
  handleDamage(effectDef, source, target) {
    if (!target || typeof target !== "object" || !("currentHealth" in target)) {
      return {
        success: false,
        error: "Damage target must be a Unit",
        data: null,
      };
    }

    const unit = /** @type {Unit} */ (target);

    // Calculate base damage
    let baseDamage = this.calculatePower(effectDef, source);

    // Apply conditional multiplier if condition is met
    if (effectDef.conditional_multiplier) {
      const condMult = effectDef.conditional_multiplier;
      // Check if condition is met (e.g., target has specific tag)
      if (
        condMult.condition &&
        target &&
        typeof target === "object" &&
        "tags" in target
      ) {
        const unit = /** @type {Unit} */ (target);
        if (unit.tags && unit.tags.includes(condMult.condition)) {
          baseDamage *= condMult.value;
        }
      }
    }

    // Apply defense reduction
    const defense = unit.baseStats?.defense || 0;
    let finalDamage = Math.max(0, baseDamage - defense);

    // TODO: Apply element resistance/weakness if element is specified
    // For now, we'll skip element checks

    // Apply damage
    const previousHP = unit.currentHealth;
    unit.currentHealth = Math.max(0, unit.currentHealth - finalDamage);

    return {
      success: true,
      error: null,
      data: {
        type: "DAMAGE",
        amount: finalDamage,
        previousHP,
        currentHP: unit.currentHealth,
        target: unit.id,
      },
    };
  }

  /**
   * Handles HEAL effect.
   * @param {EffectDefinition} effectDef - Effect definition
   * @param {Unit} source - Source unit
   * @param {Unit | Position} target - Target unit or position
   * @returns {EffectResult} - Result object
   */
  handleHeal(effectDef, source, target) {
    if (!target || typeof target !== "object" || !("currentHealth" in target)) {
      return {
        success: false,
        error: "Heal target must be a Unit",
        data: null,
      };
    }

    const unit = /** @type {Unit} */ (target);

    // Calculate heal amount
    const healAmount = this.calculatePower(effectDef, source);

    // Apply healing
    const previousHP = unit.currentHealth;
    unit.currentHealth = Math.min(
      unit.maxHealth || unit.currentHealth,
      unit.currentHealth + healAmount
    );

    const actualHeal = unit.currentHealth - previousHP;

    return {
      success: true,
      error: null,
      data: {
        type: "HEAL",
        amount: actualHeal,
        previousHP,
        currentHP: unit.currentHealth,
        target: unit.id,
      },
    };
  }

  /**
   * Handles APPLY_STATUS and REMOVE_STATUS effects.
   * @param {EffectDefinition} effectDef - Effect definition
   * @param {Unit} source - Source unit
   * @param {Unit | Position} target - Target unit or position
   * @returns {EffectResult} - Result object
   */
  handleStatus(effectDef, source, target) {
    if (!target || typeof target !== "object" || !("statusEffects" in target)) {
      return {
        success: false,
        error: "Status target must be a Unit",
        data: null,
      };
    }

    const unit = /** @type {Unit} */ (target);
    const statusId = effectDef.status_id;

    if (!statusId) {
      return {
        success: false,
        error: "Status effect missing status_id",
        data: null,
      };
    }

    // Initialize statusEffects array if needed
    if (!unit.statusEffects) {
      unit.statusEffects = [];
    }

    if (effectDef.type === "APPLY_STATUS") {
      // Check chance (default 1.0 = 100%)
      const chance = effectDef.chance !== undefined ? effectDef.chance : 1.0;
      const roll = this.rng ? this.rng.next() : Math.random();

      if (roll >= chance) {
        return {
          success: false,
          error: "Status effect failed chance roll",
          data: null,
        };
      }

      const duration = effectDef.duration || 1;

      // Check if status already exists
      const existingStatus = unit.statusEffects.find((s) => s.id === statusId);

      // Build status effect object with any DoT/HoT properties
      const statusEffect = {
        id: statusId,
        type: "STATUS",
        duration: duration,
      };

      // Store DoT damage if provided (for damage over time effects)
      if (effectDef.power !== undefined && effectDef.power > 0) {
        statusEffect.damage = effectDef.power;
        statusEffect.type = "DOT"; // Mark as DoT for processing
        statusEffect.element = effectDef.element; // Store element for display
      }

      // Store HoT heal if provided (for heal over time effects)
      if (effectDef.power !== undefined && effectDef.power < 0) {
        statusEffect.heal = Math.abs(effectDef.power);
        statusEffect.type = "HOT"; // Mark as HoT for processing
      }

      if (existingStatus) {
        // Refresh duration if status already exists
        existingStatus.duration = Math.max(existingStatus.duration, duration);
        // Update damage/heal if provided
        if (statusEffect.damage !== undefined) {
          existingStatus.damage = statusEffect.damage;
          existingStatus.type = "DOT";
          existingStatus.element = statusEffect.element;
        }
        if (statusEffect.heal !== undefined) {
          existingStatus.heal = statusEffect.heal;
          existingStatus.type = "HOT";
        }
      } else {
        // Add new status effect
        unit.statusEffects.push(statusEffect);
      }

      return {
        success: true,
        error: null,
        data: {
          type: "APPLY_STATUS",
          statusId,
          duration,
          target: unit.id,
        },
      };
    } else if (effectDef.type === "REMOVE_STATUS") {
      // Remove status effect
      const index = unit.statusEffects.findIndex((s) => s.id === statusId);

      if (index !== -1) {
        unit.statusEffects.splice(index, 1);
        return {
          success: true,
          error: null,
          data: {
            type: "REMOVE_STATUS",
            statusId,
            target: unit.id,
          },
        };
      } else {
        return {
          success: false,
          error: "Status effect not found on target",
          data: null,
        };
      }
    }

    return {
      success: false,
      error: "Invalid status effect type",
      data: null,
    };
  }

  /**
   * Handles CHAIN_DAMAGE effect.
   * Applies damage to primary target, then chains to nearest enemies with decay.
   * @param {EffectDefinition} effectDef - Effect definition
   * @param {Unit} source - Source unit
   * @param {Unit | Position} target - Target unit or position
   * @returns {EffectResult} - Result object
   */
  handleChainDamage(effectDef, source, target) {
    if (!target || typeof target !== "object" || !("currentHealth" in target)) {
      return {
        success: false,
        error: "Chain damage target must be a Unit",
        data: null,
      };
    }

    const primaryTarget = /** @type {Unit} */ (target);
    const results = [];

    // Step 1: Apply damage to primary target
    const primaryDamageDef = {
      type: "DAMAGE",
      power: effectDef.power,
      attribute: effectDef.attribute,
      scaling: effectDef.scaling,
      element: effectDef.element,
    };

    const primaryResult = this.handleDamage(
      primaryDamageDef,
      source,
      primaryTarget
    );
    if (primaryResult.success) {
      results.push(primaryResult.data);
    }

    // Step 2: Find nearest enemies for chaining
    const bounces = effectDef.bounces || 2;
    const chainRange = effectDef.chainRange || 3;
    const decay = effectDef.decay !== undefined ? effectDef.decay : 0.5; // Default 50% damage per bounce
    const synergyTrigger = effectDef.synergy_trigger; // e.g., "STATUS_WET"

    // Get all enemy units in range (excluding primary target)
    const enemyTeam = source
      ? source.team === "PLAYER"
        ? "ENEMY"
        : "PLAYER"
      : "ENEMY";
    const allEnemies = this.unitManager
      .getUnitsInRange(primaryTarget.position, chainRange, enemyTeam)
      .filter((unit) => {
        if (unit.id === primaryTarget.id) return false;
        // Check if unit is alive
        if (typeof unit.isAlive === "function") {
          return unit.isAlive();
        }
        return unit.currentHealth > 0;
      });

    // Sort by distance to primary target
    allEnemies.sort((a, b) => {
      const distA =
        Math.abs(a.position.x - primaryTarget.position.x) +
        Math.abs(a.position.y - primaryTarget.position.y) +
        Math.abs(a.position.z - primaryTarget.position.z);
      const distB =
        Math.abs(b.position.x - primaryTarget.position.x) +
        Math.abs(b.position.y - primaryTarget.position.y) +
        Math.abs(b.position.z - primaryTarget.position.z);
      return distA - distB;
    });

    // Take up to 'bounces' nearest enemies
    const chainTargets = allEnemies.slice(0, bounces);

    // Step 3: Apply chained damage with decay
    let currentDecay = decay;
    for (const chainTarget of chainTargets) {
      // Check for synergy trigger (e.g., WET status)
      let damageMultiplier = 1.0;
      if (synergyTrigger && chainTarget.statusEffects) {
        const hasSynergy = chainTarget.statusEffects.some(
          (effect) => effect.id === synergyTrigger
        );
        if (hasSynergy) {
          damageMultiplier = 2.0; // Double damage for synergy
        }
      }

      // Calculate chained damage: base damage * decay * synergy multiplier
      const chainDamageDef = {
        type: "DAMAGE",
        power: (effectDef.power || 0) * currentDecay * damageMultiplier,
        attribute: effectDef.attribute,
        scaling: effectDef.scaling,
        element: effectDef.element,
      };

      const chainResult = this.handleDamage(
        chainDamageDef,
        source,
        chainTarget
      );
      if (chainResult.success) {
        results.push(chainResult.data);
      }

      // Apply further decay for next bounce (optional - could be cumulative or fixed)
      currentDecay *= decay;
    }

    return {
      success: true,
      error: null,
      data: {
        type: "CHAIN_DAMAGE",
        primaryTarget: primaryTarget.id,
        chainTargets: chainTargets.map((u) => u.id),
        results: results,
      },
    };
  }

  /**
   * Handles PUSH, PULL, and TELEPORT effects.
   * @param {EffectDefinition} effectDef - Effect definition
   * @param {Unit} source - Source unit
   * @param {Unit | Position} target - Target unit or position
   * @returns {EffectResult} - Result object
   */
  handleMove(effectDef, source, target) {
    if (effectDef.type === "TELEPORT") {
      // For TELEPORT, source is the unit being teleported, target is destination or reference
      if (!source || !source.position) {
        return {
          success: false,
          error: "TELEPORT requires a source unit",
          data: null,
        };
      }

      const unit = source; // Source unit is the one being teleported
      const oldPosition = { ...unit.position };

      // Determine destination
      let destination;
      if (effectDef.destination === "BEHIND_TARGET") {
        // Calculate position behind target relative to source
        if (!target || typeof target !== "object" || !("position" in target)) {
          return {
            success: false,
            error: "TELEPORT BEHIND_TARGET requires a target unit",
            data: null,
          };
        }
        const targetUnit = /** @type {Unit} */ (target);
        const dx = targetUnit.position.x - unit.position.x;
        const dz = targetUnit.position.z - unit.position.z;
        destination = {
          x: targetUnit.position.x + dx,
          y: targetUnit.position.y,
          z: targetUnit.position.z + dz,
        };
      } else {
        // Default: target is the destination position
        if (!target || typeof target !== "object" || !("x" in target)) {
          return {
            success: false,
            error: "TELEPORT requires a destination position",
            data: null,
          };
        }
        destination = /** @type {Position} */ (target);
      }

      // Validate destination (must be Air and Unoccupied)
      if (this.voxelGrid.isSolid(destination)) {
        return {
          success: false,
          error: "Teleport destination is solid",
          data: null,
        };
      }

      if (this.voxelGrid.isOccupied(destination)) {
        return {
          success: false,
          error: "Teleport destination is occupied",
          data: null,
        };
      }

      // Move unit
      if (this.voxelGrid.moveUnit(unit, destination, { force: true })) {
        return {
          success: true,
          error: null,
          data: {
            type: "TELEPORT",
            oldPosition,
            newPosition: destination,
            target: unit.id,
          },
        };
      }

      return {
        success: false,
        error: "Failed to move unit",
        data: null,
      };
    } else if (effectDef.type === "PUSH" || effectDef.type === "PULL") {
      // For PUSH/PULL, target is the unit being moved
      if (!target || typeof target !== "object" || !("position" in target)) {
        return {
          success: false,
          error: "PUSH/PULL target must be a Unit",
          data: null,
        };
      }

      const unit = /** @type {Unit} */ (target);
      const oldPosition = { ...unit.position };
      // Calculate direction from source to target (or opposite for PULL)
      if (!source || !source.position) {
        return {
          success: false,
          error: "PUSH/PULL requires a source unit",
          data: null,
        };
      }

      const force = effectDef.force || 1;

      // Calculate direction vector from source to target
      const dx = unit.position.x - source.position.x;
      const dz = unit.position.z - source.position.z;

      // Calculate distance
      const distance = Math.sqrt(dx * dx + dz * dz);
      if (distance === 0) {
        return {
          success: false,
          error: "Source and target are at same position",
          data: null,
        };
      }

      // Normalize direction
      const normalizedX = dx / distance;
      const normalizedZ = dz / distance;

      // For PUSH: move away from source (same direction as vector)
      // For PULL: move toward source (opposite direction)
      const pushDirectionX =
        effectDef.type === "PULL" ? -normalizedX : normalizedX;
      const pushDirectionZ =
        effectDef.type === "PULL" ? -normalizedZ : normalizedZ;

      // Calculate new position
      const newPosition = {
        x: Math.round(unit.position.x + pushDirectionX * force),
        y: unit.position.y,
        z: Math.round(unit.position.z + pushDirectionZ * force),
      };

      // Check if destination or any intermediate position is solid (CoA 4: Physics Safety)
      // Check all positions from current to destination
      for (let i = 1; i <= force; i++) {
        const checkPos = {
          x: Math.round(unit.position.x + pushDirectionX * i),
          y: unit.position.y,
          z: Math.round(unit.position.z + pushDirectionZ * i),
        };
        if (this.voxelGrid.isSolid(checkPos)) {
          // Optionally apply "Smash" damage (for now, just fail)
          return {
            success: false,
            error: "Push/Pull blocked by solid terrain",
            data: null,
          };
        }
      }

      // Check if destination is occupied
      if (this.voxelGrid.isOccupied(newPosition)) {
        return {
          success: false,
          error: "Push/Pull destination is occupied",
          data: null,
        };
      }

      // Move unit
      if (this.voxelGrid.moveUnit(unit, newPosition)) {
        return {
          success: true,
          error: null,
          data: {
            type: effectDef.type,
            oldPosition,
            newPosition,
            target: unit.id,
          },
        };
      }

      return {
        success: false,
        error: "Failed to move unit",
        data: null,
      };
    }

    return {
      success: false,
      error: "Invalid move effect type",
      data: null,
    };
  }

  /**
   * Handles GIVE_AP effect.
   * Restores Action Points to the target unit.
   * @param {EffectDefinition} effectDef - Effect definition
   * @param {Unit} source - Source unit
   * @param {Unit} target - Target unit
   * @returns {EffectResult} - Result object
   */
  handleGiveAP(effectDef, source, target) {
    if (!target || typeof target !== "object" || !("currentAP" in target)) {
      return {
        success: false,
        error: "GIVE_AP target must be a Unit",
        data: null,
      };
    }

    const unit = /** @type {Unit} */ (target);
    const amount = effectDef.amount || effectDef.power || 0;

    const previousAP = unit.currentAP;
    unit.currentAP = (unit.currentAP || 0) + amount;

    return {
      success: true,
      error: null,
      data: {
        type: "GIVE_AP",
        amount: amount,
        previousAP,
        currentAP: unit.currentAP,
        target: unit.id,
      },
    };
  }

  /**
   * Handles HEAL_SELF effect.
   * Heals the source unit for a percentage of healing dealt.
   * @param {EffectDefinition} effectDef - Effect definition
   * @param {Unit} source - Source unit
   * @param {Unit} target - Target unit (usually ignored, heals source)
   * @returns {EffectResult} - Result object
   */
  handleHealSelf(effectDef, source, target) {
    if (!source || typeof source !== "object" || !("currentHealth" in source)) {
      return {
        success: false,
        error: "HEAL_SELF requires a source unit",
        data: null,
      };
    }

    const unit = source; // Heal the source unit
    const percentage = effectDef.percentage || 0.1;

    // Get heal amount from context (should be passed via effectDef.healAmount)
    const healAmount = effectDef.healAmount || 0;
    const healSelfAmount = Math.floor(healAmount * percentage);

    if (healSelfAmount <= 0) {
      return {
        success: false,
        error: "No healing to reflect",
        data: null,
      };
    }

    const previousHP = unit.currentHealth;
    unit.currentHealth = Math.min(
      unit.maxHealth,
      unit.currentHealth + healSelfAmount
    );

    return {
      success: true,
      error: null,
      data: {
        type: "HEAL_SELF",
        amount: healSelfAmount,
        previousHP,
        currentHP: unit.currentHealth,
        target: unit.id,
      },
    };
  }

  /**
   * Handles APPLY_BUFF effect.
   * Applies a temporary stat modification to the target unit.
   * @param {EffectDefinition} effectDef - Effect definition
   * @param {Unit} source - Source unit
   * @param {Unit} target - Target unit
   * @returns {EffectResult} - Result object
   */
  handleApplyBuff(effectDef, source, target) {
    if (!target || typeof target !== "object" || !("baseStats" in target)) {
      return {
        success: false,
        error: "APPLY_BUFF target must be a Unit",
        data: null,
      };
    }

    const unit = /** @type {Unit} */ (target);
    const stat = effectDef.stat;
    const value = effectDef.value || 0;
    const duration = effectDef.duration || 1;

    if (!stat) {
      return {
        success: false,
        error: "APPLY_BUFF requires a stat name",
        data: null,
      };
    }

    // Initialize buffs array if it doesn't exist
    if (!unit.buffs) {
      unit.buffs = [];
    }

    // Add buff to unit
    const buff = {
      id: `buff_${stat}_${Date.now()}`,
      stat: stat,
      value: value,
      duration: duration,
      source: source ? source.id : null,
    };

    unit.buffs.push(buff);

    return {
      success: true,
      error: null,
      data: {
        type: "APPLY_BUFF",
        stat: stat,
        value: value,
        duration: duration,
        target: unit.id,
      },
    };
  }
}

/**
 * @typedef {Object} EffectResult
 * @property {boolean} success - Whether the effect was successful
 * @property {string | null} error - Error message if failed
 * @property {Object | null} data - Result data
 */
