import type { BossGroupId } from "../bossCatalog";
import type { HealerClassId, MemberId, RunBuffId } from "../types";
import { groupDrop, type DifficultySlug, type MaterialStack } from "./loot";

export type GearOwnerId = HealerClassId | Exclude<MemberId, "aelia">;
export type GearSlotId = "weapon" | "helmet" | "chest" | "legs" | "feet";
export type GearLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
export type GearStatId = "healingPower" | "damage" | "attackCooldown" | "maxHealth" | "moveSpeed" | "stunResist" | "hazardDamageTaken";

export interface GearSlotProgress {
  level: GearLevel;
}

export interface ClassGearProgress {
  slots: Record<GearSlotId, GearSlotProgress>;
  infusionAbilityId: string | null;
  passiveInfusionId: RunBuffId | null;
}

export type GearProgress = Record<GearOwnerId, ClassGearProgress>;

export interface GearRecipe {
  ownerId: GearOwnerId;
  slotId: GearSlotId;
  statId: GearStatId;
  primaryGroupId: BossGroupId;
  secondaryGroupId: BossGroupId;
}

export interface GearUpgradeCost {
  itemId: string;
  itemName: string;
  quantity: number;
}

export const GEAR_OWNER_ORDER: readonly GearOwnerId[] = ["priest", "druid", "shaman", "brann", "nia", "orin", "vale"];
export const GEAR_SLOT_ORDER: readonly GearSlotId[] = ["weapon", "helmet", "chest", "legs", "feet"];
export const MAX_GEAR_LEVEL: GearLevel = 10;

export const GEAR_OWNER_LABELS: Record<GearOwnerId, string> = {
  priest: "Priest",
  druid: "Druid",
  shaman: "Shaman",
  brann: "Brann · Knight",
  nia: "Nia · Ranger",
  orin: "Orin · Mage",
  vale: "Vale · Rogue",
};

export const GEAR_SLOT_LABELS: Record<GearSlotId, string> = {
  weapon: "Weapon",
  helmet: "Helmet",
  chest: "Chest",
  legs: "Legs",
  feet: "Feet",
};

export const GEAR_STAT_LABELS: Record<GearStatId, string> = {
  healingPower: "Healing Power",
  damage: "Damage",
  attackCooldown: "Cooldown",
  maxHealth: "Max Health",
  moveSpeed: "Move Speed",
  stunResist: "Stun Resist",
  hazardDamageTaken: "Hazard Damage Taken",
};

type RecipeSeed = Record<GearSlotId, readonly [BossGroupId, BossGroupId]>;

const HEALER_RECIPES: RecipeSeed = {
  weapon: ["bind-venom", "breath-skyfall"],
  helmet: ["ricochet", "burrow-eruption"],
  chest: ["charge", "shockwave-fall"],
  legs: ["slash-cross", "ricochet"],
  feet: ["bind-venom", "burrow-eruption"],
};

const OWNER_RECIPE_SEEDS: Record<GearOwnerId, RecipeSeed> = {
  priest: HEALER_RECIPES,
  druid: HEALER_RECIPES,
  shaman: HEALER_RECIPES,
  brann: {
    weapon: ["charge", "scuttle-burst"],
    helmet: ["slash-cross", "burrow-eruption"],
    chest: ["charge", "breath-skyfall"],
    legs: ["charge", "ricochet"],
    feet: ["burrow-eruption", "bind-venom"],
  },
  nia: {
    weapon: ["ricochet", "breath-skyfall"],
    helmet: ["bind-venom", "burrow-eruption"],
    chest: ["charge", "rift-lanes"],
    legs: ["ricochet", "slash-cross"],
    feet: ["burrow-eruption", "bind-venom"],
  },
  orin: {
    weapon: ["breath-skyfall", "bind-venom"],
    helmet: ["burrow-eruption", "ricochet"],
    chest: ["bind-venom", "charge"],
    legs: ["ricochet", "slash-cross"],
    feet: ["burrow-eruption", "charge"],
  },
  vale: {
    weapon: ["slash-cross", "breath-skyfall"],
    helmet: ["ricochet", "burrow-eruption"],
    chest: ["charge", "rift-lanes"],
    legs: ["slash-cross", "ricochet"],
    feet: ["bind-venom", "burrow-eruption"],
  },
};

function statFor(ownerId: GearOwnerId, slotId: GearSlotId): GearStatId {
  if (slotId === "weapon") return ownerId === "priest" || ownerId === "druid" || ownerId === "shaman" ? "healingPower" : "damage";
  if (slotId === "helmet") return ownerId === "brann" ? "stunResist" : "attackCooldown";
  if (slotId === "chest") return "maxHealth";
  if (slotId === "legs") return "moveSpeed";
  return "hazardDamageTaken";
}

export const GEAR_RECIPES = Object.fromEntries(GEAR_OWNER_ORDER.map((ownerId) => [
  ownerId,
  Object.fromEntries(GEAR_SLOT_ORDER.map((slotId) => {
    const [primaryGroupId, secondaryGroupId] = OWNER_RECIPE_SEEDS[ownerId][slotId];
    return [slotId, { ownerId, slotId, statId: statFor(ownerId, slotId), primaryGroupId, secondaryGroupId }];
  })),
])) as Record<GearOwnerId, Record<GearSlotId, GearRecipe>>;

