/** A low-cost anonymous Tera crow, ready to perch or flap in flight. */
import * as THREE from "three";
import {
  actorMesh,
  disposeActor,
  namedGroup,
  requireGroup,
  type ActorRigBase,
} from "./common.ts";

export const CROW_METRICS = { bodyLength: 0.42, perchedHeight: 0.34, wingspan: 0.84 } as const;

export interface CrowMaterials {
  feather: THREE.Material;
  sheen: THREE.Material;
  beak: THREE.Material;
  eye: THREE.Material;
  foot: THREE.Material;
}

export interface CrowBuildOptions {
  materials?: CrowMaterials;
  featherColor?: THREE.ColorRepresentation;
  sheenColor?: THREE.ColorRepresentation;
}

export interface CrowJoints {
  body: THREE.Group;
  head: THREE.Group;
  wingLeft: THREE.Group;
  wingRight: THREE.Group;
  tail: THREE.Group;
}

export interface CrowRig extends ActorRigBase {
  joints: CrowJoints;
}

export function createCrowMaterials(options: CrowBuildOptions = {}): CrowMaterials {
  return {
    feather: new THREE.MeshStandardMaterial({ name: "crow.feather", color: options.featherColor ?? 0x111519, roughness: 0.62, metalness: 0.12 }),
    sheen: new THREE.MeshStandardMaterial({ name: "crow.sheen", color: options.sheenColor ?? 0x1f2c36, roughness: 0.4, metalness: 0.28 }),
    beak: new THREE.MeshStandardMaterial({ name: "crow.beak", color: 0x202327, roughness: 0.78 }),
    eye: new THREE.MeshBasicMaterial({ name: "crow.eye", color: 0xd4b168, toneMapped: false }),
    foot: new THREE.MeshStandardMaterial({ name: "crow.foot", color: 0x24272a, roughness: 0.9 }),
  };
}

function wingGeometry(side: -1 | 1): THREE.BufferGeometry {
  const s = side;
  const geometry = new THREE.BufferGeometry();
  geometry.setAttribute(
    "position",
    new THREE.Float32BufferAttribute([
      0, 0, 0.04,
      s * 0.32, -0.015, 0.1,
      s * 0.4, -0.03, 0.2,
      s * 0.18, -0.015, -0.14,
      0, 0, -0.16,
    ], 3),
  );
  geometry.setIndex([0, 1, 3, 1, 2, 3, 0, 3, 4]);
  geometry.computeVertexNormals();
  geometry.name = `crow.wing.${side < 0 ? "left" : "right"}`;
  return geometry;
}

function resolveCrow(root: THREE.Group, ownsMaterials: boolean): CrowRig {
  return {
    root,
    ownsMaterials,
    joints: {
      body: requireGroup(root, "crow.body"),
      head: requireGroup(root, "crow.head"),
      wingLeft: requireGroup(root, "crow.wing.left"),
      wingRight: requireGroup(root, "crow.wing.right"),
      tail: requireGroup(root, "crow.tail"),
    },
  };
}

export function buildCrow(options: CrowBuildOptions = {}): CrowRig {
  const m = options.materials ?? createCrowMaterials(options);
  const root = namedGroup("crow");
  root.userData.kind = "actor";
  root.userData.actorType = "anonymous-crow";
  root.userData.forwardAxis = "-Z";
  const body = namedGroup("crow.body", [0, 0.2, 0]);
  root.add(body);
  body.add(
    actorMesh("crow.torso", new THREE.SphereGeometry(0.13, 12, 9), m.feather, {
      rotation: [-0.16, 0, 0],
      scale: [0.82, 1.08, 1.34],
    }),
    actorMesh("crow.breast", new THREE.SphereGeometry(0.105, 10, 8), m.sheen, {
      position: [0, 0.012, -0.105],
      scale: [0.72, 1, 0.58],
    }),
  );
  const head = namedGroup("crow.head", [0, 0.145, -0.105]);
  body.add(head);
  head.add(
    actorMesh("crow.skull", new THREE.SphereGeometry(0.093, 12, 9), m.feather, { scale: [0.92, 1, 0.95] }),
    actorMesh("crow.beak", new THREE.ConeGeometry(0.055, 0.18, 6), m.beak, {
      position: [0, -0.018, -0.145],
      rotation: [-Math.PI / 2, 0, 0],
      scale: [0.72, 1, 0.68],
    }),
  );
  for (const side of [-1, 1] as const) {
    const word = side < 0 ? "left" : "right";
    head.add(
      actorMesh(`crow.eye.${word}`, new THREE.SphereGeometry(0.011, 7, 5), m.eye, {
        position: [side * 0.068, 0.02, -0.06],
      }),
    );
    const wing = namedGroup(`crow.wing.${word}`, [side * 0.075, 0.035, 0]);
    body.add(wing);
    wing.add(actorMesh(`crow.wing-mesh.${word}`, wingGeometry(side), m.feather, { receiveShadow: false }));
  }
  const tail = namedGroup("crow.tail", [0, -0.01, 0.13]);
  body.add(tail);
  for (const side of [-1, 0, 1] as const) {
    tail.add(
      actorMesh(`crow.tail-feather.${side}`, new THREE.ConeGeometry(0.045, 0.25, 4), m.feather, {
        position: [side * 0.04, -0.015, 0.12],
        rotation: [Math.PI / 2, 0, 0],
        scale: [0.72, 1, 0.35],
      }),
    );
  }
  for (const side of [-1, 1] as const) {
    body.add(
      actorMesh(`crow.foot.${side < 0 ? "left" : "right"}`, new THREE.CylinderGeometry(0.012, 0.009, 0.14, 6), m.foot, {
        position: [side * 0.048, -0.14, -0.012],
      }),
    );
  }
  return resolveCrow(root, !options.materials);
}

export function cloneCrow(source: CrowRig): CrowRig {
  return resolveCrow(source.root.clone(true), false);
}

/** Pose a flap. `amount=0` folds the wings; `amount=1` is a broad flight stroke. */
export function poseCrowFlight(rig: CrowRig, phase: number, amount = 1): void {
  const strength = THREE.MathUtils.clamp(amount, 0, 1);
  const stroke = Math.sin(phase) * 0.72 * strength;
  const spread = 0.2 + strength * 0.74;
  rig.joints.wingLeft.rotation.z = spread + stroke;
  rig.joints.wingRight.rotation.z = -spread - stroke;
  rig.joints.wingLeft.rotation.x = -0.12 * strength;
  rig.joints.wingRight.rotation.x = -0.12 * strength;
  rig.joints.body.rotation.x = 0.08 * Math.cos(phase) * strength;
  rig.joints.tail.rotation.x = -0.12 * Math.cos(phase) * strength;
}

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