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;
  loadMeshFromOFF(off: string): number;
  loadMeshFromPLYBinary(ptr: number, len: number): number;
  loadMesh(ptr: number, len: number, filename: string): number;
  exportToPLY(handle: number, includeColors: boolean): string;
  exportToOFF(handle: number): string;
  getVertexCount(h: number): number;
  getFaceCount(h: number): number;
  hasVertexColors(h: number): boolean;
  loadMeshFromPLY(ply: string): number;
  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');

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

describe('OFF format', () => {
  it('reads OFF file', () => {
    const off = readFileSync(resolve(__dirname, '../fixtures/cube.off'), 'utf-8');
    const h = core.loadMeshFromOFF(off);
    expect(core.getVertexCount(h)).toBe(8);
    expect(core.getFaceCount(h)).toBe(6);
    core.deleteMesh(h);
  });

  it('OFF round-trip preserves data', () => {
    const h1 = core.loadMeshFromOBJ(cubeOBJ);
    const offStr = core.exportToOFF(h1);
    const h2 = core.loadMeshFromOFF(offStr);
    expect(core.getVertexCount(h2)).toBe(core.getVertexCount(h1));
    expect(core.getFaceCount(h2)).toBe(core.getFaceCount(h1));
    core.deleteMesh(h1);
    core.deleteMesh(h2);
  });
});

describe('Binary PLY', () => {
  it('reads binary PLY file', () => {
    const data = readFileSync(resolve(__dirname, '../fixtures/cube-binary.ply'));
    const ptr = core._malloc(data.byteLength);
    core.HEAPU8.set(new Uint8Array(data.buffer, data.byteOffset, data.byteLength), ptr);
    const h = core.loadMeshFromPLYBinary(ptr, data.byteLength);
    core._free(ptr);
    expect(core.getVertexCount(h)).toBe(8);
    expect(core.getFaceCount(h)).toBe(6);
    core.deleteMesh(h);
  });

  it('auto-detects binary PLY via loadMesh', () => {
    const data = readFileSync(resolve(__dirname, '../fixtures/cube-binary.ply'));
    const ptr = core._malloc(data.byteLength);
    core.HEAPU8.set(new Uint8Array(data.buffer, data.byteOffset, data.byteLength), ptr);
    const h = core.loadMesh(ptr, data.byteLength, 'cube.ply');
    core._free(ptr);
    expect(core.getVertexCount(h)).toBe(8);
    core.deleteMesh(h);
  });
});

describe('PLY export', () => {
  it('exports PLY ASCII', () => {
    const h = core.loadMeshFromOBJ(cubeOBJ);
    const ply = core.exportToPLY(h, false);
    expect(ply).toContain('ply');
    expect(ply).toContain('element vertex 8');
    // Round-trip
    const h2 = core.loadMeshFromPLY(ply);
    expect(core.getVertexCount(h2)).toBe(8);
    core.deleteMesh(h);
    core.deleteMesh(h2);
  });
});

describe('Format auto-detection via loadMesh', () => {
  it('detects OFF by extension', () => {
    const off = readFileSync(resolve(__dirname, '../fixtures/cube.off'), 'utf-8');
    const enc = new TextEncoder();
    const data = enc.encode(off);
    const ptr = core._malloc(data.byteLength);
    core.HEAPU8.set(data, ptr);
    const h = core.loadMesh(ptr, data.byteLength, 'test.off');
    core._free(ptr);
    expect(core.getVertexCount(h)).toBe(8);
    core.deleteMesh(h);
  });
});
