import { describe, expect, it } from "vitest";
import { diffBottomGameSnapshot, executeFrontendCommand, executeGameCommand, type BottomGameSnapshot } from "./dualScreenSync";
import { useGameStore } from "../game/store";
import { useFrontendStore } from "../frontend/store";

function snapshot(): BottomGameSnapshot {
  return {
    bossId: "bulldrome",
    bossInstanceId: "boss-0-bulldrome",
    paused: false,
    healerClassId: "priest",
    phase: "combat",
    round: 1,
    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, 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 },
      nextEventId: 1,
    },
    mana: 100,
    maxMana: 100,
    selectedMemberId: "brann",
    cooldowns: { mend: 0, renew: 0, shield: 0, purify: 0, radiance: 0, barrier: 0 },
    globalCooldownUntil: 0,
    activeTab: "combat",
    selectedItemId: "",
    inventory: [],
    playerPosition: [0, 0],
    activeCast: null,
    barrier: { center: [0, 0], expiresAt: 0, nextHealAt: 0 },
  };
}

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 });
  });

  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 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}`); },
    });

    executeGameCommand({ name: "continueRoguelikeRound" });
    executeGameCommand({ name: "startRogueTrialsEndless" });
    executeFrontendCommand({ name: "selectPassiveAbility", abilityId: "shield" });
    executeFrontendCommand({ name: "selectPassiveInfusion", passiveId: "shield-guard" });
    expect(calls).toEqual(["continue", "endless", "ability:shield", "passive:shield-guard"]);

    useGameStore.setState({ continueRoguelikeRound: originalContinue, startRogueTrialsEndless: originalStartEndless });
    useFrontendStore.setState({ selectPassiveAbility: originalSelectAbility, selectPassiveInfusion: originalSelectPassive });
  });
});
