import { BitbybitGeometryRuntime } from './facade/geometryRuntime'
import {
  generateCamoticsSourceStageGcode,
  OPEN_CAM_LIB_ARTIFACT_SHA256,
  nativeCamCapabilities,
  runOpenCamLibDropCutter,
  simulateOcctSolidRemoval,
} from './facade/camNativeSimulation'

type NativeSimulationReport = {
  schemaVersion: 1
  status: 'pass' | 'failed'
  browserId: 'chrome'
  crossOriginIsolated: boolean
  capabilities?: ReturnType<typeof nativeCamCapabilities>
  openCamLib?: { backend: string; sourceRevision: string; artifactSha256: string; triangles: number; points: number; minimumZ: number; maximumZ: number; deterministic: boolean }
  camotics?: { backend: string; sourceRevision: string; nativeExecutable: false; lineCount: number; gcodeBytes: number; gcodeHash: string; parserAuthority: string; camoticsParsesCanonicalGcode: false }
  pipeline?: { stages: string[]; canonicalGcodeAuthority: string; camoticsSemanticParser: false; linuxcncWasmExecution: 'handoff-required' }
  solidRemoval?: { backend: string; model: string; sweepMode: string; nativeSolid: boolean; freeCadNativeEquivalent: boolean; sampleCount: number; stockVolume: number; removedVolume: number; remainingVolume: number; structuralValid: boolean; solids: number; meshVertices: number; meshTriangles: number; brepBytes: number; brepHash: string }
  curvedSolidRemoval?: { backend: string; model: string; sweepMode: string; sampleCount: number; stockVolume: number; removedVolume: number; remainingVolume: number; structuralValid: boolean; solids: number; meshVertices: number; meshTriangles: number }
  afterRelease?: { shapeCount: number; kernelReferenceCount: number }
  error?: string
}

declare global { interface Window { __bitbybitCamNativeSimulationReport?: NativeSimulationReport } }

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

