import { describe, expect, it } from "vitest";
import {
  CHARACTER_PART_CATALOG,
  CHARACTER_WEAPON_GRIP_BY_MODEL,
  characterAppearancePartIds,
  normalizeCharacterAppearance,
  resolveCharacterModelMode,
  type CharacterAppearanceV1,
  type CharacterPartSlot,
} from "./characterAppearance";
import { createDefaultHealerAppearance, HEALER_VISUAL_PROFILES } from "./healerVisuals";

const EXPECTED_SLOTS: readonly CharacterPartSlot[] = ["head", "upper-body", "lower-body"];

describe("modular character appearance", () => {
  it("defaults to modular rendering and supports query or build rollback", () => {
    expect(resolveCharacterModelMode({})).toBe("modular");
    expect(resolveCharacterModelMode({ search: "?characterModels=legacy" })).toBe("legacy");
    expect(resolveCharacterModelMode({ search: "?legacyCharacterModels" })).toBe("legacy");
    expect(resolveCharacterModelMode({ envMode: "legacy", search: "?characterModels=modular" })).toBe("legacy");
  });

  it("gives every healer a valid versioned appearance", () => {
    for (const [classId, profile] of Object.entries(HEALER_VISUAL_PROFILES)) {
      const appearance: CharacterAppearanceV1 = profile.appearance;
      expect(appearance.version, classId).toBe(1);
      expect(appearance.rigId, classId).toBe("medium");
      const slots = characterAppearancePartIds(appearance)
        .map((partId) => CHARACTER_PART_CATALOG[partId].slot);
      for (const requiredSlot of EXPECTED_SLOTS) expect(slots, `${classId}:${requiredSlot}`).toContain(requiredSlot);
      expect(new Set(slots).size, classId).toBe(slots.length);
    }
  });

  it("keeps every catalog part inside one declared slot", () => {
    for (const [partId, definition] of Object.entries(CHARACTER_PART_CATALOG)) {
      expect(definition.nodeNames.length, partId).toBeGreaterThan(0);
      expect(new Set(definition.nodeNames).size, partId).toBe(definition.nodeNames.length);
    }
  });

  it("normalizes mixed appearances and canonicalizes every weapon grip", () => {
    const fallback = createDefaultHealerAppearance("priest");
    const mixed = normalizeCharacterAppearance({
      version: 1,
      rigId: "medium",
      scaleSourceMemberId: "brann",
      headPartId: "rogue-head",
      upperBodyPartId: "knight-upper",
      lowerBodyPartId: "ranger-lower",
      headwearPartId: "mage-hat",
      backPartId: "druid-backpack",
      mainHand: { modelId: "sword", grip: "staff" },
      offHand: { modelId: "spellbook", grip: "sword" },
    }, fallback);

    expect(mixed).toEqual({
      version: 1,
      rigId: "medium",
      scaleSourceMemberId: "brann",
      headPartId: "rogue-head",
      upperBodyPartId: "knight-upper",
      lowerBodyPartId: "ranger-lower",
      headwearPartId: "mage-hat",
      backPartId: "druid-backpack",
      mainHand: { modelId: "cc/adv_sword_1handed", grip: "upright" },
      offHand: { modelId: "cc/spellbook_open", grip: "prop" },
    });
    expect(mixed).not.toBe(fallback);
    expect(mixed.mainHand).not.toBe(fallback.mainHand);
    expect(Object.keys(CHARACTER_WEAPON_GRIP_BY_MODEL)).toHaveLength(54);
    expect(CHARACTER_WEAPON_GRIP_BY_MODEL).toMatchObject({
      "cc/adv_druid_staff": "staff",
      "cc/adv_sword_1handed": "upright",
      "cc/shield_badge": "prop",
      "cc/adv_wand": "wand",
      "cc/adv_dagger": "dagger",
      "cc/crossbow_2handed": "crossbow",
    });
  });

  it("repairs invalid fields without discarding safe choices", () => {
    const fallback = createDefaultHealerAppearance("paladin");
    const normalized = normalizeCharacterAppearance({
      version: 1,
      rigId: "medium",
      scaleSourceMemberId: "intruder",
      headPartId: "mage-upper",
      upperBodyPartId: "rogue-upper",
      lowerBodyPartId: "toString",
      headwearPartId: "druid-backpack",
      backPartId: null,
      mainHand: { modelId: "wand", grip: "invalid-grip" },
      offHand: { modelId: "toString", grip: "prop" },
    }, fallback);

    expect(normalized).toMatchObject({
      scaleSourceMemberId: fallback.scaleSourceMemberId,
      headPartId: fallback.headPartId,
      upperBodyPartId: "rogue-upper",
      lowerBodyPartId: fallback.lowerBodyPartId,
      headwearPartId: fallback.headwearPartId,
      backPartId: null,
      mainHand: { modelId: "cc/adv_wand", grip: "wand" },
    });
    expect(normalized.offHand).toEqual(fallback.offHand);
  });

  it("falls back as one unit for unknown appearance versions or rigs", () => {
    const fallback = createDefaultHealerAppearance("druid");
    expect(normalizeCharacterAppearance({ ...fallback, version: 2 }, fallback)).toEqual(fallback);
    expect(normalizeCharacterAppearance({ ...fallback, rigId: "large" }, fallback)).toEqual(fallback);
    expect(normalizeCharacterAppearance(null, fallback)).toEqual(fallback);
    expect(normalizeCharacterAppearance(null, fallback)).not.toBe(fallback);
  });
});
