// ─── Invocation Combat Processing ──────────────────────────────────────────────
// Extracted from combat-actions.ts to stay under the 400-line file limit.
// Handles charge drain, spell selection, casting, auto-activate, and auto-end.

import { SPELLS_DEF, HOURS_PER_TICK } from '../constants';
import { getGuardianForFloor } from '../data/guardian-encounters';
import type { CombatStore, CombatState } from './combat-state.types';
import type { FloorState } from '../types';
import { getFloorElement, getMultiElementBonus, calcDamage } from '../utils';
import { applyDamageToRoom } from './combat-damage';
import { computeDisciplineEffects } from '../effects/discipline-effects';
import {
  selectInvocationGuardian,
  getGuardianSpellbook,
  selectInvocationSpell,
  deductInvocationSpellCost,
  computeChargeFillRate,
  computeCastSpeedBonus,
  computeCostMultiplier,
  computeDrainRateMultiplier,
  type ActiveInvocation,
} from '../utils/invocation-utils';

export interface InvocationTickParams {
  get: () => CombatStore;
  set: (s: Partial<CombatState>) => void;
  rawMana: number;
  elements: Record<string, { current: number; max: number; unlocked: boolean }>;
  attackSpeedMult: number;
  signedPacts: number[];
  currentRoom: FloorState;
  floorHP: number;
  pactAffinityUpgrade: number;
}

export interface InvocationTickResult {
  rawMana: number;
  elements: Record<string, { current: number; max: number; unlocked: boolean }>;
  floorHP: number;
  floorMaxHP: number;
  currentFloor: number;
  currentRoom: FloorState;
  invocationCharge: number;
  activeInvocation: ActiveInvocation | null;
  logMessages: string[];
}

export function processInvocationTick(
  params: InvocationTickParams,
  onFloorCleared: (floor: number, wasGuardian: boolean) => void,
  onDamageDealt: (damage: number) => {
    rawMana: number;
    elements: Record<string, { current: number; max: number; unlocked: boolean }>;
    modifiedDamage?: number;
  },
): InvocationTickResult {
  const { get, set, rawMana: startRawMana, elements: startElements, attackSpeedMult, signedPacts, currentRoom, floorHP: startFloorHP, pactAffinityUpgrade } = params;
  let rawMana = startRawMana;
  let elements = startElements;
  let floorHP = startFloorHP;
  let floorMaxHP = params.get().floorMaxHP;
  let currentFloor = params.get().currentFloor;
  let currentRoomState = currentRoom;
  const logMessages: string[] = [];
  let invocationCharge = params.get().invocationCharge;
  let activeInvocation = params.get().activeInvocation;

  if (activeInvocation !== null) {
    // ── Invocation is active: drain charge and process cast ──
    const invResult = processActiveInvocation({
      get, set, rawMana, elements, attackSpeedMult, signedPacts, pactAffinityUpgrade,
      currentFloor, floorHP, floorMaxHP, currentRoom: currentRoomState,
      invocationCharge, activeInvocation, logMessages,
    }, onFloorCleared, onDamageDealt);

    rawMana = invResult.rawMana;
    elements = invResult.elements;
    floorHP = invResult.floorHP;
    floorMaxHP = invResult.floorMaxHP;
    currentFloor = invResult.currentFloor;
    currentRoomState = invResult.currentRoom;
    invocationCharge = invResult.invocationCharge;
    activeInvocation = invResult.activeInvocation;
  } else if (invocationCharge >= 100) {
    // ── Try to auto-activate ──
    const livingEnemies = currentRoomState.enemies.filter((e) => e.hp > 0);
    if (livingEnemies.length > 0 && signedPacts.length > 0) {
      const enemyElements = currentRoomState.enemies
        .filter((e) => e.hp > 0)
        .map((e) => e.element);
      const uniqueEnemyElements = Array.from(new Set(enemyElements));

      const bestFloor = selectInvocationGuardian(signedPacts, uniqueEnemyElements);
      if (bestFloor !== null) {
        const guardian = getGuardianForFloor(bestFloor);
        if (guardian) {
          const spellbook = getGuardianSpellbook(guardian);
          const disciplineEffects = computeDisciplineEffects();
          const costMult = computeCostMultiplier(disciplineEffects);
          const spellSelection = selectInvocationSpell(spellbook, rawMana, elements, costMult);

          if (spellSelection) {
            activeInvocation = {
              guardianFloor: bestFloor,
              spellId: spellSelection.spellId,
              element: spellSelection.element,
              castProgress: 0,
            };
            logMessages.push(`\u{1F49C} Invoking ${guardian.name}'s power!`);
          }
        }
      }
    }
  } else {
    // ── Not invoking and charge < 100: fill charge ──
    const disciplineEffects = computeDisciplineEffects();
    const chargeRateBonus = disciplineEffects.bonuses.invocationChargeRateBonus || 0;
    const fillRate = computeChargeFillRate(signedPacts.length, chargeRateBonus);
    invocationCharge = Math.min(100, invocationCharge + fillRate);
  }

  return {
    rawMana,
    elements,
    floorHP,
    floorMaxHP,
    currentFloor,
    currentRoom: currentRoomState,
    invocationCharge,
    activeInvocation,
    logMessages,
  };
}

interface ActiveInvParams {
  get: () => CombatStore;
  set: (s: Partial<CombatState>) => void;
  rawMana: number;
  elements: Record<string, { current: number; max: number; unlocked: boolean }>;
  attackSpeedMult: number;
  signedPacts: number[];
  pactAffinityUpgrade: number;
  currentFloor: number;
  floorHP: number;
  floorMaxHP: number;
  currentRoom: FloorState;
  invocationCharge: number;
  activeInvocation: ActiveInvocation;
  logMessages: string[];
}