export function createDefaultGearProgress(): GearProgress {
  return Object.fromEntries(GEAR_OWNER_ORDER.map((ownerId) => [
    ownerId,
    {
      slots: Object.fromEntries(GEAR_SLOT_ORDER.map((slotId) => [slotId, { level: 0 }])),
      infusionAbilityId: null,
      passiveInfusionId: null,
    },
  ])) as GearProgress;
}

export function gearUpgradeCosts(ownerId: GearOwnerId, slotId: GearSlotId, currentLevel: GearLevel): GearUpgradeCost[] {
  if (currentLevel >= MAX_GEAR_LEVEL) return [];
  const nextLevel = currentLevel + 1 as Exclude<GearLevel, 0>;
  const recipe = GEAR_RECIPES[ownerId][slotId];
  const difficultySlug = upgradeDifficultySlug(nextLevel);
  const primary = groupDrop(recipe.primaryGroupId, difficultySlug);
  const secondary = groupDrop(recipe.secondaryGroupId, difficultySlug);
  if (nextLevel === 1) return [{ itemId: primary.id, itemName: primary.name, quantity: 2 }];
  const primaryQuantity = nextLevel <= 3 ? 3 : nextLevel <= 5 ? nextLevel : nextLevel;
  const secondaryQuantity = nextLevel === 2 ? 1 : nextLevel === 3 ? 2 : nextLevel - 1;
  return [
    { itemId: primary.id, itemName: primary.name, quantity: primaryQuantity },
    { itemId: secondary.id, itemName: secondary.name, quantity: secondaryQuantity },
  ];
}

export function canAffordGearUpgrade(inventory: readonly MaterialStack[], costs: readonly GearUpgradeCost[]): boolean {
  return costs.every((cost) => (inventory.find((item) => item.id === cost.itemId)?.quantity ?? 0) >= cost.quantity);
}

export function canUpgradeGearSlot(
  progress: GearProgress,
  inventory: readonly MaterialStack[],
  ownerId: GearOwnerId,
  slotId: GearSlotId,
): boolean {
  const currentLevel = progress[ownerId].slots[slotId].level;
  return currentLevel < MAX_GEAR_LEVEL
    && canAffordGearUpgrade(inventory, gearUpgradeCosts(ownerId, slotId, currentLevel));
}

export function spendGearCosts(inventory: readonly MaterialStack[], costs: readonly GearUpgradeCost[]): MaterialStack[] {
  if (!canAffordGearUpgrade(inventory, costs)) {
    const missing = costs.find((cost) => (inventory.find((item) => item.id === cost.itemId)?.quantity ?? 0) < cost.quantity);
    throw new Error(missing ? `Need ${missing.quantity} ${missing.itemName}.` : "Missing gear materials.");
  }
  return inventory.flatMap((item) => {
    const cost = costs.find((entry) => entry.itemId === item.id);
    if (!cost) return [{ ...item }];
    const quantity = item.quantity - cost.quantity;
    return quantity > 0 ? [{ ...item, quantity }] : [];
  });
}

export function upgradeGearSlot(
  progress: GearProgress,
  inventory: readonly MaterialStack[],
  ownerId: GearOwnerId,
  slotId: GearSlotId,
): { gearProgress: GearProgress; inventory: MaterialStack[] } {
  const currentLevel = progress[ownerId].slots[slotId].level;
  if (currentLevel >= MAX_GEAR_LEVEL) throw new Error(`${GEAR_SLOT_LABELS[slotId]} already at +${MAX_GEAR_LEVEL}.`);
  const costs = gearUpgradeCosts(ownerId, slotId, currentLevel);
  const inventoryAfter = spendGearCosts(inventory, costs);
  return {
    inventory: inventoryAfter,
    gearProgress: {
      ...progress,
      [ownerId]: {
        ...progress[ownerId],
        slots: {
          ...progress[ownerId].slots,
          [slotId]: { level: currentLevel + 1 as GearLevel },
        },
      },
    },
  };
}

export function gearBonusText(statId: GearStatId, level: number): string {
  if (statId === "moveSpeed") return `+${formatPercent(level * 2.5)}%`;
  if (statId === "attackCooldown" || statId === "hazardDamageTaken") return `-${level * 3}%`;
  if (statId === "stunResist") return `-${level * 8}%`;
  return `+${level * 4}%`;
}

function formatPercent(value: number): string {
  return Number.isInteger(value) ? String(value) : value.toFixed(1);
}

// IWT2 parity: +6 through +10 return to Veteran drops while infusion costs use higher tiers.
function upgradeDifficultySlug(level: Exclude<GearLevel, 0>): DifficultySlug {
  if (level <= 2) return "initiate";
  if (level >= 6) return "veteran";
  if (level === 3) return "veteran";
  if (level === 4) return "champion";
  return "mythic";
}
