import * as THREE from "three";

export interface ActorRigBase {
  /** Floor-centred root. Every actor faces -Z at yaw zero. */
  root: THREE.Group;
  /** True only when the builder created the material set. */
  readonly ownsMaterials: boolean;
}

export function actorMesh(
  name: string,
  geometry: THREE.BufferGeometry,
  material: THREE.Material,
  options: {
    position?: readonly [number, number, number];
    rotation?: readonly [number, number, number];
    scale?: readonly [number, number, number];
    receiveShadow?: boolean;
  } = {},
): THREE.Mesh {
  const mesh = new THREE.Mesh(geometry, material);
  mesh.name = name;
  if (options.position) mesh.position.set(...options.position);
  if (options.rotation) mesh.rotation.set(...options.rotation);
  if (options.scale) mesh.scale.set(...options.scale);
  mesh.castShadow = true;
  mesh.receiveShadow = options.receiveShadow ?? true;
  return mesh;
}

export function namedGroup(name: string, position?: readonly [number, number, number]): THREE.Group {
  const group = new THREE.Group();
  group.name = name;
  if (position) group.position.set(...position);
  return group;
}

export function requireGroup(root: THREE.Object3D, name: string): THREE.Group {
  const object = root.getObjectByName(name);
  if (!(object instanceof THREE.Group)) throw new Error(`actor: missing joint "${name}"`);
  return object;
}

/** Release only resources owned by a prototype. Clones share those resources. */
export function disposeActor(
  rig: ActorRigBase,
  options: { disposeMaterials?: boolean } = {},
): void {
  const geometries = new Set<THREE.BufferGeometry>();
  const materials = new Set<THREE.Material>();
  rig.root.traverse((object) => {
    if (!(object instanceof THREE.Mesh)) return;
    geometries.add(object.geometry);
    if (Array.isArray(object.material)) {
      for (const material of object.material) materials.add(material);
    } else materials.add(object.material);
  });
  for (const geometry of geometries) geometry.dispose();
  if (options.disposeMaterials ?? rig.ownsMaterials) {
    for (const material of materials) material.dispose();
  }
}

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