/**
 * Lane A — third-person soft-follow camera.
 *
 * Trails the blob with position lag, eases its yaw to sit behind the direction
 * of travel, and kicks FOV a touch at speed for a sense of hustle. Runs as a
 * per-frame (visual-only) hook. The controller reads `world.camera` for its
 * move basis, so this cam swinging around keeps WASD camera-relative.
 */
import * as THREE from 'three'
import type { World } from '../contracts'
import type { BlobHandle } from './createBlob'

export interface FollowCameraOptions {
  distance?: number
  height?: number
  /** How fast the camera position catches up (higher = tighter). */
  followLag?: number
  /** How fast the camera swings behind travel (higher = snappier). */
  yawLag?: number
}

export interface FollowCamera {
  update(dt: number): void
  yaw(): number
}

function expo(rate: number, dt: number): number {
  return 1 - Math.exp(-rate * dt)
}

export function createFollowCamera(
  world: World,
  blob: BlobHandle,
  opts: FollowCameraOptions = {},
): FollowCamera {
  const cam = world.camera
  const distance = opts.distance ?? 9
  const height = opts.height ?? 4.5
  const followLag = opts.followLag ?? 6
  const yawLag = opts.yawLag ?? 2.5

  const baseFov = cam.fov
  let camYaw = Math.PI // start looking toward -Z (down the course)
  const desired = new THREE.Vector3()
  const look = new THREE.Vector3()
  let lookInit = false

  return {
    update(dt: number): void {
      const p = blob.body.translation()
      const v = blob.body.linvel()
      const size = blob.modifiers.size
      const speed = Math.hypot(v.x, v.z)

      // ease the rig behind the direction of travel (only while actually moving)
      if (speed > 1.5) {
        const targetYaw = Math.atan2(v.x, v.z)
        let delta = targetYaw - camYaw
        delta = Math.atan2(Math.sin(delta), Math.cos(delta))
        camYaw += delta * expo(yawLag, dt)
      }

      const dist = distance * size
      const h = height * size
      desired.set(
        p.x - Math.sin(camYaw) * dist,
        p.y + h,
        p.z - Math.cos(camYaw) * dist,
      )
      cam.position.lerp(desired, expo(followLag, dt))

      // look at a point a little above the blob, itself eased for smoothness
      const lookTarget = new THREE.Vector3(p.x, p.y + 1.0 * size, p.z)
      if (!lookInit) {
        look.copy(lookTarget)
        lookInit = true
      } else {
        look.lerp(lookTarget, expo(8, dt))
      }
      cam.lookAt(look)

      // FOV kick at speed
      const targetFov = baseFov + Math.min(speed * 0.9, 12)
      cam.fov += (targetFov - cam.fov) * expo(4, dt)
      cam.updateProjectionMatrix()
    },
    yaw: () => camYaw,
  }
}
