import type {
  CharacterAppearanceV1,
  CharacterHeldItemVisual,
  CharacterPartIdFor,
} from "./characterAppearance";
import {
  heldItemForModel,
  weaponDefinition,
  weaponDefinitionsForSlot,
  weaponUsesBothHands,
  type CharacterWeaponCategory,
  type CharacterWeaponModelId,
  type WeaponDefinition,
} from "./weaponCatalog";

export type AppearanceSlotId = "head" | "upper" | "lower" | "headwear" | "back" | "main-hand-family" | "main-hand" | "off-hand";

interface AppearanceChoice<Value> {
  value: Value;
  label: string;
}

export interface AppearanceSlotDefinition {
  id: AppearanceSlotId;
  label: string;
  assetNote: string;
}

export const APPEARANCE_SLOT_DEFINITIONS: readonly AppearanceSlotDefinition[] = [
  { id: "head", label: "Face + hair", assetNote: "Combined in current character assets" },
  { id: "upper", label: "Shirt + arms", assetNote: "Combined in current character assets" },
  { id: "lower", label: "Pants + shoes", assetNote: "Combined in current character assets" },
  { id: "headwear", label: "Hat", assetNote: "Separate skinned equipment" },
  { id: "back", label: "Back item", assetNote: "Capes, packs, and skinned quiver" },
  { id: "main-hand-family", label: "Weapon type", assetNote: "10 Claudecraft families" },
  { id: "main-hand", label: "Weapon style", assetNote: "49 imported designs" },
  { id: "off-hand", label: "Offhand", assetNote: "Saved while a two-hander is active" },
] as const;

const HEAD_CHOICES: readonly AppearanceChoice<CharacterPartIdFor<"head">>[] = [
  { value: "priest-head", label: "First Light" },
  { value: "druid-head", label: "Grove" },
  { value: "mage-head", label: "Mystic" },
  { value: "ranger-head", label: "Wayfinder" },
  { value: "knight-head", label: "Vanguard" },
  { value: "rogue-head", label: "Chronicle" },
];

const UPPER_CHOICES: readonly AppearanceChoice<CharacterPartIdFor<"upper-body">>[] = [
  { value: "priest-upper", label: "First Light vestments" },
  { value: "druid-upper", label: "Grove leathers" },
  { value: "mage-upper", label: "Mystic robes" },
  { value: "ranger-upper", label: "Wayfinder mail" },
  { value: "knight-upper", label: "Vanguard plate" },
  { value: "rogue-upper", label: "Chronicle coat" },
];

const LOWER_CHOICES: readonly AppearanceChoice<CharacterPartIdFor<"lower-body">>[] = [
  { value: "priest-lower", label: "First Light boots" },
  { value: "druid-lower", label: "Grove boots" },
  { value: "mage-lower", label: "Mystic boots" },
  { value: "ranger-lower", label: "Wayfinder boots" },
  { value: "knight-lower", label: "Vanguard greaves" },
  { value: "rogue-lower", label: "Chronicle boots" },
];

const HEADWEAR_CHOICES: readonly AppearanceChoice<CharacterPartIdFor<"headwear"> | null>[] = [
  { value: null, label: "None" },
  { value: "mage-hat", label: "Mystic hat" },
  { value: "knight-helmet", label: "Vanguard helm" },
];

const BACK_CHOICES: readonly AppearanceChoice<CharacterPartIdFor<"back"> | null>[] = [
  { value: null, label: "None" },
  { value: "priest-cape", label: "First Light mantle" },
  { value: "druid-backpack", label: "Grove pack" },
  { value: "mage-cape", label: "Mystic cape" },
  { value: "ranger-cape", label: "Wayfinder cape" },
  { value: "ranger-quiver", label: "Wayfinder quiver" },
  { value: "knight-cape", label: "Vanguard cape" },
  { value: "rogue-cape", label: "Chronicle cape" },
];

const MAIN_HAND_CATEGORY_ORDER = [
  "staff",
  "wand",
  "sword",
  "axe",
  "hammer",
  "dagger",
  "crossbow",
  "halberd",
  "scythe",
  "spear",
] as const satisfies readonly CharacterWeaponCategory[];

const WEAPON_CATEGORY_LABELS: Record<CharacterWeaponCategory, string> = {
  axe: "Axes",
  crossbow: "Crossbows",
  dagger: "Daggers",
  halberd: "Halberds",
  hammer: "Hammers",
  scythe: "Scythes",
  shield: "Shields",
  spear: "Spears",
  spellbook: "Spellbooks",
  staff: "Staves",
  sword: "Swords",
  wand: "Wands",
};
const MAIN_HAND_CATEGORY_CHOICES: readonly AppearanceChoice<CharacterWeaponCategory>[] = MAIN_HAND_CATEGORY_ORDER.map((value) => ({
  value,
  label: WEAPON_CATEGORY_LABELS[value],
}));

function definitionsInCategory(
  definitions: readonly WeaponDefinition[],
  category: CharacterWeaponCategory,
) {
  return definitions.filter((definition) => definition.category === category);
}

const MAIN_HAND_DEFINITIONS = MAIN_HAND_CATEGORY_ORDER.flatMap((category) =>
  definitionsInCategory(weaponDefinitionsForSlot("main"), category));
