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 {
  loadPointCloudFromXYZ(text: string): number;
  loadMeshFromPLY(ply: string): number;
  isPointCloud(handle: number): boolean;
  getPointCount(handle: number): number;
  getFaceCount(handle: number): number;
  getVertexCount(handle: number): number;
  getNormalBuffer(handle: number): Float32Array;
  estimateNormals(handle: number, k: number): void;
  marchingCubesReconstruction(handle: number, resolution: number): number;
  deleteMesh(handle: number): void;
}

let core: WasmModule;

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

describe('XYZ Point Cloud Loading', () => {
  it('loads XYZ file as point cloud', () => {
    const xyz = readFileSync(resolve(__dirname, '../fixtures/plane.xyz'), 'utf-8');
    const h = core.loadPointCloudFromXYZ(xyz);
    expect(core.isPointCloud(h)).toBe(true);
    expect(core.getPointCount(h)).toBe(25);
    expect(core.getFaceCount(h)).toBe(0);
    core.deleteMesh(h);
  });

  it('loads XYZ with normals', () => {
    const xyz = '0 0 0 0 0 1\n1 0 0 0 0 1\n0 1 0 0 0 1\n';
    const h = core.loadPointCloudFromXYZ(xyz);
    expect(core.getPointCount(h)).toBe(3);
    const normals = new Float32Array(core.getNormalBuffer(h));
    expect(normals.length).toBe(9);
    expect(normals[2]).toBeCloseTo(1); // z normal
    core.deleteMesh(h);
  });
});

describe('PLY as Point Cloud', () => {
  it('face-less PLY is recognized as point cloud', () => {
    const ply = `ply\nformat ascii 1.0\nelement vertex 4\nproperty float x\nproperty float y\nproperty float z\nend_header\n0 0 0\n1 0 0\n0 1 0\n0 0 1\n`;
    const h = core.loadMeshFromPLY(ply);
    expect(core.isPointCloud(h)).toBe(true);
    expect(core.getPointCount(h)).toBe(4);
    core.deleteMesh(h);
  });
});

describe('Normal Estimation', () => {
  it('estimates normals for planar point cloud', () => {
    const xyz = readFileSync(resolve(__dirname, '../fixtures/plane.xyz'), 'utf-8');
    const h = core.loadPointCloudFromXYZ(xyz);
    core.estimateNormals(h, 10);
    const normals = new Float32Array(core.getNormalBuffer(h));
    expect(normals.length).toBe(25 * 3);
    // Planar points should have normals mostly in Z direction
    for (let i = 0; i < normals.length; i += 3) {
      expect(Math.abs(normals[i + 2])).toBeGreaterThan(0.5);
    }
    core.deleteMesh(h);
  });
});

describe('Marching Cubes', () => {
  it('generates mesh from point cloud with normals', () => {
    // Sphere point cloud with normals
    let xyz = '';
    for (let i = 0; i < 100; i++) {
      const theta = Math.acos(2 * (i / 100) - 1);
      const phi = 2 * Math.PI * (i * 0.618);
      const x = Math.sin(theta) * Math.cos(phi);
      const y = Math.sin(theta) * Math.sin(phi);
      const z = Math.cos(theta);
      xyz += `${x} ${y} ${z} ${x} ${y} ${z}\n`;
    }
    const h = core.loadPointCloudFromXYZ(xyz);
    const meshH = core.marchingCubesReconstruction(h, 16);
    expect(meshH).toBeGreaterThanOrEqual(0);
    expect(core.isPointCloud(meshH)).toBe(false);
    expect(core.getFaceCount(meshH)).toBeGreaterThan(0);
    core.deleteMesh(h);
    core.deleteMesh(meshH);
  });

  it('returns -1 for point cloud without normals', () => {
    const xyz = '0 0 0\n1 0 0\n0 1 0\n';
    const h = core.loadPointCloudFromXYZ(xyz);
    const meshH = core.marchingCubesReconstruction(h, 16);
    expect(meshH).toBe(-1);
    core.deleteMesh(h);
  });
});
