import { describe, expect, it } from "vitest";
import { createEncounterGearModifiers } from "./gearEffects";
import {
  GEAR_RECIPES,
  canAffordGearUpgrade,
  canUpgradeGearSlot,
  createDefaultGearProgress,
  gearUpgradeCosts,
  upgradeGearSlot,
} from "./gear";
import { groupDrop, type MaterialStack } from "./loot";

describe("IWT2-style gear progression", () => {
  it("uses exact rank-one and rank-two group drop costs", () => {
    const rankOne = gearUpgradeCosts("priest", "weapon", 0);
    const rankTwo = gearUpgradeCosts("priest", "weapon", 1);
    expect(rankOne.map((cost) => cost.quantity)).toEqual([2]);
    expect(rankTwo.map((cost) => cost.quantity)).toEqual([3, 1]);
  });

  it("preserves IWT2 Veteran drop parity for ranks six through ten", () => {
    const recipe = GEAR_RECIPES.nia.weapon;
    const costs = gearUpgradeCosts("nia", "weapon", 5);
    expect(costs[0].itemId).toBe(groupDrop(recipe.primaryGroupId, "veteran").id);
    expect(costs.map((cost) => cost.quantity)).toEqual([6, 5]);
  });

  it("requires two distinct mechanic groups for every gear recipe", () => {
    for (const owner of Object.values(GEAR_RECIPES)) {
      for (const recipe of Object.values(owner)) expect(recipe.primaryGroupId).not.toBe(recipe.secondaryGroupId);
    }
    expect(GEAR_RECIPES.priest.chest).toMatchObject({ primaryGroupId: "charge", secondaryGroupId: "shockwave-fall" });
    expect(GEAR_RECIPES.brann.weapon).toMatchObject({ primaryGroupId: "charge", secondaryGroupId: "scuttle-burst" });
    expect(GEAR_RECIPES.nia.chest).toMatchObject({ primaryGroupId: "charge", secondaryGroupId: "rift-lanes" });
    expect(GEAR_RECIPES.vale.chest).toMatchObject({ primaryGroupId: "charge", secondaryGroupId: "rift-lanes" });
  });

  it("spends all costs atomically and advances one rank", () => {
    const progress = createDefaultGearProgress();
    const costs = gearUpgradeCosts("brann", "weapon", 0);
    const drop = groupDrop(GEAR_RECIPES.brann.weapon.primaryGroupId, "initiate");
    const inventory: MaterialStack[] = [{ id: drop.id, name: drop.name, quantity: 3, rarity: drop.rarity, itemLevel: drop.itemLevel, glyph: drop.glyph }];
    expect(canAffordGearUpgrade(inventory, costs)).toBe(true);
    const result = upgradeGearSlot(progress, inventory, "brann", "weapon");
    expect(result.gearProgress.brann.slots.weapon.level).toBe(1);
    expect(result.inventory[0].quantity).toBe(1);
    expect(progress.brann.slots.weapon.level).toBe(0);
  });

  it("reports only affordable non-max gear slots as upgradeable", () => {
    const progress = createDefaultGearProgress();
    const recipe = GEAR_RECIPES.priest.weapon;
    const drop = groupDrop(recipe.primaryGroupId, "initiate");
    const inventory: MaterialStack[] = [{ ...drop, quantity: 2 }];

    expect(canUpgradeGearSlot(progress, inventory, "priest", "weapon")).toBe(true);
    expect(canUpgradeGearSlot(progress, [], "priest", "weapon")).toBe(false);

    progress.priest.slots.weapon.level = 10;
    expect(canUpgradeGearSlot(progress, inventory, "priest", "weapon")).toBe(false);
  });

  it("projects rank bonuses without mutating saved progress", () => {
    const progress = createDefaultGearProgress();
    progress.priest.slots.weapon.level = 10;
    progress.priest.slots.chest.level = 10;
    progress.brann.slots.weapon.level = 10;
    progress.brann.slots.helmet.level = 10;
    const modifiers = createEncounterGearModifiers(progress, "priest");
    expect(modifiers.aelia.healingPower).toBeCloseTo(1.4);
    expect(modifiers.aelia.maxHealth).toBeCloseTo(1.4);
    expect(modifiers.brann.damage).toBeCloseTo(1.4);
    expect(modifiers.brann.stunDuration).toBeCloseTo(0.2);
  });
});
