import type { CombatStore, CombatState } from './combat-state.types';
import type { SpellState, EnemyState, EquipmentInstance, FloorState, RuntimeActiveGolem } from '../types';
import { applyOnHitEffect, processDoTPhase } from './dot-runtime';
import { getFloorMaxHP, getFloorElement, getMultiElementBonus, calcDamage, canAffordSpellCost, deductSpellCost } from '../utils';
import { computeDisciplineEffects } from '../effects/discipline-effects';
import { processRoomEnchantmentPhase } from './combat-room-enchantments';
import { processGolemMaintenance, processGolemManaRegen } from './golem-combat-actions';
import { processGolemAttacksFromStore } from './golem-combat-helpers';
import { applyDamageToRoom } from './combat-damage';
import { getGuardianForFloor } from '../data/guardian-encounters';
import { processInvocationTick } from './combat-invocation';
import { processMeleeTick } from './combat-melee';
import { computeChannelStats, applyChannelDrain, getChannelMultiplier } from './combat-channel';
import { SPELLS_DEF, HOURS_PER_TICK } from '../constants';
// ─── Result Type ───────────────────────────────────────────────────────────────
/**
 * Create a default CombatTickResult for safe fallback on error.
 */
function makeDefaultCombatTickResult(
  rawMana: number,
  elements: Record<string, { current: number; max: number; unlocked: boolean }>,
  state: CombatState,
  activeGolems: RuntimeActiveGolem[],
): CombatTickResult {
  return {
    rawMana,
    elements,
    logMessages: [],
    totalManaGathered: 0,
    currentFloor: state.currentFloor,
    floorHP: state.floorHP,
    floorMaxHP: state.floorMaxHP,
    maxFloorReached: state.maxFloorReached,
    castProgress: state.castProgress,
    weaponCastProgress: state.weaponCastProgress,
    equipmentSpellStates: state.equipmentSpellStates,
    activeGolems,
    meleeSwordProgress: state.meleeSwordProgress,
    currentRoom: state.currentRoom,
  };
}

export interface CombatTickResult {
  rawMana: number;
  elements: Record<string, { current: number; max: number; unlocked: boolean }>;
  logMessages: string[];
  totalManaGathered: number;
  currentFloor: number;
  floorHP: number;
  floorMaxHP: number;
  maxFloorReached: number;
  castProgress: number;
  weaponCastProgress: Record<string, number>;
  equipmentSpellStates: CombatState['equipmentSpellStates'];
  activeGolems: RuntimeActiveGolem[];
  meleeSwordProgress: Record<string, number>;
  currentRoom: FloorState;
}

// ─── Main Combat Tick ──────────────────────────────────────────────────────────

