/** A compact, friendly anonymous office dog with a poseable head, legs and tail. */
import * as THREE from "three";
import {
  actorMesh,
  disposeActor,
  namedGroup,
  requireGroup,
  type ActorRigBase,
} from "./common.ts";

export const DOG_METRICS = { length: 0.76, shoulderHeight: 0.46, height: 0.74 } as const;

export interface DogMaterials {
  coat: THREE.Material;
  markings: THREE.Material;
  nose: THREE.Material;
  collar: THREE.Material;
}

export interface DogBuildOptions {
  materials?: DogMaterials;
  coatColor?: THREE.ColorRepresentation;
  markingsColor?: THREE.ColorRepresentation;
  collarColor?: THREE.ColorRepresentation;
}

export interface DogJoints {
  body: THREE.Group;
  head: THREE.Group;
  earLeft: THREE.Group;
  earRight: THREE.Group;
  tail: THREE.Group;
  legFrontLeft: THREE.Group;
  legFrontRight: THREE.Group;
  legRearLeft: THREE.Group;
  legRearRight: THREE.Group;
}

export interface DogRig extends ActorRigBase {
  joints: DogJoints;
}

export function createDogMaterials(options: DogBuildOptions = {}): DogMaterials {
  return {
    coat: new THREE.MeshStandardMaterial({ name: "dog.coat", color: options.coatColor ?? 0x9a673d, roughness: 0.92 }),
    markings: new THREE.MeshStandardMaterial({ name: "dog.markings", color: options.markingsColor ?? 0xe4c9a2, roughness: 0.95 }),
    nose: new THREE.MeshStandardMaterial({ name: "dog.nose", color: 0x151719, roughness: 0.72 }),
    collar: new THREE.MeshStandardMaterial({ name: "dog.collar", color: options.collarColor ?? 0x2d92a7, roughness: 0.55 }),
  };
}

function resolveDog(root: THREE.Group, ownsMaterials: boolean): DogRig {
  return {
    root,
    ownsMaterials,
    joints: {
      body: requireGroup(root, "dog.body"),
      head: requireGroup(root, "dog.head"),
      earLeft: requireGroup(root, "dog.ear.left"),
      earRight: requireGroup(root, "dog.ear.right"),
      tail: requireGroup(root, "dog.tail"),
      legFrontLeft: requireGroup(root, "dog.leg.front.left"),
      legFrontRight: requireGroup(root, "dog.leg.front.right"),
      legRearLeft: requireGroup(root, "dog.leg.rear.left"),
      legRearRight: requireGroup(root, "dog.leg.rear.right"),
    },
  };
}

export function buildDog(options: DogBuildOptions = {}): DogRig {
  const m = options.materials ?? createDogMaterials(options);
  const root = namedGroup("dog");
  root.userData.kind = "actor";
  root.userData.actorType = "anonymous-dog";
  root.userData.forwardAxis = "-Z";
  const body = namedGroup("dog.body", [0, 0.39, 0.04]);
  root.add(body);
  body.add(
    actorMesh("dog.torso", new THREE.CapsuleGeometry(0.18, 0.34, 5, 10), m.coat, {
      rotation: [Math.PI / 2, 0, 0],
      scale: [0.82, 1, 0.92],
    }),
    actorMesh("dog.chest", new THREE.SphereGeometry(0.185, 12, 9), m.markings, {
      position: [0, 0.01, -0.17],
      scale: [0.72, 1, 0.56],
    }),
  );

  const head = namedGroup("dog.head", [0, 0.16, -0.32]);
  body.add(head);
  head.add(
    actorMesh("dog.skull", new THREE.SphereGeometry(0.16, 12, 10), m.coat, { scale: [0.82, 0.92, 0.88] }),
    actorMesh("dog.muzzle", new THREE.SphereGeometry(0.105, 12, 8), m.markings, {
      position: [0, -0.045, -0.13],
      scale: [0.8, 0.62, 1],
    }),
    actorMesh("dog.nose", new THREE.SphereGeometry(0.045, 10, 7), m.nose, {
      position: [0, -0.035, -0.224],
      scale: [1.15, 0.72, 0.7],
    }),
    actorMesh("dog.collar", new THREE.TorusGeometry(0.118, 0.016, 6, 18), m.collar, {
      position: [0, -0.11, 0.1],
      rotation: [Math.PI / 2, 0, 0],
      scale: [1, 0.82, 1],
    }),
  );
  for (const side of [-1, 1] as const) {
    const word = side < 0 ? "left" : "right";
    const ear = namedGroup(`dog.ear.${word}`, [side * 0.1, 0.105, -0.015]);
    head.add(ear);
    ear.add(
      actorMesh(`dog.ear-flap.${word}`, new THREE.ConeGeometry(0.075, 0.19, 5), m.coat, {
        position: [side * 0.015, -0.065, 0.02],
        rotation: [0.18, 0, side * 0.28],
      }),
    );
  }

  for (const z of [-0.2, 0.22] as const) {
    for (const side of [-1, 1] as const) {
      const fore = z < 0 ? "front" : "rear";
      const word = side < 0 ? "left" : "right";
      const leg = namedGroup(`dog.leg.${fore}.${word}`, [side * 0.125, -0.1, z]);
      body.add(leg);
      leg.add(
        actorMesh(`dog.leg-mesh.${fore}.${word}`, new THREE.CapsuleGeometry(0.046, 0.19, 3, 7), m.coat, {
          position: [0, -0.135, 0],
        }),
        actorMesh(`dog.paw.${fore}.${word}`, new THREE.SphereGeometry(0.06, 8, 6), m.markings, {
          position: [0, -0.29, -0.022],
          scale: [0.84, 0.48, 1.18],
        }),
      );
    }
  }

  const tail = namedGroup("dog.tail", [0, 0.03, 0.31]);
  body.add(tail);
  tail.rotation.x = 0.68;
  tail.add(
    actorMesh("dog.tail-mesh", new THREE.CapsuleGeometry(0.04, 0.25, 4, 8), m.coat, {
      position: [0, 0.15, 0],
    }),
  );
  return resolveDog(root, !options.materials);
}

export function cloneDog(source: DogRig): DogRig {
  return resolveDog(source.root.clone(true), false);
}

export function poseDogWalk(rig: DogRig, phase: number, amount = 0.55): void {
  const swing = Math.sin(phase) * THREE.MathUtils.clamp(amount, 0, 0.8);
  rig.joints.legFrontLeft.rotation.x = swing;
  rig.joints.legRearRight.rotation.x = swing;
  rig.joints.legFrontRight.rotation.x = -swing;
  rig.joints.legRearLeft.rotation.x = -swing;
  rig.joints.body.position.y = 0.39 + Math.abs(Math.cos(phase)) * Math.abs(swing) * 0.018;
}

export function poseDogAttention(rig: DogRig, lookYaw: number, tailPhase: number): void {
  rig.joints.head.rotation.y = THREE.MathUtils.clamp(lookYaw, -0.9, 0.9);
  rig.joints.head.rotation.x = -0.08;
  rig.joints.earLeft.rotation.z = 0.08;
  rig.joints.earRight.rotation.z = -0.08;
  rig.joints.tail.rotation.z = Math.sin(tailPhase) * 0.72;
}

export function disposeDog(rig: DogRig, options: { disposeMaterials?: boolean } = {}): void {
  disposeActor(rig, options);
}
