import { AVAILABLE_BOSS_IDS } from "./bossCatalog";
import { canAddBossToEncounter } from "./bossSelection";
import type { AbilitySlotId, 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;
  abilitySlotId: AbilitySlotId;
  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,
  abilitySlotId: AbilitySlotId,
  effectKind: RunBuffEffectKind,
  name: string,
  icon: string,
  summary: string,
  detail: string,
  accent: string,
  maxRank: 1 | 3 = 3,
): RunBuffDefinition => ({ id, abilitySlotId, effectKind, name, icon, summary, detail, accent, maxRank, infusionEligible: true });

export const RUN_BUFFS: Record<RunBuffId, RunBuffDefinition> = {
  "mend-echo": buff("mend-echo", "ability1", "extra-target", "Echoing", "+", "+1 secondary ally", "Ability 1 heals another injured ally for 50% power per rank.", "#f2d690"),
  "mend-efficiency": buff("mend-efficiency", "ability1", "mana-cost", "Efficient", "▽", "−25% mana cost", "Ability 1 mana cost is multiplied by 0.75 per rank, rounded up.", "#72c8ef"),
  "mend-cast-speed": buff("mend-cast-speed", "ability1", "cast-time", "Swift", "»", "−25% cast time", "Ability 1 cast time is multiplied by 0.75 per rank.", "#d8b4ff"),
  "renew-spread": buff("renew-spread", "ability2", "extra-target", "Spreading", "✣", "+1 injured ally", "Direct Ability 2 casts affect another injured ally per rank.", "#71df9c"),
  "renew-duration": buff("renew-duration", "ability2", "duration", "Enduring", "◷", "+2s duration", "Ability 2 periodic healing lasts 2 seconds longer per rank.", "#83d9aa"),
  "renew-potency": buff("renew-potency", "ability2", "healing", "Potent", "↑", "+20% tick healing", "Ability 2 periodic healing is 20% stronger per rank.", "#a8e875"),
  "shield-echo": buff("shield-echo", "ability3", "extra-target", "Echoing Ward", "◇", "+1 secondary ally", "Ability 3 affects another injured ally at 50% power per rank.", "#75c9ff"),
  "shield-potency": buff("shield-potency", "ability3", "absorb", "Reinforced Ward", "⬡", "+25% ward power", "Ability 3 healing or absorption is 25% stronger per rank.", "#61b9ee"),
  "shield-guard": buff("shield-guard", "ability3", "damage-reduction", "Guardian Ward", "▣", "−8% warded damage", "Targets protected by Ability 3 take 8% less incoming damage per rank.", "#8baeff"),
  "purify-renew": buff("purify-renew", "ability4", "trigger-renew", "Cleansing Renewal", "✧", "Cleanse applies Ability 2", "Every ally cleansed by Ability 4 also gains Ability 2's healing effect.", "#b58cff", 1),
  "purify-shield": buff("purify-shield", "ability4", "trigger-shield", "Purifying Ward", "◈", "Cleanse grants Ability 3", "Every ally cleansed by Ability 4 gains a half-strength Ability 3 effect.", "#9f9aff", 1),
  "purify-chain": buff("purify-chain", "ability4", "chain-cleanse", "Mass Purification", "✦", "+1 cleansed ally", "Ability 4 also cleanses the most injured other debuffed ally.", "#d4a7ff", 1),
  "radiance-cooldown": buff("radiance-cooldown", "ability5", "cooldown", "Quickened", "☀", "−20% cooldown", "Ability 5 cooldown is multiplied by 0.8 per rank.", "#ffd66b"),
  "radiance-renew": buff("radiance-renew", "ability5", "trigger-renew", "Restorative Wave", "❈", "Ability 5 applies Ability 2", "Ability 5 applies Ability 2's healing effect to every living party member.", "#d4e978", 1),
  "radiance-shield": buff("radiance-shield", "ability5", "absorb", "Warding Wave", "◎", "+9 party ward", "Ability 5 grants 9 base healing or absorption to every living ally per rank.", "#ffe58c"),
  "barrier-cooldown": buff("barrier-cooldown", "ability6", "cooldown", "Quickened Mastery", "◉", "−20% cooldown", "Ability 6 cooldown is multiplied by 0.8 per rank.", "#e7cb62"),
  "barrier-duration": buff("barrier-duration", "ability6", "duration", "Lingering Mastery", "⌛", "+2s duration", "Ability 6 remains active 2 seconds longer per rank.", "#cdbd69"),
  "barrier-regen": buff("barrier-regen", "ability6", "barrier-healing", "Restorative Mastery", "✚", "+3 healing per second", "Ability 6 adds 3 party healing per 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(abilitySlotId: AbilitySlotId, baseCost: number, modifiers: CompiledRunModifiers): number {
  if (baseCost <= 0) return 0;
  const multiplier = abilitySlotId === "ability1" ? modifiers.mendManaMultiplier : 1;
  return Math.max(1, Math.ceil(baseCost * multiplier));
}

export function runAbilityCastTime(abilitySlotId: AbilitySlotId, baseCastTime: number, modifiers: CompiledRunModifiers): number {
  return abilitySlotId === "ability1" ? baseCastTime * modifiers.mendCastTimeMultiplier : baseCastTime;
}

export function runAbilityCooldown(abilitySlotId: AbilitySlotId, baseCooldown: number, modifiers: CompiledRunModifiers): number {
  if (abilitySlotId === "ability5") return baseCooldown * modifiers.radianceCooldownMultiplier;
  if (abilitySlotId === "ability6") return baseCooldown * modifiers.barrierCooldownMultiplier;
  return baseCooldown;
}

export function formatRunBuffEffect(buffId: RunBuffId, requestedRank: number, abilityName?: string): string {
  const rank = Math.max(1, Math.min(RUN_BUFFS[buffId].maxRank, requestedRank));
  const reduced = (multiplier: number) => `${Math.round((1 - multiplier ** rank) * 100)}% less`;
  const defaultAbilityNames: Record<AbilitySlotId, string> = {
    ability1: "Mend",
    ability2: "Renew",
    ability3: "Shield",
    ability4: "Purify",
    ability5: "Radiance",
    ability6: "Barrier",
  };
  const name = abilityName ?? defaultAbilityNames[RUN_BUFFS[buffId].abilitySlotId];
  switch (buffId) {
    case "mend-echo": return `${rank} secondary ${rank === 1 ? "ally" : "allies"} at 50% healing`;
    case "mend-efficiency": return `${reduced(0.75)} ${name} mana cost`;
    case "mend-cast-speed": return `${reduced(0.75)} ${name} cast time`;
    case "renew-spread": return `${rank} additional ${name} ${rank === 1 ? "target" : "targets"}`;
    case "renew-duration": return `+${rank * 2}s ${name} duration`;
    case "renew-potency": return `+${rank * 20}% ${name} tick healing`;
    case "shield-echo": return `${rank} secondary ${name} ${rank === 1 ? "target" : "targets"} at 50% power`;
    case "shield-potency": return `+${rank * 25}% ${name} power`;
    case "shield-guard": return `${rank * 8}% less damage while protected by ${name}`;
    case "purify-renew": return `${name} applies periodic healing`;
    case "purify-shield": return `${name} grants a 50% ward`;
    case "purify-chain": return `${name} cleanses one additional ally`;
    case "radiance-cooldown": return `${reduced(0.8)} ${name} cooldown`;
    case "radiance-renew": return `${name} spreads periodic healing party-wide`;
    case "radiance-shield": return `+${rank * 9} base party healing or absorption`;
    case "barrier-cooldown": return `${reduced(0.8)} ${name} cooldown`;
    case "barrier-duration": return `+${rank * 2}s ${name} duration`;
    case "barrier-regen": return `${rank * 3} ${name} 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]];
}
