export const CAMOTICS_WASM_SOURCE_REVISION = 'e84665f2fa9d1151f03282ac7e01320bc65e015b'
export const CAMOTICS_WASM_ARTIFACT_URL = '/vendor/camotics/camotics-sweep.wasm'
export const CAMOTICS_WASM_ARTIFACT_SHA256 = '889588d2dc9755f84f56b8ebe1acc9bc1f0ded7e676e162a5a001011f4ebec5b'

export type CamoticsWasmPoint = [number, number, number]

type CamoticsSweepExports = WebAssembly.Exports & {
  memory: WebAssembly.Memory
  camotics_sweep_abi_version(): number
  camotics_conic_depth(length: number, topRadius: number, bottomRadius: number, ax: number, ay: number, az: number, bx: number, by: number, bz: number, px: number, py: number, pz: number): number
  camotics_spheroid_depth(radius: number, length: number, ax: number, ay: number, az: number, bx: number, by: number, bz: number, px: number, py: number, pz: number): number
  camotics_conic_bbox_count(length: number, topRadius: number, bottomRadius: number, ax: number, ay: number, az: number, bx: number, by: number, bz: number, tolerance: number): number
}

export type CamoticsSweepKernel = {
  backend: 'camotics-upstream-sweep-wasm'
  sourceRevision: string
  artifactSha256: string
  abiVersion: 1
  imports: readonly []
  fullCamoticsProgram: false
  gcodeParserIncluded: false
  conicDepth(input: { length: number; topRadius: number; bottomRadius?: number; start: CamoticsWasmPoint; end: CamoticsWasmPoint; point: CamoticsWasmPoint }): number
  spheroidDepth(input: { radius: number; length?: number; start: CamoticsWasmPoint; end: CamoticsWasmPoint; point: CamoticsWasmPoint }): number
  conicBoundingBoxCount(input: { length: number; topRadius: number; bottomRadius?: number; start: CamoticsWasmPoint; end: CamoticsWasmPoint; tolerance?: number }): number
}

const finite = (value: number, label: string) => {
  if (!Number.isFinite(value)) throw new RangeError(`${label} must be finite.`)
  return value
}

const positive = (value: number, label: string) => {
  finite(value, label)
  if (value <= 0) throw new RangeError(`${label} must be greater than zero.`)
  return value
}

const point = (value: CamoticsWasmPoint, label: string) => {
  if (value.length !== 3) throw new RangeError(`${label} must contain three coordinates.`)
  return value.map((coordinate, index) => finite(coordinate, `${label}[${index}]`)) as CamoticsWasmPoint
}

const digest = async (bytes: ArrayBuffer) => [...new Uint8Array(await crypto.subtle.digest('SHA-256', bytes))].map((value) => value.toString(16).padStart(2, '0')).join('')

export const createCamoticsSweepKernel = (exports: CamoticsSweepExports): CamoticsSweepKernel => {
  const abiVersion = exports.camotics_sweep_abi_version()
  if (abiVersion !== 1) throw new Error(`Unsupported CAMotics sweep WASM ABI: ${abiVersion}`)
  return {
    backend: 'camotics-upstream-sweep-wasm',
    sourceRevision: CAMOTICS_WASM_SOURCE_REVISION,
    artifactSha256: CAMOTICS_WASM_ARTIFACT_SHA256,
    abiVersion,
    imports: [],
    fullCamoticsProgram: false,
    gcodeParserIncluded: false,
    conicDepth(input) {
      const length = positive(input.length, 'Conic length')
      const topRadius = positive(input.topRadius, 'Conic top radius')
      const bottomRadius = input.bottomRadius === undefined ? topRadius : positive(input.bottomRadius, 'Conic bottom radius')
      const start = point(input.start, 'Conic start')
      const end = point(input.end, 'Conic end')
      const sample = point(input.point, 'Conic sample point')
      return exports.camotics_conic_depth(length, topRadius, bottomRadius, ...start, ...end, ...sample)
    },
    spheroidDepth(input) {
      const radius = positive(input.radius, 'Spheroid radius')
      const length = input.length === undefined ? radius * 2 : positive(input.length, 'Spheroid length')
      const start = point(input.start, 'Spheroid start')
      const end = point(input.end, 'Spheroid end')
      const sample = point(input.point, 'Spheroid sample point')
      return exports.camotics_spheroid_depth(radius, length, ...start, ...end, ...sample)
    },
    conicBoundingBoxCount(input) {
      const length = positive(input.length, 'Conic length')
      const topRadius = positive(input.topRadius, 'Conic top radius')
      const bottomRadius = input.bottomRadius === undefined ? topRadius : positive(input.bottomRadius, 'Conic bottom radius')
      const start = point(input.start, 'Conic start')
      const end = point(input.end, 'Conic end')
      const tolerance = input.tolerance === undefined ? 0.01 : finite(input.tolerance, 'Conic tolerance')
      if (tolerance < 0) throw new RangeError('Conic tolerance must not be negative.')
      return exports.camotics_conic_bbox_count(length, topRadius, bottomRadius, ...start, ...end, tolerance)
    },
  }
}

let kernelPromise: Promise<CamoticsSweepKernel> | null = null

export const loadCamoticsSweepKernel = async (): Promise<CamoticsSweepKernel> => {
  if (typeof WebAssembly === 'undefined' || typeof crypto?.subtle === 'undefined') throw new Error('CAMotics sweep kernel requires WebAssembly and Web Crypto.')
  if (!kernelPromise) kernelPromise = (async () => {
    const response = await fetch(CAMOTICS_WASM_ARTIFACT_URL, { cache: 'no-store', credentials: 'same-origin' })
    if (!response.ok) throw new Error(`CAMotics sweep WASM fetch failed: HTTP ${response.status}`)
    const bytes = await response.arrayBuffer()
    const actualDigest = await digest(bytes)
    if (actualDigest !== CAMOTICS_WASM_ARTIFACT_SHA256) throw new Error(`CAMotics sweep WASM digest mismatch: ${actualDigest}`)
    const module = await WebAssembly.compile(bytes)
    if (WebAssembly.Module.imports(module).length !== 0) throw new Error('CAMotics sweep WASM must remain standalone with zero imports.')
    const instance = await WebAssembly.instantiate(module, {})
    return createCamoticsSweepKernel(instance.exports as CamoticsSweepExports)
  })().catch((error) => {
    kernelPromise = null
    throw error
  })
  return kernelPromise
}