export function processCombatTick(
  get: () => CombatStore,
  set: (state: Partial<CombatState>) => void,
  rawMana: number,
  elements: Record<string, { current: number; max: number; unlocked: boolean }>,
  _maxMana: number,
  attackSpeedMult: number,
  onFloorCleared: (floor: number, wasGuardian: boolean) => void,
  onDamageDealt: (damage: number, skipSpecials?: boolean) => {
    rawMana: number;
    elements: Record<string, { current: number; max: number; unlocked: boolean }>;
    modifiedDamage?: number;
  },
  signedPacts: number[],
  golemancyState: { activeGolems: RuntimeActiveGolem[] },
  golemApplyDamageToRoom: (dmg: number) => { floorHP: number; floorMaxHP: number; roomCleared: boolean },
  applyEnemyDefenses: (
    dmg: number,
    enemy: EnemyState | null,
    roomType: string,
    addLog: (msg: string) => void,
    bypassArmor?: boolean,
    bypassBarrier?: boolean,
  ) => number,
  equippedSwords?: Record<string, EquipmentInstance>,
  pactAffinityUpgrade?: number,
): CombatTickResult {
  const state = get();
  const logMessages: string[] = [];
  let totalManaGathered = 0;

  if (state.currentAction !== 'climb' || state.isDescentComplete) {
    return makeDefaultCombatTickResult(rawMana, elements, state, golemancyState.activeGolems);
  }

  try {
    // ─── Transference Channel: update stats + drain ──────────────────────
    const channelStats = computeChannelStats();
    set({
      channelSpeedMultiplier: channelStats.speedMultiplier,
      channelDrainRate: channelStats.drainRate,
    });
    if (state.isChanneling) {
      applyChannelDrain(get, set, channelStats.drainRate);
    }

    // ─── Golem maintenance (spec §13) ──────────────────────────────────────
    const golemDesigns = state.golemancy.golemDesigns || {};
    const maintenanceResult = processGolemMaintenance(
      golemancyState.activeGolems,
      golemDesigns,
      rawMana,
      elements,
    );
    let activeGolems = maintenanceResult.maintainedGolems;
    rawMana = maintenanceResult.rawMana;
    elements = maintenanceResult.elements;
    logMessages.push(...maintenanceResult.logMessages);

    // ─── Golem mana regen (spec §12) ───────────────────────────────────────
    activeGolems = processGolemManaRegen(activeGolems, golemDesigns);

    // Write maintained golems back immediately so tick state stays consistent
    set({ golemancy: { ...state.golemancy, activeGolems } });

    const spellId = state.activeSpell;
    const spellDef = SPELLS_DEF[spellId];

    let floorHP = state.floorHP;
    let currentFloor = state.currentFloor;
    let floorMaxHP = state.floorMaxHP;
    const weaponCastProgress = { ...state.weaponCastProgress };
    let currentRoom = state.currentRoom;
    const updatedEquipmentSpellStates = [...state.equipmentSpellStates];

    // ─── Spell casting (only when a valid spell is configured) ────────────────
    if (spellDef) {
      const disciplineEffects = computeDisciplineEffects();
      const totalAttackSpeed = attackSpeedMult;
      const spellCastSpeed = spellDef.castSpeed || 1;
      const progressPerTick = HOURS_PER_TICK * spellCastSpeed * totalAttackSpeed;
      const isSpellAoe = !!spellDef.isAoe;

      // Per-weapon cast progress for primary spell (spec §3.1, D-01 fix)
      // Fall back to legacy castProgress for backward compatibility
      weaponCastProgress['primary'] = ((weaponCastProgress['primary'] ?? state.castProgress) || 0) + progressPerTick;

      // Process complete casts for active spell
      let safetyCounter = 0;
      const MAX_CASTS_PER_TICK = 100;
      while (weaponCastProgress['primary'] >= 1 && canAffordSpellCost(spellDef.cost, rawMana, elements) && floorHP > 0 && safetyCounter < MAX_CASTS_PER_TICK) {
        const afterCost = deductSpellCost(spellDef.cost, rawMana, elements);
        rawMana = afterCost.rawMana;
        elements = afterCost.elements;

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

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

        if (!Number.isFinite(finalDamage)) {
          logMessages.push('⚠️ Combat stopped: invalid damage value');
          break;
        }

        // Apply damage per-enemy (spec §3.2)
        const roomResult = applyDamageToRoom(get, set, finalDamage, isSpellAoe, spellDef.aoeTargets);
        floorHP = roomResult.floorHP;
        floorMaxHP = roomResult.floorMaxHP;
        currentRoom = get().currentRoom;
        weaponCastProgress['primary'] -= 1;
        safetyCounter++;

        applyOnHitEffect(get, set, spellId, logMessages);
        currentRoom = get().currentRoom;

        if (roomResult.roomCleared) {
          const guardian = getGuardianForFloor(currentFloor);
          onFloorCleared(currentFloor, !!guardian);
          get().advanceRoomOrFloor();
          const newState = get();
          currentFloor = newState.currentFloor;
          floorMaxHP = newState.floorMaxHP;
          floorHP = newState.floorHP;
          currentRoom = newState.currentRoom;
          weaponCastProgress['primary'] = 0;
          if (guardian) {
            logMessages.push(`⚔️ ${guardian.name} defeated!`);
          } else if (currentFloor % 5 === 0) {
            logMessages.push(`🗺️ Floor ${currentFloor - 1} cleared!`);
          }
        }
      }

      // Process equipment spell states
      for (let i = 0; i < updatedEquipmentSpellStates.length; i++) {
        const eSpell = updatedEquipmentSpellStates[i];
        const eSpellDef = SPELLS_DEF[eSpell.spellId];
        if (!eSpellDef) continue;

        const isESpellAoe = !!eSpellDef.isAoe;
        const eSpellCastSpeed = eSpellDef.castSpeed || 1;
        const channelMult = getChannelMultiplier(get);
        const eProgressPerTick = HOURS_PER_TICK * eSpellCastSpeed * totalAttackSpeed * channelMult;
        let eCastProgress = (eSpell.castProgress || 0) + eProgressPerTick;

        let eSafetyCounter = 0;
        const MAX_E_CASTS_PER_TICK = 100;
        while (eCastProgress >= 1 && canAffordSpellCost(eSpellDef.cost, rawMana, elements) && floorHP > 0 && eSafetyCounter < MAX_E_CASTS_PER_TICK) {
          const eAfterCost = deductSpellCost(eSpellDef.cost, rawMana, elements);
          rawMana = eAfterCost.rawMana;
          elements = eAfterCost.elements;

          const eFloorElement = getFloorElement(currentFloor);
          const eBaseDamage = calcDamage({ signedPacts }, eSpell.spellId, undefined, disciplineEffects);
          const eGuardian = getGuardianForFloor(currentFloor);
          const eFloorElems = eGuardian && eGuardian.element.length > 0 ? eGuardian.element : [eFloorElement];
          const eMultiElemBonus = getMultiElementBonus(eSpellDef.elem, eFloorElems);
          const eDamage = eBaseDamage * eMultiElemBonus;

          const eResult = onDamageDealt(eDamage);
          rawMana = eResult.rawMana;
          elements = eResult.elements;
          const eFinalDamage = eResult.modifiedDamage || eDamage;

          if (!Number.isFinite(eFinalDamage)) break;

          // Apply damage per-enemy (spec §3.2)
          const eRoomResult = applyDamageToRoom(get, set, eFinalDamage, isESpellAoe, eSpellDef.aoeTargets);
          floorHP = eRoomResult.floorHP;
          floorMaxHP = eRoomResult.floorMaxHP;
          currentRoom = get().currentRoom;
          eCastProgress -= 1;
          eSafetyCounter++;

          if (eRoomResult.roomCleared) {
            const eGuardian = getGuardianForFloor(currentFloor);
            onFloorCleared(currentFloor, !!eGuardian);
            get().advanceRoomOrFloor();
            const newState = get();
            currentFloor = newState.currentFloor;
            floorMaxHP = newState.floorMaxHP;
            floorHP = newState.floorHP;
            currentRoom = newState.currentRoom;
            eCastProgress = 0;
            if (eGuardian) {
              logMessages.push(`⚔️ ${eGuardian.name} defeated!`);
            } else if (currentFloor % 5 === 0) {
              logMessages.push(`🗺️ Floor ${currentFloor - 1} cleared!`);
            }
            break;
          }
        }

        updatedEquipmentSpellStates[i] = { ...eSpell, castProgress: eCastProgress % 1 };
      }
    }

    // ─── Invocation system (spec §10.1) ───────────────────────────────────
    const invResult = processInvocationTick(
      { get, set, rawMana, elements, attackSpeedMult, signedPacts, currentRoom, floorHP, pactAffinityUpgrade: pactAffinityUpgrade || 0 },
      onFloorCleared,
      onDamageDealt,
    );
    rawMana = invResult.rawMana;
    elements = invResult.elements;
    floorHP = invResult.floorHP;
    floorMaxHP = invResult.floorMaxHP;
    currentFloor = invResult.currentFloor;
    currentRoom = invResult.currentRoom;
    logMessages.push(...invResult.logMessages);
    set({ invocationCharge: invResult.invocationCharge, activeInvocation: invResult.activeInvocation });

    // ─── Melee sword attacks (spec §3.1, §4.3) ────────────────────────────
    const meleeResult = processMeleeTick(
      { get, set, rawMana, elements, attackSpeedMult, equippedSwords: equippedSwords || {}, floorHP, currentRoom },
      applyEnemyDefenses,
      onFloorCleared,
    );
    rawMana = meleeResult.rawMana;
    elements = meleeResult.elements;
    floorHP = meleeResult.floorHP;
    floorMaxHP = meleeResult.floorMaxHP;
    currentFloor = meleeResult.currentFloor;
    currentRoom = meleeResult.currentRoom;
    const updatedMeleeSwordProgress = meleeResult.meleeSwordProgress;
    logMessages.push(...meleeResult.logMessages);

    // ─── Golem attacks (spec §11) ───────────────────────────────────────────
    if (activeGolems.length > 0 && floorHP > 0) {
      const golemResult = processGolemAttacksFromStore(
        activeGolems,
        golemDesigns,
        onDamageDealt,
        golemApplyDamageToRoom,
        getFloorElement(currentFloor),
        get,
        set,
        rawMana,
        elements,
      );
      rawMana = golemResult.rawMana;
      elements = golemResult.elements;
      activeGolems = golemResult.activeGolems;
      logMessages.push(...golemResult.logMessages);

      const postGolemState = get();
      floorHP = postGolemState.floorHP;
      floorMaxHP = postGolemState.floorMaxHP;
      currentFloor = postGolemState.currentFloor;
      currentRoom = postGolemState.currentRoom;
    }

    // ─── DoT/Debuff tick processing (spec §6.3) ─────────────────────────────
    if (floorHP > 0) {
      processDoTPhase(get, set, applyEnemyDefenses, logMessages);
      // processDoTPhase writes per-enemy HP to currentRoom.
      // Recalculate floorHP from updated room state.
      const postDoTState = get();
      currentRoom = postDoTState.currentRoom;
      floorHP = currentRoom.enemies.reduce((sum, e) => sum + e.hp, 0);
      set({ floorHP });

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

    // ─── Room Enchantment tick (after DoT phase) ────────────────────────────
    if (floorHP > 0 && state.currentAction === 'climb') {
      const roomEnchantResult = processRoomEnchantmentPhase({
        get,
        set,
        floorHP,
        currentRoom,
        currentFloor,
        rawMana,
        logMessages,
        onFloorCleared,
      });
      floorHP = roomEnchantResult.floorHP;
      floorMaxHP = roomEnchantResult.floorMaxHP;
      currentFloor = roomEnchantResult.currentFloor;
      currentRoom = roomEnchantResult.currentRoom;
      logMessages.push(...roomEnchantResult.logMessages);
    }

    const newMaxFloorReached = Math.max(state.maxFloorReached, currentFloor);

    return {
      rawMana,
      elements,
      logMessages,
      totalManaGathered,
      currentFloor,
      floorHP,
      floorMaxHP,
      maxFloorReached: newMaxFloorReached,
      castProgress: weaponCastProgress['primary'] ?? state.castProgress,
      weaponCastProgress,
      equipmentSpellStates: updatedEquipmentSpellStates,
      activeGolems,
      meleeSwordProgress: updatedMeleeSwordProgress,
      currentRoom,
    };
  } catch (error) {
    const errorMsg = error instanceof Error ? error.message : String(error);
    logMessages.push(`⚠️ Combat error: ${errorMsg}`);
    return makeDefaultCombatTickResult(rawMana, elements, state, golemancyState.activeGolems);
  }
}

// Helper function to create initial spells
export function makeInitialSpells(spellsToKeep: string[] = []): Record<string, SpellState> {
  const startSpells: Record<string, SpellState> = {
    manaBolt: { learned: true, level: 1, studyProgress: 0 },
  };

  for (const spellId of spellsToKeep) {
    startSpells[spellId] = { learned: true, level: 1, studyProgress: 0 };
  }

  return startSpells;
}
