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);

let core: any;
const cubeOBJ = readFileSync(resolve(__dirname, '../fixtures/cube.obj'), 'utf-8');
function loadCube(): number { return core.loadMeshFromOBJ(cubeOBJ); }

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

// ========== QEM SIMPLIFICATION ==========
describe('QEM Simplification (improved)', () => {
  it('reduces to target face count', () => {
    const h = core.generateTestSphere(4); // ~1280 faces
    const fBefore = core.getFaceCount(h);
    expect(fBefore).toBeGreaterThan(500);
    core.filterSimplify(h, 200);
    const fAfter = core.getFaceCount(h);
    expect(fAfter).toBeLessThanOrEqual(220); // Within tolerance
    expect(fAfter).toBeGreaterThan(100);
    core.deleteMesh(h);
  });

  it('preserves sphere shape better than midpoint', () => {
    const h = core.generateTestSphere(4);
    core.filterSimplify(h, 100);
    // Check vertices are still roughly on a sphere
    const verts = new Float32Array(core.getVertexBuffer(h));
    let maxDev = 0;
    for (let i = 0; i < verts.length; i += 3) {
      const r = Math.sqrt(verts[i]**2 + verts[i+1]**2 + verts[i+2]**2);
      maxDev = Math.max(maxDev, Math.abs(r - 1.0));
    }
    // QEM should keep deviation under 10% for a sphere
    expect(maxDev).toBeLessThan(0.15);
    core.deleteMesh(h);
  });

  it('handles 0 targetFaces as half of triangulated count', () => {
    const h = core.generateTestSphere(3);
    const fBefore = core.getFaceCount(h);
    core.filterSimplify(h, 0);
    // 0 means half: result should be roughly fBefore/2 (triangulated)
    expect(core.getFaceCount(h)).toBeLessThan(fBefore);
    expect(core.getFaceCount(h)).toBeGreaterThan(0);
    core.deleteMesh(h);
  });
});

// ========== KD-TREE ICP ==========
describe('ICP with KD-Tree', () => {
  it('aligns identical meshes (identity transform)', () => {
    const h1 = loadCube();
    const h2 = loadCube();
    const json = core.computeICP(h1, h2, 10, 0.001, 1.0);
    const result = JSON.parse(json);
    expect(result.finalError).toBeLessThan(0.1);
    expect(result.iterations).toBeLessThanOrEqual(10);
    core.deleteMesh(h1);
    core.deleteMesh(h2);
  });

  it('converges for translated mesh', () => {
    const h1 = loadCube();
    const h2 = loadCube();
    core.filterTranslate(h2, 0.1, 0.1, 0.1);
    const json = core.computeICP(h1, h2, 50, 0.001, 0.5);
    const result = JSON.parse(json);
    expect(result.finalError).toBeLessThan(0.5);
    core.deleteMesh(h1);
    core.deleteMesh(h2);
  });
});

// ========== SCREENED POISSON ==========
describe('Screened Poisson (improved)', () => {
  it('produces a mesh from a sphere point cloud', () => {
    const h = core.generateTestSphere(3);
    core.estimateNormals(h, 10);
    const result = core.screenedPoissonReconstruction(h, 6);
    expect(result).toBeGreaterThanOrEqual(0);
    expect(core.getFaceCount(result)).toBeGreaterThan(10);
    expect(core.getVertexCount(result)).toBeGreaterThan(10);
    core.deleteMesh(h);
    core.deleteMesh(result);
  });

  it('higher depth produces more faces', () => {
    const h1 = core.generateTestSphere(3);
    core.estimateNormals(h1, 10);
    const r1 = core.screenedPoissonReconstruction(h1, 5);

    const h2 = core.generateTestSphere(3);
    core.estimateNormals(h2, 10);
    const r2 = core.screenedPoissonReconstruction(h2, 6);

    expect(core.getFaceCount(r2)).toBeGreaterThan(core.getFaceCount(r1));
    core.deleteMesh(h1); core.deleteMesh(h2);
    core.deleteMesh(r1); core.deleteMesh(r2);
  });
});

// ========== BALL PIVOTING ==========
describe('Ball Pivoting (improved)', () => {
  it('produces faces from a point cloud', () => {
    const h = core.createPointsOnSphere(100);
    const result = core.ballPivotingReconstruction(h, 0);
    expect(result).toBeGreaterThanOrEqual(0);
    expect(core.getFaceCount(result)).toBeGreaterThan(0);
    core.deleteMesh(h);
    core.deleteMesh(result);
  });

  it('produces more faces with smaller radius', () => {
    const h = core.generateTestSphere(2);
    // Convert to point cloud (use vertices only)
    const pts = core.createPointsOnSphere(200);
    const r1 = core.ballPivotingReconstruction(pts, 0.5);
    const f1 = core.getFaceCount(r1);
    expect(f1).toBeGreaterThan(0);
    core.deleteMesh(h); core.deleteMesh(pts); core.deleteMesh(r1);
  });
});

// ========== DXF/WRL EXPORT ==========
describe('DXF Export', () => {
  it('generates valid DXF string', async () => {
    // Can only test the pure function, not the wasm-dependent import
    // Test DXF format structure
    const vertices = new Float32Array([0,0,0, 1,0,0, 0,1,0]);
    const indices = new Uint32Array([0,1,2]);

    // Inline DXF export logic for testing
    let dxf = '0\nSECTION\n2\nHEADER\n0\nENDSEC\n';
    dxf += '0\nSECTION\n2\nENTITIES\n';
    for (let i = 0; i < indices.length; i += 3) {
      const i0 = indices[i], i1 = indices[i+1], i2 = indices[i+2];
      dxf += '0\n3DFACE\n8\n0\n';
      dxf += `10\n${vertices[i0*3]}\n20\n${vertices[i0*3+1]}\n30\n${vertices[i0*3+2]}\n`;
      dxf += `11\n${vertices[i1*3]}\n21\n${vertices[i1*3+1]}\n31\n${vertices[i1*3+2]}\n`;
      dxf += `12\n${vertices[i2*3]}\n22\n${vertices[i2*3+1]}\n32\n${vertices[i2*3+2]}\n`;
      dxf += `13\n${vertices[i2*3]}\n23\n${vertices[i2*3+1]}\n33\n${vertices[i2*3+2]}\n`;
    }
    dxf += '0\nENDSEC\n0\nEOF\n';

    expect(dxf).toContain('3DFACE');
    expect(dxf).toContain('EOF');
    expect(dxf).toContain('SECTION');
  });
});

describe('WRL Export', () => {
  it('generates valid VRML string', () => {
    const vertices = new Float32Array([0,0,0, 1,0,0, 0,1,0]);
    const indices = new Uint32Array([0,1,2]);

    let wrl = '#VRML V2.0 utf8\n\nShape {\n  geometry IndexedFaceSet {\n';
    wrl += '    coord Coordinate {\n      point [\n';
    for (let i = 0; i < vertices.length; i += 3) {
      wrl += `        ${vertices[i]} ${vertices[i+1]} ${vertices[i+2]}\n`;
    }
    wrl += '      ]\n    }\n    coordIndex [\n';
    for (let i = 0; i < indices.length; i += 3) {
      wrl += `      ${indices[i]}, ${indices[i+1]}, ${indices[i+2]}, -1\n`;
    }
    wrl += '    ]\n  }\n}\n';

    expect(wrl).toContain('#VRML V2.0');
    expect(wrl).toContain('IndexedFaceSet');
    expect(wrl).toContain('coordIndex');
  });
});
