import { describe, expect, it } from "vitest";
import {
  BOSS_HIT_ANIMATION_SECONDS,
  BOSS_MELEE_ANIMATION_SECONDS,
  bossAnimationClipName,
  bossAnimationTrigger,
  isBossAnimationOneShot,
  selectBossAnimationState,
  shouldStartBossAnimation,
  writeBossProceduralPose,
  type BossAnimationClips,
  type BossProceduralPose,
} from "./bossAnimation";

const CLIPS: BossAnimationClips = {
  idle: "Idle",
  move: "Move",
  attack: "Attack",
  special: "Special",
  melee: "Melee",
  death: "Death",
};

function pose(): BossProceduralPose {
  return { x: 0, y: 0, z: 0, pitch: 0, yaw: 0, roll: 0, scaleX: 1, scaleY: 1, scaleZ: 1 };
}

describe("boss animation playback", () => {
  it("prioritizes death, mechanics, melee, and hit reactions in gameplay order", () => {
    const base = { defeated: false, activeMechanic: false, mechanicCue: "idle" as const };
    expect(selectBossAnimationState({ ...base, defeated: true, meleeElapsed: 0, hitElapsed: 0 })).toBe("death");
    expect(selectBossAnimationState({ ...base, activeMechanic: true, mechanicCue: "special", meleeElapsed: 0, hitElapsed: 0 })).toBe("special");
    expect(selectBossAnimationState({ ...base, meleeElapsed: 0, hitElapsed: 0 })).toBe("melee");
    expect(selectBossAnimationState({ ...base, meleeElapsed: BOSS_MELEE_ANIMATION_SECONDS, hitElapsed: 0 })).toBe("hit");
    expect(selectBossAnimationState({ ...base, meleeElapsed: BOSS_MELEE_ANIMATION_SECONDS, hitElapsed: BOSS_HIT_ANIMATION_SECONDS })).toBe("idle");
  });

  it("restarts one-shot melee and hit clips only for new triggers", () => {
    expect(shouldStartBossAnimation("melee", 2, "melee", 2)).toBe(false);
    expect(shouldStartBossAnimation("melee", 2, "melee", 4.5)).toBe(true);
    expect(shouldStartBossAnimation("hit", 1, "hit", 2)).toBe(true);
    expect(shouldStartBossAnimation("move", 0, "move", 3)).toBe(false);
    expect(isBossAnimationOneShot("special")).toBe(true);
  });

  it("uses idle plus procedural recoil when a source model lacks a hit clip", () => {
    expect(bossAnimationClipName(CLIPS, "hit")).toBe("Idle");
    const next = pose();
    writeBossProceduralPose(next, "ricochet", "holding", "hit", BOSS_HIT_ANIMATION_SECONDS / 2, 1);
    expect(next.z).toBeLessThan(0);
    expect(next.pitch).toBeGreaterThan(0);
  });

  it("adds signature windup motion for breath and shockwave bosses", () => {
    const dragon = pose();
    writeBossProceduralPose(dragon, "sky-sweeper", "breath_sweeping", "attack", 0.5, 1);
    expect(dragon.y).toBeGreaterThan(0);
    expect(dragon.pitch).toBeLessThan(0);

    const golem = pose();
    writeBossProceduralPose(golem, "golem", "golem_shockwave", "special", 0.5, 1);
    expect(golem.scaleY).toBeLessThan(1);
    expect(golem.scaleX).toBeGreaterThan(1);
  });

  it("uses semantic triggers for repeated one-shot clips", () => {
    expect(bossAnimationTrigger("attack", 4, 2, 1)).toBe(4);
    expect(bossAnimationTrigger("melee", 4, 2, 1)).toBe(2);
    expect(bossAnimationTrigger("hit", 4, 2, 1)).toBe(1);
  });
});
