import type { Mat4 } from "../math/Mat4"
import type { Camera } from "../scene/Camera"
import type { RenderConfig } from "./RenderConfig"
import type { RenderInstance, RenderScene } from "./RenderScene"

export type RenderWorkerInit = {
  colorSAB: SharedArrayBuffer
  depthSAB: SharedArrayBuffer
  width: number
  height: number
  scene: RenderScene
  band: [number, number]
  config: RenderConfig
  skyStep: number
  ctrlSAB: SharedArrayBuffer
  cameraSAB: SharedArrayBuffer
  viewProjectionSAB: SharedArrayBuffer
  visibleChunkSAB: SharedArrayBuffer
  instanceIdSAB: SharedArrayBuffer
  instanceTransformSAB: SharedArrayBuffer
  timesSAB: SharedArrayBuffer
  workerIndex: number
}

export type RenderFrameCamera = {
  camera: Camera
  time: number
}

/** Shared frame-buffer layout used by browser driver and render workers. */
export namespace RenderProtocol {
  export const FRAME = 0
  export const DONE = 1
  export const VISIBLE_CHUNKS = 2
  export const VISIBLE_INSTANCES = 3
  export const CONTROL_LENGTH = 4
  export const CAMERA_X = 0
  export const CAMERA_Y = 1
  export const CAMERA_Z = 2
  export const CAMERA_YAW = 3
  export const CAMERA_PITCH = 4
  export const CAMERA_FOV = 5
  export const CAMERA_TIME = 6
  export const CAMERA_LENGTH = 7
  export const VIEW_PROJECTION_LENGTH = 16
  export const TRANSFORM_FLOATS = 5

  export function writeCamera(
    output: Float64Array<ArrayBufferLike>,
    camera: Camera,
    time: number,
  ): void {
    output[CAMERA_X] = camera.position.x
    output[CAMERA_Y] = camera.position.y
    output[CAMERA_Z] = camera.position.z
    output[CAMERA_YAW] = camera.yaw
    output[CAMERA_PITCH] = camera.pitch
    output[CAMERA_FOV] = camera.fov
    output[CAMERA_TIME] = time
  }

  export function readCamera(input: Float64Array<ArrayBufferLike>): RenderFrameCamera {
    return {
      camera: {
        position: {
          x: input[CAMERA_X],
          y: input[CAMERA_Y],
          z: input[CAMERA_Z],
        },
        yaw: input[CAMERA_YAW],
        pitch: input[CAMERA_PITCH],
        fov: input[CAMERA_FOV],
      },
      time: input[CAMERA_TIME],
    }
  }

  export function writeViewProjection(
    output: Float32Array<ArrayBufferLike>,
    viewProjection: Mat4,
  ): void {
    output.set(viewProjection)
  }

  export function writeInstances(
    ids: Int32Array<ArrayBufferLike>,
    transforms: Float32Array<ArrayBufferLike>,
    instances: RenderInstance[],
  ): number {
    const count = Math.min(instances.length, ids.length)
    for (let i = 0; i < count; i++) {
      const instance = instances[i]
      const offset = i * TRANSFORM_FLOATS
      ids[i] = instance.prototype
      transforms[offset] = instance.x
      transforms[offset + 1] = instance.y
      transforms[offset + 2] = instance.z
      transforms[offset + 3] = instance.heading
      transforms[offset + 4] = instance.scale
    }
    return count
  }

  export function readInstances(
    ids: Int32Array<ArrayBufferLike>,
    transforms: Float32Array<ArrayBufferLike>,
    count: number,
    output: RenderInstance[],
  ): void {
    output.length = 0
    for (let i = 0; i < count; i++) {
      const offset = i * TRANSFORM_FLOATS
      output.push({
        prototype: ids[i],
        x: transforms[offset],
        y: transforms[offset + 1],
        z: transforms[offset + 2],
        heading: transforms[offset + 3],
        scale: transforms[offset + 4],
      })
    }
  }
}
