import test from 'node:test'
import assert from 'node:assert/strict'
import {
  CAMOTICS_SOURCE_REVISION,
  OPEN_CAM_LIB_ARTIFACT_SHA256,
  generateCamoticsSourceStageGcode,
  nativeCamCapabilities,
  runOpenCamLibDropCutter,
  sampleFlatEndPath,
} from '../src/facade/camNativeSimulation'

test('native CAM capability report distinguishes workspace CAMotics CLI from browser execution', () => {
  const capabilities = nativeCamCapabilities()
  assert.equal(capabilities.openCamLib.backend, 'upstream-opencamlib-wasm')
  assert.equal(capabilities.openCamLib.artifactSha256, OPEN_CAM_LIB_ARTIFACT_SHA256)
  assert.deepEqual(capabilities.openCamLib.algorithms, ['path-drop-cutter', 'adaptive-path-drop-cutter', 'waterline', 'adaptive-waterline'])
  assert.equal(capabilities.solidRemoval.backend, 'bitbybit-occt-wasm')
  assert.equal(capabilities.solidRemoval.nativeSolid, true)
  assert.equal(capabilities.solidRemoval.freeCadNativeEquivalent, false)
  assert.deepEqual(capabilities.solidRemoval.sweepModes, ['sampled-flat-end', 'continuous-segment'])
  assert.equal(capabilities.camotics.status, 'native-host-available')
  assert.equal(capabilities.camotics.backend, 'camotics-native-host-qt5-tpl')
  assert.equal(capabilities.camotics.workspaceNativeExecutable, true)
  assert.equal(capabilities.camotics.nativeGuiPath, 'CAMotics/camotics')
  assert.equal(capabilities.camotics.nativeTplPath, 'CAMotics/tplang')
  assert.equal(capabilities.camotics.nativeArtifactEvidence, 'config/camotics-native-artifact.json')
  assert.equal(capabilities.camotics.browserExecutable, false)
  assert.equal(capabilities.camotics.browserWasmKernelExecutable, true)
  assert.equal(capabilities.camotics.nativeCliPath, 'CAMotics/camsim')
  assert.equal(capabilities.camotics.wasmKernel.backend, 'camotics-upstream-sweep-wasm')
  assert.equal(capabilities.camotics.wasmKernel.fullCamoticsProgram, false)
  assert.equal(capabilities.camotics.wasmKernel.gcodeParserIncluded, false)
  assert.equal(capabilities.camotics.sourceRevision, CAMOTICS_SOURCE_REVISION)
  assert.equal(capabilities.camotics.generationAuthority, 'camotics-source-stage')
  assert.equal(capabilities.camotics.parserAuthority, 'linuxcnc-wasm')
  assert.equal(capabilities.camotics.camoticsParsesCanonicalGcode, false)
  assert.match(capabilities.camotics.reason, /workspace-verified/)
})

test('CAMotics source stage only generates canonical G-code for LinuxCNC WASM', () => {
  const result = generateCamoticsSourceStageGcode([
    { type: 'rapid', point: [0, 0, 5] },
    { type: 'cut', point: [1.25, 2, 3.5] },
    { type: 'cut', point: [2, 2, 3.5] },
  ], { toolNumber: 2, feed: 120 })
  assert.equal(result.backend, 'camotics-source-stage-contract')
  assert.equal(result.nativeExecutable, false)
  assert.equal(result.parserAuthority, 'linuxcnc-wasm')
  assert.equal(result.camoticsParsesCanonicalGcode, false)
  assert.equal(result.lineCount, 7)
  assert.equal(result.gcode, 'G21G90\nM6 T2\nG0 X0. Y0. Z5.\nF120.\nG1 X1.25 Y2. Z3.5\nG1 X2.\nM2\n')
  assert.throws(() => generateCamoticsSourceStageGcode([], { feed: 120 }), /must not be empty/)
  assert.throws(() => generateCamoticsSourceStageGcode([{ type: 'cut', point: [0, 0, 0] }], { feed: 0 }), /greater than zero/)
})

test('flat-end solid-removal sampling has a deterministic chord bound', () => {
  const samples = sampleFlatEndPath([[0, 0, 1], [3, 4, 1]], 1)
  assert.equal(samples.length, 6)
  assert.deepEqual(samples[0], [0, 0, 1])
  assert.deepEqual(samples.at(-1), [3, 4, 1])
  for (let index = 1; index < samples.length; index += 1) {
    assert.ok(Math.hypot(...samples[index].map((value, axis) => value - samples[index - 1][axis])) <= 1 + 1e-12)
  }
  assert.deepEqual(sampleFlatEndPath([[1, 2, 3]], 0.5), [[1, 2, 3]])
  assert.throws(() => sampleFlatEndPath([], 1), /must not be empty/)
  assert.throws(() => sampleFlatEndPath([[0, 0, 0]], 0), /greater than zero/)
})

test('OpenCAMLib adapter validates unsafe input before requesting the browser WASM runtime', async () => {
  await assert.rejects(() => runOpenCamLibDropCutter({ triangles: [], path: [[0, 0, 0], [1, 0, 0]], cutter: { shape: 'endmill', diameter: 1, length: 10 }, sampling: 0.1 }), /at least one surface triangle/)
  await assert.rejects(() => runOpenCamLibDropCutter({ triangles: [[[0, 0, 0], [1, 0, 0], [0, 1, 0]]], path: [[0, 0, 0]], cutter: { shape: 'endmill', diameter: 1, length: 10 }, sampling: 0.1 }), /at least two points/)
  await assert.rejects(() => runOpenCamLibDropCutter({ triangles: [[[0, 0, 0], [1, 0, 0], [0, 1, 0]]], path: [[0, 0, 0], [1, 0, 0]], cutter: { shape: 'endmill', diameter: 0, length: 10 }, sampling: 0.1 }), /greater than zero/)
})
