/**
 * Lane A — the Blobbo body.
 *
 * A friendly white soft-looking blob. The physics is a rigid, rotation-locked
 * dynamic ball (determinism lives in the capsule); the softness is faked by the
 * feel layer wobbling the mesh (see feel.ts). The body mesh is a *clean UV
 * sphere with untouched UVs* — Lane B paints splats into that UV space, so we
 * never touch its geometry attributes.
 */
import * as THREE from 'three'
import type RAPIER from '@dimforge/rapier3d-compat'
import type { Blob, World } from '../contracts'
import { BASE_WHITE, defaultModifiers } from '../contracts'
import { assets } from '../assets/registry'
import { resolveBlobBody } from '../assets/blobBody'

export interface CreateBlobOptions {
  position?: THREE.Vector3
  /** Base collider radius before the `size` modifier is applied. */
  radius?: number
  /** UV-sphere longitude segments (latitude is derived). Keep high for smooth paint. */
  segments?: number
}

/**
 * Runtime handle. Extends the frozen `Blob` with the concrete bits the Lane A
 * controller/feel need (collider, base radius, cosmetic face). Anything typed
 * as `Blob` elsewhere still sees exactly the frozen shape.
 */
export interface BlobHandle extends Blob {
  collider: RAPIER.Collider
  /** Base radius (pre-`size`); collider radius = radius * modifiers.size. */
  radius: number
  /** Cosmetic eyes group — NOT paintable, kept separate from `mesh`. */
  face: THREE.Group
}

function buildFace(radius: number): THREE.Group {
  const face = new THREE.Group()
  const eyeWhiteMat = new THREE.MeshStandardMaterial({ color: '#ffffff', roughness: 0.3 })
  const pupilMat = new THREE.MeshStandardMaterial({ color: '#1a1a22', roughness: 0.5 })

  const eyeWhiteGeo = new THREE.SphereGeometry(radius * 0.3, 20, 16)
  const pupilGeo = new THREE.SphereGeometry(radius * 0.15, 16, 12)

  for (const side of [-1, 1]) {
    const eye = new THREE.Group()
    eye.position.set(side * radius * 0.34, radius * 0.34, radius * 0.86)
    const white = new THREE.Mesh(eyeWhiteGeo, eyeWhiteMat)
    white.castShadow = true
    const pupil = new THREE.Mesh(pupilGeo, pupilMat)
    pupil.position.set(0, 0, radius * 0.2)
    eye.add(white, pupil)
    face.add(eye)
  }
  return face
}

export function createBlob(world: World, opts: CreateBlobOptions = {}): BlobHandle {
  const { scene, physics, rapier } = world
  const radius = opts.radius ?? 0.5
  const pos = opts.position ?? new THREE.Vector3(0, 3, 0)
  const seg = opts.segments ?? 48

  // Root group carries world position, facing yaw, and the `size` scale.
  const group = new THREE.Group()
  group.position.copy(pos)
  scene.add(group)

  // Paintable body: clean UV sphere. Default THREE sphere UVs are exactly what
  // Lane B wants (equirectangular, seam at the back). Do not decorate this mesh.
  // Slot `blob.body` may replace it, but only with a mesh that passes the same
  // paint requirements and is refitted to `radius` (see assets/blobBody.ts).
  const buildBodyMesh = (): THREE.Mesh => {
    const geometry = new THREE.SphereGeometry(radius, seg, Math.round(seg * 0.75))
    const material = new THREE.MeshStandardMaterial({
      color: BASE_WHITE,
      roughness: 0.5,
      metalness: 0.0,
      emissive: new THREE.Color('#88e0ff'),
      emissiveIntensity: 0.0, // driven by modifiers.glow in the feel layer
    })
    const m = new THREE.Mesh(geometry, material)
    m.castShadow = true
    return m
  }
  const mesh = resolveBlobBody(world, radius, buildBodyMesh)
  group.add(mesh)

  // Cosmetic face, parented to the group (yaws to face travel, scales with size).
  const face = new THREE.Group()
  face.add(assets().slotObject('blob.face', () => buildFace(radius)))
  group.add(face)

  // Physics proxy: dynamic ball, rotations locked so it stays an upright
  // character instead of a rolling marble. Comedy roll = visual only.
  const body = physics.createRigidBody(
    rapier.RigidBodyDesc.dynamic()
      .setTranslation(pos.x, pos.y, pos.z)
      .setLinearDamping(0.15)
      .setCanSleep(false),
  )
  body.setEnabledRotations(false, false, false, true)

  const collider = physics.createCollider(
    rapier.ColliderDesc.ball(radius)
      .setRestitution(0.0) // no physical bounce — the bounce is the squash
      .setFriction(0.6)
      .setMass(1.0),
    body,
  )

  return {
    group,
    mesh,
    body,
    collider,
    radius,
    face,
    modifiers: defaultModifiers(),
  }
}
