// ─── Combat Utilities ────────────────────────────────────────────────────────

import type { SpellCost, EquipmentInstance } from '../types';
import type { DisciplineBonuses } from './mana-utils';
import { SPELLS_DEF, ELEMENT_OPPOSITES, INCURSION_START_DAY, MAX_DAY } from '../constants';
import { ENCHANTMENT_EFFECTS } from '../data/enchantment-effects';
import { getGuardianForFloor } from '../data/guardian-encounters';
import { STATIC_GUARDIANS as BASE_GUARDIANS } from '../data/guardian-data';

// ─── Damage Calculation Params ──────────────────────────────────────────────

export interface DamageCalcParams {
  signedPacts: number[];
}

// ─── Insight Calculation Params ─────────────────────────────────────────────

export interface InsightCalcParams {
  maxFloorReached: number;
  totalManaGathered: number;
  signedPacts: number[];
  prestigeUpgrades: Record<string, number>;
}

// ─── DPS Calculation Params ─────────────────────────────────────────────────

export interface DPSCalcParams {
  signedPacts: number[];
  equippedInstances: Record<string, string | null>;
  equipmentInstances: Record<string, EquipmentInstance>;
  spells: Record<string, { learned: boolean; level: number }>;
  prestigeUpgrades: Record<string, number>;
}

// ─── Elemental Damage Bonus ──────────────────────────────────────────────────
// +50% if spell element opposes floor element (super effective)
// -25% if spell element matches its own opposite (weak)
export function getElementalBonus(spellElem: string, floorElem: string): number {
  if (spellElem === 'raw') return 1.0; // Raw mana has no elemental bonus

  if (spellElem === floorElem) return 1.25; // Same element: +25% damage

  // Check for super effective: spellElem is in floorElem's opposites list
  const floorOpposites = ELEMENT_OPPOSITES[floorElem];
  if (floorOpposites && floorOpposites.includes(spellElem)) return 1.5; // Super effective: +50% damage

  // Check for weak: floorElem is in spellElem's opposites list
  const spellOpposites = ELEMENT_OPPOSITES[spellElem];
  if (spellOpposites && spellOpposites.includes(floorElem)) return 0.75; // Weak: -25% damage

  return 1.0; // Neutral
}

/**
 * Get the elemental bonus against a multi-element floor/guardian.
 * Uses the minimum bonus across all elements so multi-element guardians
 * are not trivially countered (each element can resist different spells).
 */
export function getMultiElementBonus(spellElem: string, floorElems: string[]): number {
  if (floorElems.length === 0) return 1.0;
  if (floorElems.length === 1) return getElementalBonus(spellElem, floorElems[0]);
  return Math.min(...floorElems.map(e => getElementalBonus(spellElem, e)));
}

// ─── Boon Bonuses ─────────────────────────────────────────────────────────────

export interface BoonBonuses {
  maxMana: number;
  manaRegen: number;
  castingSpeed: number;
  elementalDamage: number;
  rawDamage: number;
  critChance: number;
  critDamage: number;
  spellEfficiency: number;
  manaGain: number;
  insightGain: number;
  studySpeed: number;
  prestigeInsight: number;
}

// Helper to calculate total boon bonuses from signed pacts
export function getBoonBonuses(
  signedPacts: number[],
  guardianBoonMultiplier: number = 1.0,
): BoonBonuses {
  const bonuses: BoonBonuses = {
    maxMana: 0,
    manaRegen: 0,
    castingSpeed: 0,
    elementalDamage: 0,
    rawDamage: 0,
    critChance: 0,
    critDamage: 0,
    spellEfficiency: 0,
    manaGain: 0,
    insightGain: 0,
    studySpeed: 0,
    prestigeInsight: 0,
  };

  for (const floor of signedPacts) {
    const guardian = getGuardianForFloor(floor);
    if (!guardian) continue;

    for (const boon of guardian.boons) {
      let value = boon.value * guardianBoonMultiplier;
      switch (boon.type) {
        case 'maxMana':
          bonuses.maxMana += value;
          break;
        case 'manaRegen':
          bonuses.manaRegen += value;
          break;
        case 'castingSpeed':
          bonuses.castingSpeed += value;
          break;
        case 'elementalDamage':
          bonuses.elementalDamage += value;
          break;
        case 'rawDamage':
          bonuses.rawDamage += value;
          break;
        case 'critChance':
          bonuses.critChance += value;
          break;
        case 'critDamage':
          bonuses.critDamage += value;
          break;
        case 'spellEfficiency':
          bonuses.spellEfficiency += value;
          break;
        case 'manaGain':
          bonuses.manaGain += value;
          break;
        case 'insightGain':
          bonuses.insightGain += value;
          break;
        case 'studySpeed':
          bonuses.studySpeed += value;
          break;
        case 'prestigeInsight':
          bonuses.prestigeInsight += value;
          break;
      }
    }
  }

  return bonuses;
}

