import { AVAILABLE_BOSS_IDS } from "./bossCatalog";
import { canAddBossToEncounter } from "./bossSelection";
import type { AbilityId, BossId, RunBuffId, RunBuffRanks } from "./types";

export type RunBuffEffectKind =
  | "extra-target"
  | "mana-cost"
  | "cast-time"
  | "duration"
  | "healing"
  | "absorb"
  | "damage-reduction"
  | "trigger-renew"
  | "trigger-shield"
  | "chain-cleanse"
  | "cooldown"
  | "barrier-healing";

export interface RunBuffDefinition {
  id: RunBuffId;
  abilityId: AbilityId;
  effectKind: RunBuffEffectKind;
  name: string;
  icon: string;
  summary: string;
  detail: string;
  accent: string;
  maxRank: 1 | 3;
  infusionEligible: true;
}

export interface CompiledRunModifiers {
  mendExtraTargets: number;
  mendManaMultiplier: number;
  mendCastTimeMultiplier: number;
  renewExtraTargets: number;
  renewDurationBonus: number;
  renewHealingMultiplier: number;
  shieldExtraTargets: number;
  shieldAbsorbMultiplier: number;
  shieldDamageTakenMultiplier: number;
  purifyAppliesRenew: boolean;
  purifyAppliesShield: boolean;
  purifyExtraTargets: number;
  radianceCooldownMultiplier: number;
  radianceAppliesRenew: boolean;
  radianceAbsorb: number;
  barrierCooldownMultiplier: number;
  barrierDurationBonus: number;
  barrierHealingPerSecond: number;
}

export const RUN_BUFF_ORDER: readonly RunBuffId[] = [
  "mend-echo",
  "mend-efficiency",
  "mend-cast-speed",
  "renew-spread",
  "renew-duration",
  "renew-potency",
  "shield-echo",
  "shield-potency",
  "shield-guard",
  "purify-renew",
  "purify-shield",
  "purify-chain",
  "radiance-cooldown",
  "radiance-renew",
  "radiance-shield",
  "barrier-cooldown",
  "barrier-duration",
  "barrier-regen",
];

const buff = (
  id: RunBuffId,
  abilityId: AbilityId,
  effectKind: RunBuffEffectKind,
  name: string,
  icon: string,
  summary: string,
  detail: string,
  accent: string,
  maxRank: 1 | 3 = 3,
): RunBuffDefinition => ({ id, abilityId, effectKind, name, icon, summary, detail, accent, maxRank, infusionEligible: true });

export const RUN_BUFFS: Record<RunBuffId, RunBuffDefinition> = {
  "mend-echo": buff("mend-echo", "mend", "extra-target", "Echoing", "+", "+1 secondary ally", "Mend heals another injured ally for 50% power per rank.", "#f2d690"),
  "mend-efficiency": buff("mend-efficiency", "mend", "mana-cost", "Efficient", "▽", "−25% mana cost", "Mend mana cost is multiplied by 0.75 per rank, rounded up.", "#72c8ef"),
  "mend-cast-speed": buff("mend-cast-speed", "mend", "cast-time", "Swift", "»", "−25% cast time", "Mend cast time is multiplied by 0.75 per rank.", "#d8b4ff"),
  "renew-spread": buff("renew-spread", "renew", "extra-target", "Spreading", "✣", "+1 injured ally", "Direct Renew casts affect another injured ally per rank.", "#71df9c"),
  "renew-duration": buff("renew-duration", "renew", "duration", "Enduring", "◷", "+2s duration", "Every Renew effect lasts 2 seconds longer per rank.", "#83d9aa"),
  "renew-potency": buff("renew-potency", "renew", "healing", "Potent", "↑", "+20% tick healing", "Every Renew tick heals 20% more per rank.", "#a8e875"),
  "shield-echo": buff("shield-echo", "shield", "extra-target", "Echoing Aegis", "◇", "+1 secondary ally", "Shield another injured ally for 50% power per rank.", "#75c9ff"),
  "shield-potency": buff("shield-potency", "shield", "absorb", "Reinforced Aegis", "⬡", "+25% absorption", "All healer-created absorption is 25% stronger per rank.", "#61b9ee"),
  "shield-guard": buff("shield-guard", "shield", "damage-reduction", "Guardian Aegis", "▣", "−8% shielded damage", "Targets with absorption take 8% less incoming damage per rank.", "#8baeff"),
  "purify-renew": buff("purify-renew", "purify", "trigger-renew", "Cleansing Renewal", "✧", "Purify applies Renew", "Every ally cleansed by Purify also gains Renew.", "#b58cff", 1),
  "purify-shield": buff("purify-shield", "purify", "trigger-shield", "Purifying Ward", "◈", "Purify grants 50% Shield", "Every ally cleansed by Purify gains half-strength absorption.", "#9f9aff", 1),
  "purify-chain": buff("purify-chain", "purify", "chain-cleanse", "Mass Purification", "✦", "+1 cleansed ally", "Purify also cleanses the most injured other debuffed ally.", "#d4a7ff", 1),
  "radiance-cooldown": buff("radiance-cooldown", "radiance", "cooldown", "Quickened Radiance", "☀", "−20% cooldown", "Radiance cooldown is multiplied by 0.8 per rank.", "#ffd66b"),
  "radiance-renew": buff("radiance-renew", "radiance", "trigger-renew", "Radiant Renewal", "❈", "Radiance applies Renew", "Radiance applies Renew to every living party member.", "#d4e978", 1),
  "radiance-shield": buff("radiance-shield", "radiance", "absorb", "Radiant Aegis", "◎", "+9 party absorption", "Radiance grants 9 base absorption to every living ally per rank.", "#ffe58c"),
  "barrier-cooldown": buff("barrier-cooldown", "barrier", "cooldown", "Hallowed Ground", "◉", "−20% cooldown", "Barrier cooldown is multiplied by 0.8 per rank.", "#e7cb62"),
  "barrier-duration": buff("barrier-duration", "barrier", "duration", "Lingering Barrier", "⌛", "+2s duration", "Barrier remains active 2 seconds longer per rank.", "#cdbd69"),
  "barrier-regen": buff("barrier-regen", "barrier", "barrier-healing", "Restorative Ground", "✚", "+3 healing per second", "Living allies inside Barrier heal every second per rank.", "#84d69a"),
};