const run = async (): Promise<NativeSimulationReport> => {
  const runtime = new BitbybitGeometryRuntime()
  const report: NativeSimulationReport = { schemaVersion: 1, status: 'failed', browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true }
  let remainingShape: Awaited<ReturnType<typeof simulateOcctSolidRemoval>>['remainingShape'] | null = null
  let curvedRemainingShape: Awaited<ReturnType<typeof simulateOcctSolidRemoval>>['remainingShape'] | null = null
  try {
    if (!report.crossOriginIsolated) throw new Error('Native CAM evidence requires an isolated browser runtime.')
    report.capabilities = nativeCamCapabilities()
    const surface = [
      [[0, 0, 2], [10, 0, 2], [10, 10, 2]],
      [[0, 0, 2], [10, 10, 2], [0, 10, 2]],
    ] as const
    const ocl = await runOpenCamLibDropCutter({
      triangles: surface.map((triangle) => triangle.map((point) => [...point]) as [[number, number, number], [number, number, number], [number, number, number]]),
      path: [[2, 5, 0], [8, 5, 0]],
      cutter: { shape: 'endmill', diameter: 2, length: 20 },
      sampling: 0.5,
      minimumZ: 0,
    })
    const oclAgain = await runOpenCamLibDropCutter({
      triangles: surface.map((triangle) => triangle.map((point) => [...point]) as [[number, number, number], [number, number, number], [number, number, number]]),
      path: [[2, 5, 0], [8, 5, 0]],
      cutter: { shape: 'endmill', diameter: 2, length: 20 },
      sampling: 0.5,
      minimumZ: 0,
    })
    const heights = ocl.points.map((point) => point[2])
    report.openCamLib = {
      backend: ocl.backend,
      sourceRevision: ocl.sourceRevision,
      artifactSha256: ocl.artifactSha256,
      triangles: ocl.triangleCount,
      points: ocl.points.length,
      minimumZ: Math.min(...heights),
      maximumZ: Math.max(...heights),
      deterministic: JSON.stringify(ocl.points) === JSON.stringify(oclAgain.points),
    }
    const sourceStage = generateCamoticsSourceStageGcode([
      { type: 'rapid', point: ocl.points[0] },
      ...ocl.points.slice(1).map((point) => ({ type: 'cut' as const, point, feed: 120 })),
    ], { toolNumber: 1, feed: 120 })
    report.camotics = {
      backend: sourceStage.backend,
      sourceRevision: sourceStage.sourceRevision,
      nativeExecutable: sourceStage.nativeExecutable,
      lineCount: sourceStage.lineCount,
      gcodeBytes: new TextEncoder().encode(sourceStage.gcode).byteLength,
      gcodeHash: await sha256(sourceStage.gcode),
      parserAuthority: sourceStage.parserAuthority,
      camoticsParsesCanonicalGcode: sourceStage.camoticsParsesCanonicalGcode,
    }
    report.pipeline = {
      stages: ['cad', 'opencamlib-wasm', 'camotics-source-stage', 'gcode', 'linuxcnc-wasm-parse-execute'],
      canonicalGcodeAuthority: 'linuxcnc-wasm',
      camoticsSemanticParser: false,
      linuxcncWasmExecution: 'handoff-required',
    }

    const removal = await simulateOcctSolidRemoval(runtime, {
      id: 'browser-evidence',
      stock: { min: [0, 0, 0], max: [10, 6, 5] },
      path: [[2, 3, 4], [8, 3, 4]],
      cutterDiameter: 2,
      maxChord: 1,
      meshPrecision: 0.1,
    })
    remainingShape = removal.remainingShape
    const brep = await runtime.exportBrep(removal.remainingShape, 'remaining-stock.brep')
    report.solidRemoval = {
      backend: removal.backend,
      model: removal.model,
      sweepMode: removal.sweepMode,
      nativeSolid: removal.nativeSolid,
      freeCadNativeEquivalent: removal.freeCadNativeEquivalent,
      sampleCount: removal.sampleCount,
      stockVolume: removal.stockVolume,
      removedVolume: removal.removedVolume,
      remainingVolume: removal.remainingVolume,
      structuralValid: removal.structuralValid,
      solids: removal.solids,
      meshVertices: removal.meshVertices,
      meshTriangles: removal.meshTriangles,
      brepBytes: new TextEncoder().encode(brep.text).byteLength,
      brepHash: await sha256(brep.text),
    }
    const curvedRemoval = await simulateOcctSolidRemoval(runtime, {
      id: 'browser-curved-sweep-evidence',
      stock: { min: [0, 0, 0], max: [10, 6, 5] },
      path: [[2, 2, 4], [4, 3, 3.5], [6, 2, 3], [8, 3, 2.5]],
      cutterDiameter: 1.5,
      maxChord: 0.75,
      meshPrecision: 0.1,
      sweepMode: 'continuous-segment',
    })
    curvedRemainingShape = curvedRemoval.remainingShape
    report.curvedSolidRemoval = {
      backend: curvedRemoval.backend,
      model: curvedRemoval.model,
      sweepMode: curvedRemoval.sweepMode,
      sampleCount: curvedRemoval.sampleCount,
      stockVolume: curvedRemoval.stockVolume,
      removedVolume: curvedRemoval.removedVolume,
      remainingVolume: curvedRemoval.remainingVolume,
      structuralValid: curvedRemoval.structuralValid,
      solids: curvedRemoval.solids,
      meshVertices: curvedRemoval.meshVertices,
      meshTriangles: curvedRemoval.meshTriangles,
    }
    const oclPass = report.openCamLib.artifactSha256 === OPEN_CAM_LIB_ARTIFACT_SHA256 && report.openCamLib.triangles === 2 && report.openCamLib.points >= 2 && report.openCamLib.minimumZ >= 2 - 1e-9 && report.openCamLib.maximumZ <= 2 + 1e-9 && report.openCamLib.deterministic
    const removalPass = report.solidRemoval.nativeSolid && report.solidRemoval.sweepMode === 'sampled-flat-end' && !report.solidRemoval.freeCadNativeEquivalent && report.solidRemoval.structuralValid && report.solidRemoval.solids >= 1 && report.solidRemoval.stockVolume > report.solidRemoval.remainingVolume && report.solidRemoval.removedVolume > 0 && Math.abs(report.solidRemoval.stockVolume - report.solidRemoval.removedVolume - report.solidRemoval.remainingVolume) < 1e-7 && report.solidRemoval.meshVertices > 0 && report.solidRemoval.meshTriangles > 0 && report.solidRemoval.brepBytes > 0 && /^[0-9a-f]{64}$/.test(report.solidRemoval.brepHash)
    const curvedRemovalPass = report.curvedSolidRemoval?.backend === 'bitbybit-occt-wasm' && report.curvedSolidRemoval.model === 'continuous-segment-sweep-brep' && report.curvedSolidRemoval.sweepMode === 'continuous-segment' && report.curvedSolidRemoval.sampleCount > 4 && report.curvedSolidRemoval.structuralValid && report.curvedSolidRemoval.solids >= 1 && report.curvedSolidRemoval.stockVolume > report.curvedSolidRemoval.remainingVolume && report.curvedSolidRemoval.removedVolume > 0 && report.curvedSolidRemoval.meshVertices > 0 && report.curvedSolidRemoval.meshTriangles > 0
    const camoticsPass = report.capabilities.camotics.status === 'native-host-available' && report.capabilities.camotics.backend === 'camotics-native-host-qt5-tpl' && report.capabilities.camotics.workspaceNativeExecutable && report.capabilities.camotics.nativeGuiPath === 'CAMotics/camotics' && report.capabilities.camotics.nativeTplPath === 'CAMotics/tplang' && !report.capabilities.camotics.browserExecutable && report.capabilities.camotics.browserWasmKernelExecutable && report.capabilities.camotics.wasmKernel.backend === 'camotics-upstream-sweep-wasm' && !report.capabilities.camotics.wasmKernel.fullCamoticsProgram && !report.capabilities.camotics.wasmKernel.gcodeParserIncluded && report.camotics?.backend === 'camotics-source-stage-contract' && report.camotics.nativeExecutable === false && report.camotics.lineCount >= 6 && report.camotics.gcodeBytes > 0 && /^[0-9a-f]{64}$/.test(report.camotics.gcodeHash) && report.camotics.parserAuthority === 'linuxcnc-wasm' && report.camotics.camoticsParsesCanonicalGcode === false && report.pipeline?.canonicalGcodeAuthority === 'linuxcnc-wasm' && report.pipeline.camoticsSemanticParser === false && report.pipeline.linuxcncWasmExecution === 'handoff-required'
    report.status = oclPass && removalPass && curvedRemovalPass && camoticsPass ? 'pass' : 'failed'
  } catch (error) {
    report.error = error instanceof Error ? error.stack || error.message : String(error)
  } finally {
    if (remainingShape) await runtime.release(remainingShape)
    if (curvedRemainingShape) await runtime.release(curvedRemainingShape)
    runtime.dispose()
    await new Promise((resolve) => setTimeout(resolve, 0))
    const capabilities = runtime.capabilities()
    report.afterRelease = { shapeCount: capabilities.shapeCount, kernelReferenceCount: capabilities.kernelReferenceCount }
    if (report.afterRelease.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) {
      report.status = 'failed'
      report.error = `${report.error ? `${report.error} ` : ''}OCCT ownership gate failed.`
    }
  }
  return report
}

void run().then((report) => {
  window.__bitbybitCamNativeSimulationReport = report
  const output = document.querySelector('#result')
  if (output) output.textContent = JSON.stringify(report, null, 2)
})
