import { nextPartyRarity, upgradeRosterMemberRarity } from "./party";
import { randomInt, shuffleWithRandom } from "./random";
import { incrementSpellRank } from "./spells";
import type {
  RandomState,
  RewardChest,
  RewardChoice,
  RpgRoguelikeRunState,
  RunEquipment,
  RunGearItem,
  RunGearOwnerId,
  RunGearSlotId,
  RunGearStatId,
  RunShopState,
  ShopOffer,
} from "./types";
import { MAX_RUN_GEAR_ENHANCEMENT, MAX_SPELL_RANK } from "./types";

export const RUN_GEAR_SLOT_ORDER: readonly RunGearSlotId[] = ["weapon", "armor", "trinket"];

export interface RunGearComparison {
  readonly currentItem: RunGearItem | undefined;
  readonly effectLabel: string;
  readonly currentValue: number;
  readonly replacementValue: number;
  readonly delta: number;
}

const GEAR_SLOT_DATA: Record<RunGearSlotId, { label: string; statId: RunGearStatId }> = {
  weapon: { label: "Weapon", statId: "damage" },
  armor: { label: "Armor", statId: "maxHealth" },
  trinket: { label: "Trinket", statId: "haste" },
};

function enhancementLevel(value: number): 0 | 1 | 2 | 3 | 4 | 5 {
  return Math.max(0, Math.min(MAX_RUN_GEAR_ENHANCEMENT, Math.floor(value))) as 0 | 1 | 2 | 3 | 4 | 5;
}

export function equippedRunGear(
  equipment: RunEquipment,
  ownerId: RunGearOwnerId,
  slotId: RunGearSlotId,
): RunGearItem | undefined {
  return equipment[ownerId]?.[slotId];
}

/** Player weapons convert their damage budget into healing power in combat. */
export function runGearEffectLabel(ownerId: RunGearOwnerId, statId: RunGearStatId): string {
  if (statId === "damage") return ownerId === "player" ? "Healing power" : "Damage";
  if (statId === "maxHealth") return "Max health";
  return "Haste";
}

/** Comparison data shared by chest and shop projections on either display. */
export function compareRunGear(
  equipment: RunEquipment,
  item: RunGearItem,
): RunGearComparison {
  const currentItem = equippedRunGear(equipment, item.ownerId, item.slotId);
  const currentValue = currentItem?.statId === item.statId ? currentItem.statValue : 0;
  return {
    currentItem,
    effectLabel: runGearEffectLabel(item.ownerId, item.statId),
    currentValue,
    replacementValue: item.statValue,
    delta: item.statValue - currentValue,
  };
}

export function createRunGearItem(
  id: string,
  ownerId: RunGearOwnerId,
  slotId: RunGearSlotId,
  enhancement: number,
): RunGearItem {
  const level = enhancementLevel(enhancement);
  const data = GEAR_SLOT_DATA[slotId];
  const ownerLabel = ownerId === "player" ? "Healer" : "Companion";
  return {
    id,
    ownerId,
    slotId,
    enhancement: level,
    name: `${ownerLabel} ${data.label} +${level}`,
    statId: data.statId,
    statValue: data.statId === "haste" ? 2 + level * 2 : 4 + level * 4,
    sellPrice: 18 + level * 12,
  };
}

export function autoEquipRunGear(
  equipment: RunEquipment,
  bag: readonly RunGearItem[],
  item: RunGearItem,
): { equipment: RunEquipment; bag: RunGearItem[]; equipped: boolean } {
  const current = equippedRunGear(equipment, item.ownerId, item.slotId);
  if (current && current.enhancement >= item.enhancement) {
    return { equipment, bag: [...bag, item], equipped: false };
  }
  const ownerEquipment = { ...(equipment[item.ownerId] ?? {}) };
  ownerEquipment[item.slotId] = item;
  return {
    equipment: { ...equipment, [item.ownerId]: ownerEquipment },
    bag: current ? [...bag, current] : [...bag],
    equipped: true,
  };
}

function gearOwners(state: Pick<RpgRoguelikeRunState, "roster">): RunGearOwnerId[] {
  return ["player", ...state.roster.map((member) => member.instanceId)];
}

function gearRewardChoices(
  source: RandomState,
  state: Pick<RpgRoguelikeRunState, "roster" | "equipment" | "bossesDefeated">,
  quality: number,
  prefix: string,
): { choices: RewardChoice[]; random: RandomState } {
  const choices: RewardChoice[] = [];
  let random = source;
  for (const ownerId of gearOwners(state)) {
    for (const slotId of RUN_GEAR_SLOT_ORDER) {
      const current = equippedRunGear(state.equipment, ownerId, slotId)?.enhancement ?? -1;
      if (current >= MAX_RUN_GEAR_ENHANCEMENT) continue;
      const minimum = enhancementLevel(Math.max(current + 1, quality));
      const rolled = randomInt(random, MAX_RUN_GEAR_ENHANCEMENT - minimum + 1);
      random = rolled.random;
      const next = enhancementLevel(minimum + rolled.value);
      const item = createRunGearItem(`${prefix}-${ownerId}-${slotId}-${next}`, ownerId, slotId, next);
      choices.push({ id: `reward-${item.id}`, kind: "run-gear", item, label: item.name });
    }
  }
  return { choices, random };
}