export function runBuffRank(ranks: RunBuffRanks, buffId: RunBuffId): number {
  return Math.max(0, Math.min(RUN_BUFFS[buffId].maxRank, Math.floor(ranks[buffId] ?? 0)));
}

export function effectiveRunBuffRank(ranks: RunBuffRanks, buffId: RunBuffId, passiveInfusionId: RunBuffId | null = null): number {
  return Math.min(RUN_BUFFS[buffId].maxRank, runBuffRank(ranks, buffId) + Number(passiveInfusionId === buffId));
}

export function increaseRunBuffRank(ranks: RunBuffRanks, buffId: RunBuffId): RunBuffRanks {
  const current = runBuffRank(ranks, buffId);
  if (current >= RUN_BUFFS[buffId].maxRank) return { ...ranks };
  return { ...ranks, [buffId]: current + 1 };
}

export function selectRunBuffDraft(
  ranks: RunBuffRanks,
  passiveInfusionId: RunBuffId | null = null,
  random: () => number = Math.random,
  count = 3,
): RunBuffId[] {
  const pool = RUN_BUFF_ORDER.filter((id) => effectiveRunBuffRank(ranks, id, passiveInfusionId) < RUN_BUFFS[id].maxRank);
  const choices: RunBuffId[] = [];
  while (choices.length < count && pool.length > 0) {
    const sample = random();
    const randomValue = Number.isFinite(sample) ? Math.max(0, Math.min(0.999999999, sample)) : 0;
    const index = Math.floor(randomValue * pool.length);
    choices.push(pool[index]);
    pool.splice(index, 1);
  }
  return choices;
}

export function compileRunModifiers(ranks: RunBuffRanks, passiveInfusionId: RunBuffId | null = null): CompiledRunModifiers {
  const rank = (id: RunBuffId) => effectiveRunBuffRank(ranks, id, passiveInfusionId);
  return {
    mendExtraTargets: rank("mend-echo"),
    mendManaMultiplier: 0.75 ** rank("mend-efficiency"),
    mendCastTimeMultiplier: 0.75 ** rank("mend-cast-speed"),
    renewExtraTargets: rank("renew-spread"),
    renewDurationBonus: rank("renew-duration") * 2,
    renewHealingMultiplier: 1 + rank("renew-potency") * 0.2,
    shieldExtraTargets: rank("shield-echo"),
    shieldAbsorbMultiplier: 1 + rank("shield-potency") * 0.25,
    shieldDamageTakenMultiplier: 1 - rank("shield-guard") * 0.08,
    purifyAppliesRenew: rank("purify-renew") > 0,
    purifyAppliesShield: rank("purify-shield") > 0,
    purifyExtraTargets: rank("purify-chain"),
    radianceCooldownMultiplier: 0.8 ** rank("radiance-cooldown"),
    radianceAppliesRenew: rank("radiance-renew") > 0,
    radianceAbsorb: rank("radiance-shield") * 9,
    barrierCooldownMultiplier: 0.8 ** rank("barrier-cooldown"),
    barrierDurationBonus: rank("barrier-duration") * 2,
    barrierHealingPerSecond: rank("barrier-regen") * 3,
  };
}

export function runAbilityManaCost(abilityId: AbilityId, baseCost: number, modifiers: CompiledRunModifiers): number {
  if (baseCost <= 0) return 0;
  const multiplier = abilityId === "mend" ? modifiers.mendManaMultiplier : 1;
  return Math.max(1, Math.ceil(baseCost * multiplier));
}

