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;
  getVertexCount(h: number): number;
  getFaceCount(h: number): number;
  selectFaces(h: number, ptr: number, count: number): void;
  deselectFaces(h: number, ptr: number, count: number): void;
  selectAllFaces(h: number): void;
  deselectAllFaces(h: number): void;
  invertFaceSelection(h: number): void;
  getSelectedFaceCount(h: number): number;
  getSelectionMask(h: number): Uint8Array;
  selectConnectedComponent(h: number, faceIndex: number): void;
  selectSmallComponents(h: number, minFaces: number): void;
  deleteSelectedFaces(h: 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 loadCube(): number { return core.loadMeshFromOBJ(cubeOBJ); }

function selectFaceIndices(h: number, indices: number[]) {
  const arr = new Uint32Array(indices);
  const ptr = core._malloc(arr.byteLength);
  core.HEAPU8.set(new Uint8Array(arr.buffer), ptr);
  core.selectFaces(h, ptr, arr.length);
  core._free(ptr);
}

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

describe('Face Selection', () => {
  it('selects and deselects faces', () => {
    const h = loadCube();
    expect(core.getSelectedFaceCount(h)).toBe(0);

    selectFaceIndices(h, [0, 1, 2]);
    expect(core.getSelectedFaceCount(h)).toBe(3);

    core.deselectAllFaces(h);
    expect(core.getSelectedFaceCount(h)).toBe(0);
    core.deleteMesh(h);
  });

  it('select all and invert', () => {
    const h = loadCube();
    const total = core.getFaceCount(h);

    core.selectAllFaces(h);
    expect(core.getSelectedFaceCount(h)).toBe(total);

    core.invertFaceSelection(h);
    expect(core.getSelectedFaceCount(h)).toBe(0);
    core.deleteMesh(h);
  });

  it('selection mask reflects selection state', () => {
    const h = loadCube();
    selectFaceIndices(h, [2, 5]);
    const mask = new Uint8Array(core.getSelectionMask(h));
    expect(mask[2]).toBe(1);
    expect(mask[5]).toBe(1);
    expect(mask[0]).toBe(0);
    expect(mask[1]).toBe(0);
    core.deleteMesh(h);
  });
});

describe('Connected Component Selection', () => {
  it('selects only the connected component', () => {
    const obj = `v 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 3\nv 10 10 10\nv 11 10 10\nv 10 11 10\nf 4 5 6\n`;
    const h = core.loadMeshFromOBJ(obj);
    core.selectConnectedComponent(h, 0);
    expect(core.getSelectedFaceCount(h)).toBe(1);
    core.deleteMesh(h);
  });
});

describe('Small Component Selection', () => {
  it('selects components with <= minFaces', () => {
    const obj = `v 0 0 0\nv 1 0 0\nv 0 1 0\nv 1 1 0\nv 0 0 1\nv 1 0 1\nf 1 2 3\nf 2 4 3\nf 1 5 6\nf 1 6 2\nv 99 99 99\nv 100 99 99\nv 99 100 99\nf 7 8 9\n`;
    const h = core.loadMeshFromOBJ(obj);
    core.selectSmallComponents(h, 2);
    expect(core.getSelectedFaceCount(h)).toBe(1);
    core.deleteMesh(h);
  });
});

describe('Delete Selected Faces', () => {
  it('removes selected faces and cleans up vertices', () => {
    const h = loadCube();
    const facesBefore = core.getFaceCount(h);
    selectFaceIndices(h, [0, 1]);
    core.deleteSelectedFaces(h);
    expect(core.getFaceCount(h)).toBe(facesBefore - 2);
    expect(core.getSelectedFaceCount(h)).toBe(0);
    core.deleteMesh(h);
  });
});