// ─── Damage Calculation ───────────────────────────────────────────────────────

export function calcDamage(
  state: DamageCalcParams,
  spellId: string,
  floorElem?: string,
  discipline?: DisciplineBonuses,
): number {
  const sp = SPELLS_DEF[spellId];
  if (!sp) return 5;

  // Base damage: spell base + discipline bonus
  const discBaseDmg = discipline?.bonuses?.baseDamageBonus || 0;
  const baseDmg = sp.dmg + discBaseDmg;

  // Percentage multiplier
  const discDmgMult = discipline?.bonuses?.baseDamageMultiplier || 0;
  const pct = 1 + discDmgMult;

  // Elemental mastery bonus
  const elemMasteryBonus = 1;

  // Guardian bane bonus
  const isGuardianFloor = floorElem && Object.values(BASE_GUARDIANS).some(g => g.element.includes(floorElem));
  const guardianBonus = 1;

  // Get boon bonuses from pacts
  const boons = getBoonBonuses(state.signedPacts);

  // Apply raw damage and elemental damage bonuses
  const rawDamageMult = 1 + boons.rawDamage / 100;
  const elemDamageMult = 1 + boons.elementalDamage / 100;

  // Apply crit chance and damage from boons
  const critChance = boons.critChance / 100;
  const critDamageMult = 1.5 + boons.critDamage / 100;

  let damage = baseDmg * pct * elemMasteryBonus * guardianBonus * rawDamageMult * elemDamageMult;

  // Apply elemental bonus if floor element provided
  if (floorElem) {
    damage *= getElementalBonus(sp.elem, floorElem);
  }

  // Apply crit
  if (Math.random() < critChance) {
    damage *= critDamageMult;
  }

  // Final NaN guard — if anything produced NaN, return a safe fallback
  if (!Number.isFinite(damage)) return 5;

  return damage;
}

// ─── Insight Calculation ──────────────────────────────────────────────────────

export function calcInsight(state: InsightCalcParams, discipline?: DisciplineBonuses): number {
  const pu = state.prestigeUpgrades;
  const discInsightBonus = discipline?.bonuses?.insightGainBonus || 0;
  const skillBonus = 1 + discInsightBonus;

  // Get boon bonuses for insight gain
  const boons = getBoonBonuses(state.signedPacts);
  const boonInsightMult = 1 + boons.insightGain / 100;

  const mult = (1 + (pu.insightAmp || 0) * 0.25) * skillBonus * boonInsightMult;

  // Add prestigeInsight bonus per loop
  const prestigeInsightBonus = boons.prestigeInsight;

  return Math.floor(
    (state.maxFloorReached * 15 + state.totalManaGathered / 500 + state.signedPacts.length * 150 + prestigeInsightBonus) * mult
  );
}

// ─── Incursion Strength ───────────────────────────────────────────────────────

export function getIncursionStrength(day: number, hour: number): number {
  if (day < INCURSION_START_DAY) return 0;
  const totalHours = (day - INCURSION_START_DAY) * 24 + hour;
  const maxHours = (MAX_DAY - INCURSION_START_DAY) * 24;
  return Math.min(0.95, (totalHours / maxHours) * 0.95);
}

// ─── Spell Cost Helpers ───────────────────────────────────────────────────────

// Check if player can afford spell cost
export function canAffordSpellCost(
  cost: SpellCost,
  rawMana: number,
  elements: Record<string, { current: number; max: number; unlocked: boolean }>
): boolean {
  if (cost.type === 'raw') {
    return rawMana >= cost.amount;
  } else {
    const elem = elements[cost.element || ''];
    return elem && elem.unlocked && elem.current >= cost.amount;
  }
}

// Deduct spell cost from appropriate mana pool
export function deductSpellCost(
  cost: SpellCost,
  rawMana: number,
  elements: Record<string, { current: number; max: number; unlocked: boolean }>
): { rawMana: number; elements: Record<string, { current: number; max: number; unlocked: boolean }> } {
  const newElements = { ...elements };

  if (cost.type === 'raw') {
    const deductedAmount = Math.min(rawMana, cost.amount);
    return { rawMana: rawMana - deductedAmount, elements: newElements };
  } else if (cost.element && newElements[cost.element]) {
    const elem = newElements[cost.element];
    const deductedAmount = Math.min(elem.current, cost.amount);
    newElements[cost.element] = {
      ...elem,
      current: elem.current - deductedAmount
    };
    return { rawMana, elements: newElements };
  }

  return { rawMana, elements: newElements };
}

