import { describe, expect, it } from "vitest";
import { createClassAbilityLoadout } from "../game/healers";
import { diffBottomGameSnapshot, executeFrontendCommand, executeGameCommand, type BottomGameSnapshot } from "./dualScreenSync";
import { useGameStore } from "../game/store";
import { getFrontendSnapshot, useFrontendStore } from "../frontend/store";
import { createHockeyHealingState } from "../game/hockeyHealing";
import { createBlockbreakerState } from "../game/blockbreaker";
import { createAetherAssaultState } from "../game/aetherAssault";
import { createHockeyPvpState } from "../game/hockeyHealingPvp";
import { freshParty } from "../game/data";
import { createBossMotionState, createBossState } from "../game/bossMechanics";
import { createPartyCombatState } from "../game/partyCombat";
import { createDefaultHealerAppearance } from "../game/healerVisuals";
import { heldItemForModel } from "../game/weaponCatalog";

function snapshot(): BottomGameSnapshot {
  return {
    bossId: "bulldrome",
    bossInstanceId: "boss-0-bulldrome",
    paused: false,
  healerClassId: "priest",
  abilityLoadout: createClassAbilityLoadout("priest"),
    phase: "combat",
    round: 1,
  runMode: "encounter",
  activityMode: "boss",
  rpgRun: null,
  rpgFocusId: null,
  rpgSpellResources: { verdancy: 0, tidalSurge: 0, conviction: 0, chronoshards: 0 },
    endlessMode: false,
    endlessBossKills: 0,
    endlessChoiceSelection: "continue",
    runModifiers: {
      mendExtraTargets: 0, mendManaMultiplier: 1, mendCastTimeMultiplier: 1,
      renewExtraTargets: 0, renewDurationBonus: 0, renewHealingMultiplier: 1,
      shieldExtraTargets: 0, shieldAbsorbMultiplier: 1, shieldDamageTakenMultiplier: 1,
      purifyAppliesRenew: false, purifyAppliesShield: false, purifyExtraTargets: 0,
      radianceCooldownMultiplier: 1, radianceAppliesRenew: false, radianceAbsorb: 0,
      barrierCooldownMultiplier: 1, barrierDurationBonus: 0, barrierHealingPerSecond: 0,
    },
    time: 10,
    party: [],
    boss: { id: "bulldrome", name: "Bulldrome", maxHp: 100, hp: 100, nextMeleeAt: 1 },
    additionalBosses: [],
    partyPositions: { aelia: [0, 0], brann: [0, 0], nia: [0, 0], orin: [0, 0], vale: [0, 0] },
    bossMotion: {
      bossId: "bulldrome", activeMechanicId: null, formationOffsetX: 0, mode: "holding", position: [0, 0], chargeStart: [0, 0], chargeEnd: [0, 0], chargeTargetId: "aelia", chargeHitIds: [], phaseEndsAt: 0,
      chargeCount: 0, pounceTargetId: "aelia", pounceCenter: [0, 0],
      nextMechanicAt: Infinity, mechanicCount: 0, phaseStartedAt: 0, lastMeleeAt: -1, mechanicHitIds: [], mechanicNextDamageAt: {}, tetherIds: [], tetherBreakDistance: 0,
      breathAngle: 0, breathStartAngle: 0, breathEndAngle: 0, hazards: [], slashLanes: [], poolMechanicCount: 0, poolTelegraphs: [],
    },
    partyCombat: {
      combatants: {
        brann: { id: "brann", readyAt: 0, resource: 0, points: 0, cooldowns: {}, activeAction: null, visualAction: null, overchargeStacks: 0, bladeFlurryUntil: 0, revengeReadyUntil: 0, lastHp: 100, damageDone: 0 },
        nia: { id: "nia", readyAt: 0, resource: 0, points: 0, cooldowns: {}, activeAction: null, visualAction: null, overchargeStacks: 0, bladeFlurryUntil: 0, revengeReadyUntil: 0, lastHp: 100, damageDone: 0 },
        orin: { id: "orin", readyAt: 0, resource: 0, points: 0, cooldowns: {}, activeAction: null, visualAction: null, overchargeStacks: 0, bladeFlurryUntil: 0, revengeReadyUntil: 0, lastHp: 100, damageDone: 0 },
        vale: { id: "vale", readyAt: 0, resource: 0, points: 0, cooldowns: {}, activeAction: null, visualAction: null, overchargeStacks: 0, bladeFlurryUntil: 0, revengeReadyUntil: 0, lastHp: 100, damageDone: 0 },
      },
      tankAura: { expiresAt: 0, radius: 3, damageReduction: 0.3, sourceId: "brann" },
      nextEventId: 1,
    },
    mana: 100,
    maxMana: 100,
    selectedMemberId: "brann",
    cooldowns: { ability1: 0, ability2: 0, ability3: 0, ability4: 0, ability5: 0, ability6: 0 },
    globalCooldownUntil: 0,
    activeTab: "combat",
    selectedItemId: "",
    inventory: [],
    playerPosition: [0, 0],
    activeCast: null,
    barrier: { kind: null, center: [0, 0], expiresAt: 0, nextHealAt: 0 },
    healerMechanic: {
      resource: 0,
      maxResource: 0,
      flourishExpiresAt: 0,
      nextPulseAt: 0,
      beaconTargetId: null,
      beaconExpiresAt: 0,
      avengingCrusaderExpiresAt: 0,
      temporalAnchor: null,
      timeLoop: null,
    },
    hockey: createHockeyHealingState(false),
    blockbreaker: createBlockbreakerState(false),
    aetherAssault: createAetherAssaultState(false),
    hockeyPvp: createHockeyPvpState(),
    hockeyPvpOpponent: {
      party: freshParty(),
      partyPositions: { aelia: [0, 4.5], brann: [0, 0], nia: [-3, 2], orin: [3, 2], vale: [0, -2] },
      boss: createBossState("bulldrome"),
      bossMotion: createBossMotionState("bulldrome"),
      partyCombat: createPartyCombatState(freshParty()),
    },
  };
}