export function runAbilityCastTime(abilityId: AbilityId, baseCastTime: number, modifiers: CompiledRunModifiers): number {
  return abilityId === "mend" ? baseCastTime * modifiers.mendCastTimeMultiplier : baseCastTime;
}

export function runAbilityCooldown(abilityId: AbilityId, baseCooldown: number, modifiers: CompiledRunModifiers): number {
  if (abilityId === "radiance") return baseCooldown * modifiers.radianceCooldownMultiplier;
  if (abilityId === "barrier") return baseCooldown * modifiers.barrierCooldownMultiplier;
  return baseCooldown;
}

export function formatRunBuffEffect(buffId: RunBuffId, requestedRank: number): string {
  const rank = Math.max(1, Math.min(RUN_BUFFS[buffId].maxRank, requestedRank));
  const reduced = (multiplier: number) => `${Math.round((1 - multiplier ** rank) * 100)}% less`;
  switch (buffId) {
    case "mend-echo": return `${rank} secondary ${rank === 1 ? "ally" : "allies"} at 50% healing`;
    case "mend-efficiency": return `${reduced(0.75)} Mend mana cost`;
    case "mend-cast-speed": return `${reduced(0.75)} Mend cast time`;
    case "renew-spread": return `${rank} additional Renew ${rank === 1 ? "target" : "targets"}`;
    case "renew-duration": return `+${rank * 2}s Renew duration`;
    case "renew-potency": return `+${rank * 20}% Renew tick healing`;
    case "shield-echo": return `${rank} secondary Shield ${rank === 1 ? "target" : "targets"} at 50% power`;
    case "shield-potency": return `+${rank * 25}% healer absorption`;
    case "shield-guard": return `${rank * 8}% less damage while shielded`;
    case "purify-renew": return "Purify applies Renew";
    case "purify-shield": return "Purify grants 50% Shield";
    case "purify-chain": return "Purify cleanses one additional ally";
    case "radiance-cooldown": return `${reduced(0.8)} Radiance cooldown`;
    case "radiance-renew": return "Radiance applies Renew party-wide";
    case "radiance-shield": return `+${rank * 9} base party absorption`;
    case "barrier-cooldown": return `${reduced(0.8)} Barrier cooldown`;
    case "barrier-duration": return `+${rank * 2}s Barrier duration`;
    case "barrier-regen": return `${rank * 3} Barrier healing per second`;
  }
}

export function bossHealthMultiplier(round: number) {
  return 1 + Math.max(0, round - 1) * 0.1;
}

export const ROGUE_TRIALS_TRIO_ROUND = 5;

export function selectUnseenBosses(
  count: number,
  seenBossIds: readonly BossId[] = [],
  random: () => number = Math.random,
  selectedBossIds: readonly BossId[] = [],
): BossId[] {
  const seen = new Set(seenBossIds);
  const pool = AVAILABLE_BOSS_IDS.filter((bossId) => !seen.has(bossId));
  const requestedCount = Math.max(0, Math.floor(count));
  if (pool.length < requestedCount) {
    throw new Error(`Cannot select ${requestedCount} unseen bosses from ${pool.length} remaining bosses.`);
  }
  const bosses: BossId[] = [];
  while (bosses.length < requestedCount) {
    const compatiblePool = pool.filter((bossId) => canAddBossToEncounter([...selectedBossIds, ...bosses], bossId));
    if (!compatiblePool.length) {
      throw new Error(`Cannot select ${requestedCount} unseen bosses without duplicating an encounter-exclusive mechanic.`);
    }
    const sample = random();
    const randomValue = Number.isFinite(sample) ? Math.max(0, Math.min(0.999999999, sample)) : 0;
    const index = Math.floor(randomValue * compatiblePool.length);
    const selected = compatiblePool[index];
    bosses.push(selected);
    pool.splice(pool.indexOf(selected), 1);
  }
  return bosses;
}

export function selectRogueTrialsBosses(
  round: number,
  seenBossIds: readonly BossId[],
  random: () => number = Math.random,
): BossId[] {
  const count = round === ROGUE_TRIALS_TRIO_ROUND ? 3 : 2;
  return selectUnseenBosses(count, seenBossIds, random);
}

export function selectRandomBossPair(
  excludedBossIds: readonly BossId[] = [],
  random: () => number = Math.random,
): readonly [BossId, BossId] {
  const excluded = new Set(excludedBossIds);
  const eligibleBosses = AVAILABLE_BOSS_IDS.filter((bossId) => !excluded.has(bossId));
  const pool = eligibleBosses.length >= 2 ? eligibleBosses : AVAILABLE_BOSS_IDS;
  const firstIndex = Math.floor(random() * pool.length) % pool.length;
  const first = pool[firstIndex];
  const compatiblePool = pool.filter((bossId) => canAddBossToEncounter([first], bossId));
  const secondIndex = Math.floor(random() * compatiblePool.length) % compatiblePool.length;
  return [first, compatiblePool[secondIndex]];
}
