import assert from "node:assert/strict";
import { describe, it } from "node:test";
import * as THREE from "three";
import {
  CROW_METRICS,
  DOG_METRICS,
  HUMANOID_METRICS,
  buildCrow,
  buildDog,
  buildHumanoid,
  cloneCrow,
  cloneDog,
  cloneHumanoid,
  disposeCrow,
  disposeDog,
  disposeHumanoid,
  poseCrow,
  poseCrowFlight,
  poseDogAttention,
  poseDogWalk,
  poseHumanoid,
} from "../assets/actors/index.ts";

function meshes(root: THREE.Object3D): THREE.Mesh[] {
  const result: THREE.Mesh[] = [];
  root.traverse((object) => {
    if (object instanceof THREE.Mesh) result.push(object);
  });
  return result;
}

function assertSharedResources(source: THREE.Group, clone: THREE.Group): void {
  const a = meshes(source);
  const b = meshes(clone);
  assert.equal(b.length, a.length);
  for (let i = 0; i < a.length; i++) {
    assert.equal(b[i]!.geometry, a[i]!.geometry);
    assert.equal(b[i]!.material, a[i]!.material);
  }
}

describe("procedural actor assets", () => {
  it("builds a metre-scale customizable humanoid with a front face surface", () => {
    const rig = buildHumanoid({ bodyShape: "broad", outfitColor: 0x224466 });
    const box = new THREE.Box3().setFromObject(rig.root);
    const size = box.getSize(new THREE.Vector3());
    assert.ok(Math.abs(size.y - HUMANOID_METRICS.height) < 0.08, `height ${size.y}`);
    assert.ok(box.min.y > -0.015, `floor ${box.min.y}`);
    assert.equal(rig.root.userData.forwardAxis, "-Z");
    assert.ok(rig.face.getWorldPosition(new THREE.Vector3()).z < 0);

    const clone = cloneHumanoid(rig);
    assertSharedResources(rig.root, clone.root);
    poseHumanoid(clone, { walkPhase: Math.PI / 2, stride: 0.5, headYaw: 0.3 });
    assert.notEqual(clone.joints.hipLeft.rotation.x, rig.joints.hipLeft.rotation.x);
    assert.equal(clone.joints.head.rotation.y, 0.3);
    assert.equal(clone.ownsMaterials, false);
    disposeHumanoid(rig);
  });

  it("builds an anonymous office dog with independent attention and gait joints", () => {
    const rig = buildDog();
    const box = new THREE.Box3().setFromObject(rig.root);
    const size = box.getSize(new THREE.Vector3());
    assert.ok(size.y <= DOG_METRICS.height + 0.08, `height ${size.y}`);
    assert.ok(size.z >= DOG_METRICS.length - 0.1, `length ${size.z}`);
    assert.equal(rig.root.userData.actorType, "anonymous-dog");
    for (const side of ["left", "right"] as const) {
      const eye = rig.root.getObjectByName(`dog.eye.${side}`);
      const catchlight = rig.root.getObjectByName(`dog.eye-catchlight.${side}`);
      assert.ok(eye instanceof THREE.Mesh);
      assert.ok(catchlight instanceof THREE.Mesh);
      assert.ok(eye.getWorldPosition(new THREE.Vector3()).z < 0, `${side} eye faces -Z`);
      assert.ok(catchlight.getWorldPosition(new THREE.Vector3()).z < eye.getWorldPosition(new THREE.Vector3()).z);
    }

    const clone = cloneDog(rig);
    assertSharedResources(rig.root, clone.root);
    poseDogWalk(clone, Math.PI / 2);
    poseDogAttention(clone, 0.4, Math.PI / 2);
    assert.notEqual(clone.joints.legFrontLeft.rotation.x, rig.joints.legFrontLeft.rotation.x);
    assert.equal(clone.joints.head.rotation.y, 0.4);
    assert.notEqual(clone.joints.tail.rotation.z, 0);
    disposeDog(rig);
  });

  it("builds a Tera crow with mirrored independent wing joints", () => {
    const rig = buildCrow();
    const box = new THREE.Box3().setFromObject(rig.root);
    assert.ok(box.max.y <= CROW_METRICS.perchedHeight + 0.1, `height ${box.max.y}`);
    assert.equal(rig.root.userData.actorType, "anonymous-crow");
    const beak = rig.root.getObjectByName("crow.beak");
    assert.ok(beak);
    assert.ok(beak.getWorldPosition(new THREE.Vector3()).z < 0);

    const clone = cloneCrow(rig);
    assertSharedResources(rig.root, clone.root);
    poseCrowFlight(clone, Math.PI / 2, 1);
    assert.equal(clone.joints.wingLeft.rotation.z, -clone.joints.wingRight.rotation.z);
    assert.notEqual(clone.joints.wingLeft.rotation.z, rig.joints.wingLeft.rotation.z);
    disposeCrow(rig);
  });

  it("builds a layered v2 feather rig with shoulder, elbow, wrist, tail and claw anatomy", () => {
    const rig = buildCrow();
    assert.equal(rig.root.userData.rigVersion, 2);
    assert.equal(rig.joints.elbowLeft.parent, rig.joints.shoulderLeft);
    assert.equal(rig.joints.wristLeft.parent, rig.joints.elbowLeft);
    assert.equal(rig.joints.elbowRight.parent, rig.joints.shoulderRight);
    assert.equal(rig.joints.wristRight.parent, rig.joints.elbowRight);

    const allMeshes = meshes(rig.root);
    const featherCount = (prefix: string) => allMeshes
      .filter((mesh) => mesh.name.startsWith(prefix))
      .reduce((sum, mesh) => sum + Number(mesh.userData.featherCount ?? 0), 0);
    assert.equal(featherCount("crow.primaries."), 18);
    assert.equal(featherCount("crow.secondaries-"), 14);
    assert.equal(featherCount("crow.tail-feathers."), 9);
    assert.equal(allMeshes.filter((mesh) => mesh.name.startsWith("crow.toe.")).length, 8);
    assert.ok(allMeshes.length <= 36, `${allMeshes.length} meshes keeps one crow bounded for mobile/peers`);
    disposeCrow(rig);
  });

  it("poses a materially different glide, bank, tuck and perch without rebuilding geometry", () => {
    const rig = buildCrow();
    const width = () => new THREE.Box3().setFromObject(rig.root).getSize(new THREE.Vector3()).x;

    poseCrow(rig, { state: "glide", amount: 0.8, flight: 1 });
    const glideWidth = width();
    assert.equal(rig.root.userData.pose, "glide");
    assert.ok(Math.abs(glideWidth - CROW_METRICS.wingspan) < 0.12, `glide width ${glideWidth}`);

    poseCrow(rig, { state: "bank", amount: 0.8, bank: 0.75, flight: 1 });
    assert.equal(rig.root.userData.pose, "bank");
    assert.notEqual(rig.joints.shoulderLeft.rotation.z, -rig.joints.shoulderRight.rotation.z,
      "bank makes the wing dihedral asymmetric");
    assert.ok(rig.joints.tail.rotation.z < 0, "tail counters a right bank");

    poseCrow(rig, { state: "tuck", amount: 1, flight: 1 });
    const tuckWidth = width();
    poseCrow(rig, { state: "perch", amount: 1, flight: 0 });
    const perchWidth = width();
    assert.ok(tuckWidth < glideWidth, `${tuckWidth} tuck < ${glideWidth} glide`);
    assert.ok(perchWidth < tuckWidth, `${perchWidth} perch < ${tuckWidth} tuck`);
    assert.equal(rig.joints.legLeft.rotation.x, 0);
    assert.equal(rig.joints.legRight.rotation.x, 0);
    disposeCrow(rig);
  });

  it("never disposes caller-owned materials unless explicitly requested", () => {
    const source = buildCrow();
    const shared = meshes(source.root)[0]!.material as THREE.Material;
    let disposals = 0;
    shared.addEventListener("dispose", () => disposals++);
    const external = {
      feather: shared,
      sheen: shared,
      beak: shared,
      eye: shared,
      foot: shared,
    };
    const rig = buildCrow({ materials: external });
    disposeCrow(rig);
    assert.equal(disposals, 0);
    disposeCrow(source);
    assert.equal(disposals, 1);
  });
});
