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

import type { GameState, SpellCost, EquipmentInstance } from '../types';
import type { DisciplineBonuses } from './mana-utils';
import { GUARDIANS, SPELLS_DEF, ELEMENT_OPPOSITES, INCURSION_START_DAY, MAX_DAY } from '../constants';
import { ENCHANTMENT_EFFECTS } from '../data/enchantment-effects';

// ─── Elemental Damage Bonus ──────────────────────────────────────────────────

// 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 first: spell is the opposite of floor
  // e.g., casting water (opposite of fire) at fire floor = super effective
  if (ELEMENT_OPPOSITES[floorElem] === spellElem) return 1.5; // Super effective: +50% damage
  
  // Check for weak: spell's opposite matches floor
  // e.g., casting fire (whose opposite is water) at water floor = weak
  if (ELEMENT_OPPOSITES[spellElem] === floorElem) return 0.75; // Weak: -25% damage
  
  return 1.0; // Neutral
}

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

// Helper to calculate total boon bonuses from signed pacts
export function getBoonBonuses(signedPacts: number[]): {
  maxMana: number;
  manaRegen: number;
  castingSpeed: number;
  elementalDamage: number;
  rawDamage: number;
  critChance: number;
  critDamage: number;
  spellEfficiency: number;
  manaGain: number;
  insightGain: number;
  studySpeed: number;
  prestigeInsight: number;
} {
  const bonuses = {
    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 = GUARDIANS[floor];
    if (!guardian) continue;
    
    for (const boon of guardian.boons) {
      switch (boon.type) {
        case 'maxMana':
          bonuses.maxMana += boon.value;
          break;
        case 'manaRegen':
          bonuses.manaRegen += boon.value;
          break;
        case 'castingSpeed':
          bonuses.castingSpeed += boon.value;
          break;
        case 'elementalDamage':
          bonuses.elementalDamage += boon.value;
          break;
        case 'rawDamage':
          bonuses.rawDamage += boon.value;
          break;
        case 'critChance':
          bonuses.critChance += boon.value;
          break;
        case 'critDamage':
          bonuses.critDamage += boon.value;
          break;
        case 'spellEfficiency':
          bonuses.spellEfficiency += boon.value;
          break;
        case 'manaGain':
          bonuses.manaGain += boon.value;
          break;
        case 'insightGain':
          bonuses.insightGain += boon.value;
          break;
        case 'studySpeed':
          bonuses.studySpeed += boon.value;
          break;
        case 'prestigeInsight':
          bonuses.prestigeInsight += boon.value;
          break;
      }
    }
  }
  
  return bonuses;
}

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

export function calcDamage(
  state: Pick<GameState, 'skills' | 'signedPacts'>,
  spellId: string,
  floorElem?: string,
  discipline?: DisciplineBonuses,
): number {
  const sp = SPELLS_DEF[spellId];
  if (!sp) return 5;
  const skills = state.skills;

  // Base damage: spell base + skill bonus + discipline bonus (spell-casting → baseDamageBonus)
  const discBaseDmg = discipline?.bonuses?.baseDamageBonus || 0;
  const baseDmg = sp.dmg + (skills.combatTrain || 0) * 5 + discBaseDmg;

  // Percentage multiplier: skill arcaneFury + discipline void-manipulation (baseDamageMultiplier)
  const discDmgMult = discipline?.bonuses?.baseDamageMultiplier || 0;
  const pct = 1 + (skills.arcaneFury || 0) * 0.1 + discDmgMult;

  // Elemental mastery bonus
  const elemMasteryBonus = 1 + (skills.elementalMastery || 0) * 0.15;

  // Guardian bane bonus - check if current floor has a guardian with matching element
  const isGuardianFloor = floorElem && Object.values(GUARDIANS).some(g => g.element === floorElem);
  const guardianBonus = isGuardianFloor
    ? 1 + (skills.guardianBane || 0) * 0.2
    : 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 = (skills.precision || 0) * 0.05 + 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;
  }

  return damage;
}

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

export function calcInsight(state: Pick<GameState, 'maxFloorReached' | 'totalManaGathered' | 'signedPacts' | 'prestigeUpgrades' | 'skills'>, discipline?: DisciplineBonuses): number {
  const pu = state.prestigeUpgrades;
  const discInsightBonus = discipline?.bonuses?.insightGainBonus || 0;
  const skillBonus = 1 + (state.skills.insightHarvest || 0) * 0.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') {
    // Don't allow rawMana to go below zero
    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];
    // Don't allow elemental mana to go below zero
    const deductedAmount = Math.min(elem.current, cost.amount);
    newElements[cost.element] = {
      ...elem,
      current: elem.current - deductedAmount
    };
    return { rawMana, elements: newElements };
  }
  
  return { rawMana, elements: newElements };
}

// ─── 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) {
        // Check if we already have this spell from this equipment
        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: Pick<GameState, 'skills' | 'signedPacts' | 'equippedInstances' | 'equipmentInstances' | 'spells' | 'prestigeUpgrades'>,
  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)
    // Base cast time is 1 second, modified by casting speed bonuses
    const baseCastTime = spellDef.baseCastTime || 1.0;
    const castingSpeedBonus = 1 + (state.skills.castingSpeed || 0) * 0.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;
}