const MAIN_HAND_CHOICES: readonly AppearanceChoice<CharacterHeldItemVisual>[] = MAIN_HAND_DEFINITIONS.map((definition) => ({
  value: heldItemForModel(definition.id as CharacterWeaponModelId),
  label: definition.label,
}));
const OFF_HAND_CATEGORY_ORDER = ["shield", "spellbook", "dagger", "sword", "axe"] as const;
const OFF_HAND_DEFINITIONS = OFF_HAND_CATEGORY_ORDER.flatMap((category) =>
  definitionsInCategory(weaponDefinitionsForSlot("off"), category));
const OFF_HAND_CHOICES: readonly AppearanceChoice<CharacterHeldItemVisual | undefined>[] = [
  { value: undefined, label: "None" },
  ...OFF_HAND_DEFINITIONS.map((definition) => ({
    value: heldItemForModel(definition.id as CharacterWeaponModelId),
    label: definition.label,
  })),
];

function cycleChoice<Value>(
  choices: readonly AppearanceChoice<Value>[],
  current: Value,
  direction: -1 | 1,
  equals: (left: Value, right: Value) => boolean = Object.is,
) {
  const currentIndex = Math.max(0, choices.findIndex((choice) => equals(choice.value, current)));
  return choices[(currentIndex + direction + choices.length) % choices.length];
}

function heldItemEquals(left: CharacterHeldItemVisual | undefined, right: CharacterHeldItemVisual | undefined) {
  return left?.modelId === right?.modelId && left?.grip === right?.grip;
}

function mainHandCategory(appearance: CharacterAppearanceV1): CharacterWeaponCategory {
  return weaponDefinition(appearance.mainHand.modelId).category as CharacterWeaponCategory;
}

export function appearanceSlotEnabled(appearance: CharacterAppearanceV1, slotId: AppearanceSlotId) {
  return slotId !== "off-hand" || !weaponUsesBothHands(appearance.mainHand.modelId);
}

export function appearanceSlotLabel(appearance: CharacterAppearanceV1, slotId: AppearanceSlotId) {
  if (slotId === "head") return HEAD_CHOICES.find((choice) => choice.value === appearance.headPartId)?.label ?? appearance.headPartId;
  if (slotId === "upper") return UPPER_CHOICES.find((choice) => choice.value === appearance.upperBodyPartId)?.label ?? appearance.upperBodyPartId;
  if (slotId === "lower") return LOWER_CHOICES.find((choice) => choice.value === appearance.lowerBodyPartId)?.label ?? appearance.lowerBodyPartId;
  if (slotId === "headwear") return HEADWEAR_CHOICES.find((choice) => choice.value === appearance.headwearPartId)?.label ?? "None";
  if (slotId === "back") return BACK_CHOICES.find((choice) => choice.value === appearance.backPartId)?.label ?? "None";
  if (slotId === "main-hand-family") return WEAPON_CATEGORY_LABELS[mainHandCategory(appearance)];
  if (slotId === "main-hand") return MAIN_HAND_CHOICES.find((choice) => heldItemEquals(choice.value, appearance.mainHand))?.label ?? appearance.mainHand.modelId;
  if (!appearanceSlotEnabled(appearance, slotId)) return "Hidden by two-hander";
  return OFF_HAND_CHOICES.find((choice) => heldItemEquals(choice.value, appearance.offHand))?.label ?? "None";
}

export function cycleAppearanceSlot(
  appearance: CharacterAppearanceV1,
  slotId: AppearanceSlotId,
  direction: -1 | 1,
): CharacterAppearanceV1 {
  if (slotId === "head") return { ...appearance, headPartId: cycleChoice(HEAD_CHOICES, appearance.headPartId, direction).value };
  if (slotId === "upper") return { ...appearance, upperBodyPartId: cycleChoice(UPPER_CHOICES, appearance.upperBodyPartId, direction).value };
  if (slotId === "lower") return { ...appearance, lowerBodyPartId: cycleChoice(LOWER_CHOICES, appearance.lowerBodyPartId, direction).value };
  if (slotId === "headwear") return { ...appearance, headwearPartId: cycleChoice(HEADWEAR_CHOICES, appearance.headwearPartId, direction).value };
  if (slotId === "back") return { ...appearance, backPartId: cycleChoice(BACK_CHOICES, appearance.backPartId, direction).value };
  if (slotId === "main-hand-family") {
    const category = cycleChoice(MAIN_HAND_CATEGORY_CHOICES, mainHandCategory(appearance), direction).value;
    const definition = MAIN_HAND_DEFINITIONS.find((candidate) => candidate.category === category)!;
    return { ...appearance, mainHand: heldItemForModel(definition.id as CharacterWeaponModelId) };
  }
  if (slotId === "main-hand") {
    const choices = MAIN_HAND_CHOICES.filter((choice) => weaponDefinition(choice.value.modelId).category === mainHandCategory(appearance));
    const mainHand = cycleChoice(choices, appearance.mainHand, direction, heldItemEquals).value;
    return { ...appearance, mainHand: { ...mainHand } };
  }
  if (!appearanceSlotEnabled(appearance, slotId)) return appearance;
  const offHand = cycleChoice(OFF_HAND_CHOICES, appearance.offHand, direction, heldItemEquals).value;
  return { ...appearance, offHand: offHand ? { ...offHand } : undefined };
}

export function appearancesMatch(left: CharacterAppearanceV1, right: CharacterAppearanceV1) {
  return left.headPartId === right.headPartId
    && left.upperBodyPartId === right.upperBodyPartId
    && left.lowerBodyPartId === right.lowerBodyPartId
    && left.headwearPartId === right.headwearPartId
    && left.backPartId === right.backPartId
    && heldItemEquals(left.mainHand, right.mainHand)
    && heldItemEquals(left.offHand, right.offHand);
}
