import { describe, it, expect, beforeAll } from 'vitest';
import { readFileSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';

const __dirname = dirname(fileURLToPath(import.meta.url));
const require = createRequire(import.meta.url);

interface WasmModule {
  loadMeshFromOBJ(obj: string): number;
  generateTestSphere(subdivisions: number): number;
  getVertexCount(h: number): number;
  getVertexBuffer(h: number): Float32Array;
  getTransformMatrix(h: number): Float32Array;
  setTransformMatrix(h: number, ptr: number): void;
  freezeTransform(h: number): void;
  resetTransform(h: number): void;
  computeRigidAlignment(srcPtr: number, tgtPtr: number, n: number): Float32Array;
  computeICP(srcH: number, tgtH: number, maxIter: number, thresh: number, ratio: number): string;
  filterTranslate(h: number, tx: number, ty: number, tz: number): void;
  deleteMesh(h: number): void;
  _malloc(size: number): number;
  _free(ptr: number): void;
  HEAPU8: Uint8Array;
}

let core: WasmModule;
const cubeOBJ = readFileSync(resolve(__dirname, '../fixtures/cube.obj'), 'utf-8');

function allocFloats(data: Float32Array): number {
  const ptr = core._malloc(data.byteLength);
  core.HEAPU8.set(new Uint8Array(data.buffer, data.byteOffset, data.byteLength), ptr);
  return ptr;
}

beforeAll(async () => {
  const MeshLabCore = require(resolve(__dirname, '../../public/meshlab-core.cjs'));
  core = await MeshLabCore();
}, 10000);

describe('Rigid Alignment (Kabsch)', () => {
  it('returns identity for identical point sets', () => {
    const pts = new Float32Array([0,0,0, 1,0,0, 0,1,0, 0,0,1]);
    const srcPtr = allocFloats(pts);
    const tgtPtr = allocFloats(pts);
    const matrix = new Float32Array(core.computeRigidAlignment(srcPtr, tgtPtr, 4));
    core._free(srcPtr); core._free(tgtPtr);
    expect(matrix[0]).toBeCloseTo(1, 1);  // m00
    expect(matrix[5]).toBeCloseTo(1, 1);  // m11
    expect(matrix[10]).toBeCloseTo(1, 1); // m22
    expect(matrix[12]).toBeCloseTo(0, 1); // tx
    expect(matrix[13]).toBeCloseTo(0, 1); // ty
    expect(matrix[14]).toBeCloseTo(0, 1); // tz
  });

  it('recovers known translation', () => {
    const src = new Float32Array([0,0,0, 1,0,0, 0,1,0, 0,0,1]);
    const tgt = new Float32Array([5,3,1, 6,3,1, 5,4,1, 5,3,2]);
    const sp = allocFloats(src); const tp = allocFloats(tgt);
    const matrix = new Float32Array(core.computeRigidAlignment(sp, tp, 4));
    core._free(sp); core._free(tp);
    expect(matrix[12]).toBeCloseTo(5, 0);
    expect(matrix[13]).toBeCloseTo(3, 0);
    expect(matrix[14]).toBeCloseTo(1, 0);
  });
});

describe('Transform Matrix', () => {
  it('set and get transform', () => {
    const h = core.loadMeshFromOBJ(cubeOBJ);
    const mat = new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0, 5,3,1,1]);
    const ptr = allocFloats(mat);
    core.setTransformMatrix(h, ptr);
    core._free(ptr);
    const result = new Float32Array(core.getTransformMatrix(h));
    expect(result[12]).toBeCloseTo(5);
    expect(result[13]).toBeCloseTo(3);
    core.deleteMesh(h);
  });

  it('freeze transform applies to vertices', () => {
    const h = core.loadMeshFromOBJ(cubeOBJ);
    const vertsBefore = new Float32Array(core.getVertexBuffer(h));
    const mat = new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0, 10,0,0,1]);
    const ptr = allocFloats(mat);
    core.setTransformMatrix(h, ptr);
    core._free(ptr);
    core.freezeTransform(h);
    const vertsAfter = new Float32Array(core.getVertexBuffer(h));
    expect(vertsAfter[0]).toBeCloseTo(vertsBefore[0] + 10, 1);
    const matAfter = new Float32Array(core.getTransformMatrix(h));
    expect(matAfter[12]).toBeCloseTo(0);
    core.deleteMesh(h);
  });

  it('reset transform returns to identity', () => {
    const h = core.loadMeshFromOBJ(cubeOBJ);
    const mat = new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0, 5,3,1,1]);
    const ptr = allocFloats(mat);
    core.setTransformMatrix(h, ptr);
    core._free(ptr);
    core.resetTransform(h);
    const result = new Float32Array(core.getTransformMatrix(h));
    expect(result[12]).toBeCloseTo(0);
    expect(result[15]).toBeCloseTo(1);
    core.deleteMesh(h);
  });
});

describe('ICP', () => {
  it('identical meshes produce near-zero error', () => {
    const h1 = core.generateTestSphere(2);
    const h2 = core.generateTestSphere(2);
    const result = JSON.parse(core.computeICP(h1, h2, 10, 1e-6, 0.5));
    expect(result.finalError).toBeLessThan(0.1);
    core.deleteMesh(h1); core.deleteMesh(h2);
  });

  it('recovers translation via ICP', () => {
    const h1 = core.generateTestSphere(2);
    const h2 = core.generateTestSphere(2);
    core.filterTranslate(h1, 0.5, 0, 0);
    const result = JSON.parse(core.computeICP(h1, h2, 30, 1e-6, 0.5));
    expect(result.finalError).toBeLessThan(0.5);
    // Translation should be approximately -0.5 in x
    expect(result.matrix[12]).toBeCloseTo(-0.5, 0);
    core.deleteMesh(h1); core.deleteMesh(h2);
  });
});