function processActiveInvocation(
  p: ActiveInvParams,
  onFloorCleared: (floor: number, wasGuardian: boolean) => void,
  onDamageDealt: (damage: number) => {
    rawMana: number;
    elements: Record<string, { current: number; max: number; unlocked: boolean }>;
    modifiedDamage?: number;
  },
): InvocationTickResult {
  let { get, set, rawMana, elements, attackSpeedMult, signedPacts, pactAffinityUpgrade } = p;
  let { currentFloor, floorHP, floorMaxHP, currentRoom } = p;
  let { invocationCharge, activeInvocation } = p;
  const logMessages = p.logMessages;

  const invSpellDef = SPELLS_DEF[activeInvocation.spellId];
  if (!invSpellDef || floorHP <= 0) {
    return { rawMana, elements, floorHP, floorMaxHP, currentFloor, currentRoom, invocationCharge, activeInvocation, logMessages };
  }

  const disciplineEffects = computeDisciplineEffects();
  const drainMult = computeDrainRateMultiplier(disciplineEffects);
  const drainPerTick = computeDrainPerTick(invSpellDef.cost.amount, drainMult);
  invocationCharge = Math.max(0, invocationCharge - drainPerTick);

  // AC-12/AC-13: Apply pact affinity cast speed bonus to invocation spells only
  // pactAffinityUpgrade is the prestige level (0-9), each level = +0.1; pactAffinityBonus comes from discipline
  const pactAffinity = pactAffinityUpgrade * 0.1 + (disciplineEffects.bonuses.pactAffinityBonus || 0);
  const castSpeedBonus = computeCastSpeedBonus(pactAffinity);
  const effectiveAttackSpeed = attackSpeedMult * (1 + castSpeedBonus);
  const invCastSpeed = invSpellDef.castSpeed || 1;
  const invProgressPerTick = HOURS_PER_TICK * invCastSpeed * effectiveAttackSpeed;
  const newCastProgress = activeInvocation.castProgress + invProgressPerTick;

  if (newCastProgress >= 1 && floorHP > 0) {
    // Cast completes: deduct mana at effective cost multiplier
    const costMult = computeCostMultiplier(disciplineEffects);
    const afterCost = deductInvocationSpellCost(
      activeInvocation.spellId, costMult, rawMana, elements,
    );

    if (afterCost.rawMana !== rawMana || afterCost.elements !== elements) {
      rawMana = afterCost.rawMana;
      elements = afterCost.elements;

      // Calculate damage
      const floorElement = getFloorElement(currentFloor);
      const guardian = getGuardianForFloor(currentFloor);
      const floorElems = guardian && guardian.element.length > 0 ? guardian.element : [floorElement];
      const multiElemBonus = getMultiElementBonus(activeInvocation.element, floorElems);
      const baseDamage = calcDamage({ signedPacts }, activeInvocation.spellId, undefined, disciplineEffects);
      const invDamage = baseDamage * multiElemBonus;

      const result = onDamageDealt(invDamage);
      rawMana = result.rawMana;
      elements = result.elements;
      const finalDamage = result.modifiedDamage || invDamage;

      if (Number.isFinite(finalDamage)) {
        const roomResult = applyDamageToRoom(get, set, finalDamage, !!invSpellDef.isAoe, invSpellDef.aoeTargets);
        floorHP = roomResult.floorHP;
        floorMaxHP = roomResult.floorMaxHP;
        currentRoom = get().currentRoom;

        if (roomResult.roomCleared) {
          onFloorCleared(currentFloor, !!getGuardianForFloor(currentFloor));
          get().advanceRoomOrFloor();
          const newState = get();
          currentFloor = newState.currentFloor;
          floorMaxHP = newState.floorMaxHP;
          floorHP = newState.floorHP;
          currentRoom = newState.currentRoom;
        }
      }
    }

    // Re-evaluate spell selection
    const currentGuardian = getGuardianForFloor(activeInvocation.guardianFloor);
    if (currentGuardian && floorHP > 0) {
      const spellbook = getGuardianSpellbook(currentGuardian);
      const newCostMult = computeCostMultiplier(disciplineEffects);
      const newSelection = selectInvocationSpell(spellbook, rawMana, elements, newCostMult);

      if (newSelection) {
        activeInvocation = {
          ...activeInvocation,
          spellId: newSelection.spellId,
          element: newSelection.element,
          castProgress: newCastProgress - 1,
        };
      } else {
        // No affordable spell: end invocation
        activeInvocation = null;
        logMessages.push(`\u{1F49C} Invocation ends. ${currentGuardian.name}'s power fades.`);
      }
    } else {
      activeInvocation = { ...activeInvocation, castProgress: newCastProgress - 1 };
    }
  } else {
    activeInvocation = { ...activeInvocation, castProgress: newCastProgress };
  }

  // Check charge depleted
  if (invocationCharge <= 0 && activeInvocation !== null) {
    const g = getGuardianForFloor(activeInvocation.guardianFloor);
    activeInvocation = null;
    logMessages.push(`\u{1F49C} Invocation ends. ${g?.name || 'Guardian'}'s power fades.`);
  }

  // Check room cleared
  if (floorHP <= 0 && activeInvocation !== null) {
    const g = getGuardianForFloor(activeInvocation.guardianFloor);
    activeInvocation = null;
    logMessages.push(`\u{1F49C} Invocation ends. ${g?.name || 'Guardian'}'s power fades.`);
  }

  return { rawMana, elements, floorHP, floorMaxHP, currentFloor, currentRoom, invocationCharge, activeInvocation, logMessages };
}