// ─── Melee Damage Calculation (spec §4.3) ────────────────────────────────────

/**
 * Map from sword enchantment specialId to element type for elemental matchup.
 */
const SWORD_ENCHANT_ELEMENT: Record<string, string> = {
  fireBlade: 'fire',
  frostBlade: 'frost',
  lightningBlade: 'lightning',
  voidBlade: 'void',
};

/**
 * Calculate melee damage for a sword attack (spec §4.3).
 *
 * Formula: baseDmg = sword.baseDamage + sword.elementalEnchantDamage
 *          damage  = baseDmg × getElementalBonus(sword.enchantElement, enemy.element)
 *
 * No crit, no discipline damage bonus for melee in v1.
 * attackSpeedMult from equipment does apply to meleeProgress accumulation.
 */
export function calcMeleeDamage(
  swordInstance: EquipmentInstance,
  swordType: { stats?: { baseDamage?: number } },
  enemyElement: string,
): number {
  const baseDmg = swordType.stats?.baseDamage || 5;

  // Determine enchant element from sword's enchantments
  let enchantElement: string | null = null;
  for (const ench of swordInstance.enchantments) {
    const effectDef = ENCHANTMENT_EFFECTS[ench.effectId];
    if (effectDef?.effect.type === 'special' && effectDef.effect.specialId) {
      const elem = SWORD_ENCHANT_ELEMENT[effectDef.effect.specialId];
      if (elem) {
        enchantElement = elem;
        break;
      }
    }
  }

  // Apply elemental bonus if sword has an elemental enchant
  if (enchantElement) {
    return baseDmg * getElementalBonus(enchantElement, enemyElement);
  }

  return baseDmg;
}

// ─── Equipment Spell Helpers ──────────────────────────────────────────────────

// Return type for active equipment spells with source equipment
export interface ActiveEquipmentSpell {
  spellId: string;
  equipmentId: string;
}

// Get active spells from equipped equipment
export function getActiveEquipmentSpells(
  equippedInstances: Record<string, string | null>,
  equipmentInstances: Record<string, EquipmentInstance>
): ActiveEquipmentSpell[] {
  const equippedIds = Object.values(equippedInstances || {}).filter((id): id is string => id !== null);
  const spells: ActiveEquipmentSpell[] = [];

  for (const id of equippedIds) {
    const instance = equipmentInstances[id];
    if (!instance) continue;

    for (const ench of instance.enchantments) {
      const effectDef = ENCHANTMENT_EFFECTS[ench.effectId];
      if (effectDef?.effect.type === 'spell' && effectDef.effect.spellId) {
        const exists = spells.some(s => s.spellId === effectDef.effect.spellId && s.equipmentId === id);
        if (!exists) {
          spells.push({ spellId: effectDef.effect.spellId, equipmentId: id });
        }
      }
    }
  }

  return spells;
}

// ─── DPS Calculation ──────────────────────────────────────────────────────────

// Compute total DPS from all sources (spells, equipment, etc.)
export function getTotalDPS(
  state: DPSCalcParams,
  upgradeEffects: { spellDamageBonus?: number; attackSpeedMultiplier?: number; [key: string]: unknown },
  floorElem?: string,
  discipline?: DisciplineBonuses,
): number {
  let totalDPS = 0;

  // Get active equipment spells
  const activeSpells = getActiveEquipmentSpells(state.equippedInstances, state.equipmentInstances);

  // Calculate DPS for each active spell
  for (const { spellId } of activeSpells) {
    const spellDef = SPELLS_DEF[spellId];
    if (!spellDef) continue;

    // Calculate damage per cast
    const damage = calcDamage(state, spellId, floorElem, discipline);

    // Get cast speed (spells per second)
    const baseCastTime = spellDef.baseCastTime || 1.0;
    const castingSpeedBonus = 1;
    const equipmentAttackSpeed = upgradeEffects.attackSpeedMultiplier || 1;
    const castTime = baseCastTime / (castingSpeedBonus * equipmentAttackSpeed);

    // DPS for this spell
    const spellDPS = damage / castTime;
    totalDPS += spellDPS;
  }

  // Add equipment DPS bonuses from upgrade effects
  if (upgradeEffects.spellDamageBonus) {
    totalDPS *= (1 + upgradeEffects.spellDamageBonus / 100);
  }

  return totalDPS;
}