describe("dual-screen game snapshots", () => {
  it("sends a full initial projection, then only semantic changes", () => {
    const initial = snapshot();
    expect(diffBottomGameSnapshot(undefined, initial)).toBe(initial);

    const clonedWithoutChanges = structuredClone(initial);
    expect(diffBottomGameSnapshot(initial, clonedWithoutChanges)).toEqual({});

    const next = { ...clonedWithoutChanges, time: 10.1, mana: 97 };
    expect(diffBottomGameSnapshot(clonedWithoutChanges, next)).toEqual({ time: 10.1, mana: 97 });

    const hockey = createHockeyHealingState(true);
    const hockeyNext = { ...next, runMode: "hockey-healing" as const, hockey };
    expect(diffBottomGameSnapshot(next, hockeyNext)).toEqual({ runMode: "hockey-healing", hockey });

    const aetherAssault = createAetherAssaultState(true, 42);
    const aetherNext = { ...hockeyNext, runMode: "aether-assault" as const, aetherAssault };
    expect(diffBottomGameSnapshot(hockeyNext, aetherNext)).toEqual({ runMode: "aether-assault", aetherAssault });
  });

  it("routes maxed-run continuation and passive filter commands", () => {
    const originalContinue = useGameStore.getState().continueRoguelikeRound;
    const originalStartEndless = useGameStore.getState().startRogueTrialsEndless;
    const originalSelectAbility = useFrontendStore.getState().selectPassiveAbility;
    const originalSelectPassive = useFrontendStore.getState().selectPassiveInfusion;
    const originalSelectProfileView = useFrontendStore.getState().selectProfileCollectionView;
    const originalSelectProfileGroup = useFrontendStore.getState().selectProfileGroup;
    const originalSelectProfileStat = useFrontendStore.getState().selectProfileStat;
    const calls: string[] = [];
    useGameStore.setState({
      continueRoguelikeRound: () => { calls.push("continue"); return true; },
      startRogueTrialsEndless: () => { calls.push("endless"); return true; },
    });
    useFrontendStore.setState({
      selectPassiveAbility: (abilityId) => { calls.push(`ability:${abilityId}`); },
      selectPassiveInfusion: (passiveId) => { calls.push(`passive:${passiveId}`); },
      selectProfileCollectionView: (view) => { calls.push(`profile-view:${view}`); },
      selectProfileGroup: (groupId) => { calls.push(`profile-group:${groupId}`); },
      selectProfileStat: (statId) => { calls.push(`profile-stat:${statId}`); },
    });

    executeGameCommand({ name: "continueRoguelikeRound" });
    executeGameCommand({ name: "startRogueTrialsEndless" });
    executeFrontendCommand({ name: "selectPassiveAbility", abilityId: "ability3" });
    executeFrontendCommand({ name: "selectPassiveInfusion", passiveId: "shield-guard" });
    executeFrontendCommand({ name: "selectProfileCollectionView", view: "stats" });
    executeFrontendCommand({ name: "selectProfileGroup", groupId: "bind-venom" });
    executeFrontendCommand({ name: "selectProfileStat", statId: "broodfang-spider" });
    expect(calls).toEqual([
      "continue",
      "endless",
      "ability:ability3",
      "passive:shield-guard",
      "profile-view:stats",
      "profile-group:bind-venom",
      "profile-stat:broodfang-spider",
    ]);

    useGameStore.setState({ continueRoguelikeRound: originalContinue, startRogueTrialsEndless: originalStartEndless });
    useFrontendStore.setState({
      selectPassiveAbility: originalSelectAbility,
      selectPassiveInfusion: originalSelectPassive,
      selectProfileCollectionView: originalSelectProfileView,
      selectProfileGroup: originalSelectProfileGroup,
      selectProfileStat: originalSelectProfileStat,
    });
  });

  it("includes authoritative profile selection in companion snapshots", () => {
    const original = useFrontendStore.getState();
    original.selectProfileCollectionView("stats");
    original.selectProfileGroup("bind-venom");
    original.selectProfileStat("broodfang-spider");

    const snapshot = getFrontendSnapshot();
    expect(snapshot.profileCollectionView).toBe("stats");
    expect(snapshot.selectedProfileGroupId).toBe("bind-venom");
    expect(snapshot.selectedProfileStatId).toBe("broodfang-spider");
    expect("selectProfileCollectionView" in snapshot).toBe(false);

    useFrontendStore.setState({
      profileCollectionView: original.profileCollectionView,
      selectedProfileGroupId: original.selectedProfileGroupId,
      selectedProfileStatId: original.selectedProfileStatId,
    });
  });

  it("routes Appearance Lab commands to the authoritative frontend store", () => {
    const original = useFrontendStore.getState();
    const calls: string[] = [];
    const appearance = {
      ...createDefaultHealerAppearance("priest"),
      headPartId: "rogue-head" as const,
      mainHand: heldItemForModel("cc/staff_d"),
    };
    useFrontendStore.setState({
      openAppearanceLab: () => { calls.push("open"); },
      selectAppearanceClass: (classId) => { calls.push(`class:${classId}`); },
      updateAppearanceDraft: (draft) => { calls.push(`draft:${draft.headPartId}:${draft.mainHand.modelId}`); },
      resetAppearanceDraft: () => { calls.push("reset"); },
      saveAppearanceDraft: () => { calls.push("save"); return true; },
      closeAppearanceLab: () => { calls.push("close"); },
      setAppearancePreviewMode: (mode) => { calls.push(`mode:${mode}`); },
      setAppearancePreviewAnimation: (animation) => { calls.push(`animation:${animation}`); },
    });

    executeFrontendCommand({ name: "openAppearanceLab" });
    executeFrontendCommand({ name: "selectAppearanceClass", classId: "chronomancer" });
    executeFrontendCommand({ name: "updateAppearanceDraft", appearance });
    executeFrontendCommand({ name: "resetAppearanceDraft" });
    executeFrontendCommand({ name: "saveAppearanceDraft" });
    executeFrontendCommand({ name: "setAppearancePreviewMode", mode: "legacy" });
    executeFrontendCommand({ name: "setAppearancePreviewAnimation", animation: "cast" });
    executeFrontendCommand({ name: "closeAppearanceLab" });

    expect(calls).toEqual([
      "open",
      "class:chronomancer",
      "draft:rogue-head:cc/staff_d",
      "reset",
      "save",
      "mode:legacy",
      "animation:cast",
      "close",
    ]);

    useFrontendStore.setState({
      openAppearanceLab: original.openAppearanceLab,
      selectAppearanceClass: original.selectAppearanceClass,
      updateAppearanceDraft: original.updateAppearanceDraft,
      resetAppearanceDraft: original.resetAppearanceDraft,
      saveAppearanceDraft: original.saveAppearanceDraft,
      closeAppearanceLab: original.closeAppearanceLab,
      setAppearancePreviewMode: original.setAppearancePreviewMode,
      setAppearancePreviewAnimation: original.setAppearancePreviewAnimation,
    });
  });

  it("routes Class Help selection through the authoritative frontend store", () => {
    const original = useFrontendStore.getState();
    const calls: string[] = [];
    useFrontendStore.setState({
      openClassHelp: () => { calls.push("open"); },
      selectGuideClass: (classId) => { calls.push(`class:${classId}`); },
      selectGuideAbility: (abilityId) => { calls.push(`ability:${abilityId}`); },
    });

    executeFrontendCommand({ name: "openClassHelp" });
    executeFrontendCommand({ name: "selectGuideClass", classId: "shaman" });
    executeFrontendCommand({ name: "selectGuideAbility", abilityId: "ability5" });

    expect(calls).toEqual(["open", "class:shaman", "ability:ability5"]);

    useFrontendStore.setState({
      openClassHelp: original.openClassHelp,
      selectGuideClass: original.selectGuideClass,
      selectGuideAbility: original.selectGuideAbility,
    });
  });
});