export function generateRewardChest(
  source: RandomState,
  state: Pick<
    RpgRoguelikeRunState,
    "roster" | "selectedSpellIds" | "spellRanks" | "equipment" | "bossesDefeated"
  >,
  requestedQuality: number,
): { chest: RewardChest; random: RandomState } {
  const quality = Math.max(0, Math.min(3, Math.floor(requestedQuality)));
  const prefix = `chest-${Math.max(1, state.bossesDefeated)}-q${quality}`;
  const pool: RewardChoice[] = [];

  for (const spellId of state.selectedSpellIds) {
    const current = Math.max(0, Math.floor(state.spellRanks[spellId] ?? 0));
    if (current >= MAX_SPELL_RANK) continue;
    pool.push({
      id: `${prefix}-spell-${spellId}`,
      kind: "spell-rank",
      spellId,
      nextRank: current + 1,
      label: `${spellId} rank ${current + 1}`,
    });
  }

  for (const member of state.roster) {
    const rarity = nextPartyRarity(member.rarity);
    if (!rarity) continue;
    pool.push({
      id: `${prefix}-member-${member.instanceId}`,
      kind: "member-rarity",
      memberId: member.instanceId,
      nextRarity: rarity,
      label: `${member.name} → ${rarity}`,
    });
  }

  const gear = gearRewardChoices(source, state, quality, prefix);
  pool.push(...gear.choices);
  for (let index = pool.length; index < 3; index += 1) {
    const amount = 35 + quality * 15 + index * 5;
    pool.push({ id: `${prefix}-currency-${index}`, kind: "currency", amount, label: `${amount} gold` });
  }

  const shuffled = shuffleWithRandom(gear.random, pool);
  return {
    chest: { id: prefix, quality, choices: shuffled.values.slice(0, 3) },
    random: shuffled.random,
  };
}

export function applyRewardChoice(
  state: RpgRoguelikeRunState,
  choice: RewardChoice,
): RpgRoguelikeRunState {
  if (choice.kind === "spell-rank") {
    return { ...state, spellRanks: incrementSpellRank(state.spellRanks, choice.spellId) };
  }
  if (choice.kind === "member-rarity") {
    const roster = state.roster.map((member) => member.instanceId === choice.memberId ? upgradeRosterMemberRarity(member) : member);
    return {
      ...state,
      roster,
    };
  }
  if (choice.kind === "run-gear") {
    const applied = autoEquipRunGear(state.equipment, state.bag, choice.item);
    return { ...state, equipment: applied.equipment, bag: applied.bag };
  }
  return { ...state, currency: state.currency + choice.amount };
}

export function generateRunShop(
  source: RandomState,
  state: Pick<RpgRoguelikeRunState, "roster" | "equipment" | "bossesDefeated">,
  act: number,
): { shop: RunShopState; random: RandomState } {
  const quality = Math.max(0, Math.min(3, act));
  const gear = gearRewardChoices(source, state, quality, `shop-a${act}`);
  const rewardChoices = gear.choices
    .filter((choice): choice is Extract<RewardChoice, { kind: "run-gear" }> => choice.kind === "run-gear");
  const shuffled = shuffleWithRandom(gear.random, rewardChoices);
  const offers: ShopOffer[] = shuffled.values.slice(0, 4).map((choice) => ({
    id: `offer-${choice.item.id}`,
    item: choice.item,
    price: 55 + choice.item.enhancement * 28,
    sold: false,
  }));
  return {
    shop: {
      act,
      offers,
      restCost: 25 + act * 10,
      reviveCost: 45 + act * 15,
    },
    random: shuffled.random,
  };
}

export function buyShopOffer(state: RpgRoguelikeRunState, offerId: string): RpgRoguelikeRunState {
  const offer = state.shop?.offers.find((candidate) => candidate.id === offerId);
  if (!offer || offer.sold || state.currency < offer.price || !state.shop) return state;
  const applied = autoEquipRunGear(state.equipment, state.bag, offer.item);
  return {
    ...state,
    currency: state.currency - offer.price,
    equipment: applied.equipment,
    bag: applied.bag,
    shop: {
      ...state.shop,
      offers: state.shop.offers.map((candidate) => candidate.id === offerId ? { ...candidate, sold: true } : candidate),
    },
  };
}

export function sellBagItem(state: RpgRoguelikeRunState, itemId: string): RpgRoguelikeRunState {
  const item = state.bag.find((candidate) => candidate.id === itemId);
  if (!item) return state;
  return {
    ...state,
    bag: state.bag.filter((candidate) => candidate.id !== itemId),
    currency: state.currency + item.sellPrice,
  };
}

export function restParty(state: RpgRoguelikeRunState): RpgRoguelikeRunState {
  if (!state.shop || state.currency < state.shop.restCost) return state;
  const needsRest = state.playerHp > 0 && state.playerHp < 100
    || state.roster.some((member) => member.hp > 0 && member.hp < member.stats.maxHp);
  if (!needsRest) return state;
  const roster = state.roster.map((member) => member.hp > 0 ? { ...member, hp: member.stats.maxHp } : member);
  return {
    ...state,
    currency: state.currency - state.shop.restCost,
    playerHp: state.playerHp > 0 ? 100 : state.playerHp,
    roster,
  };
}

export function revivePartyMember(state: RpgRoguelikeRunState, memberId: string): RpgRoguelikeRunState {
  if (!state.shop || state.currency < state.shop.reviveCost) return state;
  const member = state.roster.find((candidate) => candidate.instanceId === memberId);
  if (!member || member.hp > 0) return state;
  const roster = state.roster.map((candidate) => candidate.instanceId === memberId
    ? { ...candidate, hp: Math.max(1, Math.ceil(candidate.stats.maxHp * 0.5)) }
    : candidate);
  return {
    ...state,
    currency: state.currency - state.shop.reviveCost,
    roster,
  };
}
