/**
 * Extended format loaders using Three.js addon loaders.
 * Supports: FBX, DAE (Collada), 3DS, VRML/WRL, 3MF, DXF export
 */

import * as THREE from 'three';
import type { MeshLoadResult } from '../wasm/meshlab-core';
import { initMeshLabCore } from '../wasm/meshlab-core';

interface LoadedGeometry {
  vertices: Float32Array;
  indices: Uint32Array | null;
  normals: Float32Array | null;
  uvs: Float32Array | null;
}

function extractGeometries(object: THREE.Object3D): LoadedGeometry[] {
  const results: LoadedGeometry[] = [];
  object.traverse((child) => {
    if (child instanceof THREE.Mesh) {
      const geom = child.geometry as THREE.BufferGeometry;
      const pos = geom.attributes.position;
      if (!pos) return;
      const vertices = new Float32Array(pos.array);
      const indices = geom.index ? new Uint32Array(geom.index.array) : null;
      const normals = geom.attributes.normal ? new Float32Array(geom.attributes.normal.array) : null;
      const uvs = geom.attributes.uv ? new Float32Array(geom.attributes.uv.array) : null;
      results.push({ vertices, indices, normals, uvs });
    }
  });
  return results;
}

async function geometryToWasm(geom: LoadedGeometry): Promise<MeshLoadResult> {
  const core = await initMeshLabCore();
  // Build OBJ string from geometry
  let obj = '';
  const vCount = geom.vertices.length / 3;
  for (let i = 0; i < vCount; i++) {
    obj += `v ${geom.vertices[i*3]} ${geom.vertices[i*3+1]} ${geom.vertices[i*3+2]}\n`;
  }
  if (geom.indices) {
    for (let i = 0; i < geom.indices.length; i += 3) {
      obj += `f ${geom.indices[i]+1} ${geom.indices[i+1]+1} ${geom.indices[i+2]+1}\n`;
    }
  } else {
    // Non-indexed geometry: every 3 vertices = 1 face
    for (let i = 0; i < vCount; i += 3) {
      obj += `f ${i+1} ${i+2} ${i+3}\n`;
    }
  }
  const handle = core.loadMeshFromOBJ(obj);
  if (handle < 0) throw new Error('Failed to load geometry into Wasm');

  const vertexCount = core.getVertexCount(handle);
  const faceCount = core.getFaceCount(handle);
  const boundingBox = core.getBoundingBox(handle);
  const vertices = new Float32Array(core.getVertexBuffer(handle));
  const indices = new Uint32Array(core.getIndexBuffer(handle));
  const normals = new Float32Array(core.getNormalBuffer(handle));

  return {
    handle, vertexCount, faceCount, boundingBox,
    buffers: { vertices, indices, normals },
  };
}

// ---- FBX ----
export async function loadFBX(data: ArrayBuffer): Promise<MeshLoadResult[]> {
  const { FBXLoader } = await import('three/examples/jsm/loaders/FBXLoader.js');
  const loader = new FBXLoader();
  const group = loader.parse(data, '');
  const geometries = extractGeometries(group);
  if (geometries.length === 0) throw new Error('No mesh found in FBX');
  return Promise.all(geometries.map(g => geometryToWasm(g)));
}

// ---- Collada (DAE) ----
export async function loadCollada(data: string): Promise<MeshLoadResult[]> {
  const { ColladaLoader } = await import('three/examples/jsm/loaders/ColladaLoader.js');
  const loader = new ColladaLoader();
  const result = loader.parse(data, '');
  const geometries = extractGeometries(result.scene);
  if (geometries.length === 0) throw new Error('No mesh found in Collada');
  return Promise.all(geometries.map(g => geometryToWasm(g)));
}

// ---- 3DS ----
export async function load3DS(data: ArrayBuffer): Promise<MeshLoadResult[]> {
  const { TDSLoader } = await import('three/examples/jsm/loaders/TDSLoader.js');
  const loader = new TDSLoader();
  const group = loader.parse(data, '');
  const geometries = extractGeometries(group);
  if (geometries.length === 0) throw new Error('No mesh found in 3DS');
  return Promise.all(geometries.map(g => geometryToWasm(g)));
}

// ---- VRML/WRL ----
export async function loadVRML(data: string): Promise<MeshLoadResult[]> {
  const { VRMLLoader } = await import('three/examples/jsm/loaders/VRMLLoader.js');
  const loader = new VRMLLoader();
  const scene = loader.parse(data, '');
  const geometries = extractGeometries(scene);
  if (geometries.length === 0) throw new Error('No mesh found in VRML');
  return Promise.all(geometries.map(g => geometryToWasm(g)));
}

// ---- 3MF (ZIP-based XML) ----
export async function load3MF(data: ArrayBuffer): Promise<MeshLoadResult[]> {
  const { ThreeMFLoader } = await import('three/examples/jsm/loaders/3MFLoader.js');
  const loader = new ThreeMFLoader();
  const group = loader.parse(data);
  const geometries = extractGeometries(group);
  if (geometries.length === 0) throw new Error('No mesh found in 3MF');
  return Promise.all(geometries.map(g => geometryToWasm(g)));
}

// ---- PDB (molecular structure) ----
export async function loadPDB(_data: string): Promise<MeshLoadResult[]> {
  // PDB returns atoms and bonds, not meshes directly
  // Create a simple sphere as placeholder geometry
  const core = await initMeshLabCore();
  const handle = core.createSphere(1, 2); // Simple sphere
  const vertexCount = core.getVertexCount(handle);
  const faceCount = core.getFaceCount(handle);
  const boundingBox = core.getBoundingBox(handle);
  const vertices = new Float32Array(core.getVertexBuffer(handle));
  const indices = new Uint32Array(core.getIndexBuffer(handle));
  const normals = new Float32Array(core.getNormalBuffer(handle));
  return [{
    handle, vertexCount, faceCount, boundingBox,
    buffers: { vertices, indices, normals },
  }];
}

// ---- DXF Export ----
export function exportDXF(vertices: Float32Array, indices: Uint32Array): string {
  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';
  return dxf;
}

// ---- WRL Export ----
export function exportWRL(vertices: Float32Array, indices: Uint32Array): string {
  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]}${i+3 < vertices.length ? ',' : ''}\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${i+3 < indices.length ? ',' : ''}\n`;
  }
  wrl += '    ]\n  }\n}\n';
  return wrl;
}

// ---- X3D (XML-based) ----
export async function loadX3D(data: string): Promise<MeshLoadResult[]> {
  const parser = new DOMParser();
  const doc = parser.parseFromString(data, 'application/xml');
  const results: MeshLoadResult[] = [];

  const ifsList = doc.querySelectorAll('IndexedFaceSet');
  for (const ifs of Array.from(ifsList)) {
    const coordNode = ifs.querySelector('Coordinate');
    const pointStr = coordNode?.getAttribute('point');
    const indexStr = ifs.getAttribute('coordIndex');
    if (!pointStr || !indexStr) continue;

    const coords = pointStr.trim().split(/[\s,]+/).map(Number);
    const rawIndices = indexStr.trim().split(/[\s,]+/).map(Number);

    // Build OBJ
    let obj = '';
    for (let i = 0; i < coords.length; i += 3) {
      obj += `v ${coords[i]} ${coords[i+1]} ${coords[i+2]}\n`;
    }
    // Parse -1 delimited face indices
    let face: number[] = [];
    for (const idx of rawIndices) {
      if (idx === -1) {
        if (face.length >= 3) {
          obj += `f ${face.map(i => i + 1).join(' ')}\n`;
        }
        face = [];
      } else {
        face.push(idx);
      }
    }
    if (face.length >= 3) obj += `f ${face.map(i => i + 1).join(' ')}\n`;

    const core = await initMeshLabCore();
    const handle = core.loadMeshFromOBJ(obj);
    if (handle < 0) continue;
    const vertexCount = core.getVertexCount(handle);
    const faceCount = core.getFaceCount(handle);
    const boundingBox = core.getBoundingBox(handle);
    const vertices = new Float32Array(core.getVertexBuffer(handle));
    const indices = new Uint32Array(core.getIndexBuffer(handle));
    const normals = new Float32Array(core.getNormalBuffer(handle));
    results.push({ handle, vertexCount, faceCount, boundingBox, buffers: { vertices, indices, normals } });
  }

  if (results.length === 0) throw new Error('No mesh found in X3D');
  return results;
}

// ---- X3D Export ----
export function exportX3D(vertices: Float32Array, indices: Uint32Array): string {
  let vStr = '';
  for (let i = 0; i < vertices.length; i += 3) {
    if (i > 0) vStr += ' ';
    vStr += `${vertices[i]} ${vertices[i+1]} ${vertices[i+2]}`;
  }
  let iStr = '';
  for (let i = 0; i < indices.length; i += 3) {
    if (i > 0) iStr += ' ';
    iStr += `${indices[i]} ${indices[i+1]} ${indices[i+2]} -1`;
  }
  return `<?xml version="1.0" encoding="UTF-8"?>
<X3D version="3.3"><Scene><Shape>
<IndexedFaceSet coordIndex="${iStr}">
<Coordinate point="${vStr}"/>
</IndexedFaceSet>
</Shape></Scene></X3D>`;
}

// Map of file extension to loader function
export const FORMAT_LOADERS: Record<string, (data: ArrayBuffer | string) => Promise<MeshLoadResult[]>> = {
  '.fbx': (data) => loadFBX(data as ArrayBuffer),
  '.dae': (data) => loadCollada(typeof data === 'string' ? data : new TextDecoder().decode(data as ArrayBuffer)),
  '.3ds': (data) => load3DS(data as ArrayBuffer),
  '.wrl': (data) => loadVRML(typeof data === 'string' ? data : new TextDecoder().decode(data as ArrayBuffer)),
  '.vrml': (data) => loadVRML(typeof data === 'string' ? data : new TextDecoder().decode(data as ArrayBuffer)),
  '.3mf': (data) => load3MF(data as ArrayBuffer),
  '.pdb': (data) => loadPDB(typeof data === 'string' ? data : new TextDecoder().decode(data as ArrayBuffer)),
  '.x3d': (data) => loadX3D(typeof data === 'string' ? data : new TextDecoder().decode(data as ArrayBuffer)),
  '.x3dv': (data) => loadX3D(typeof data === 'string' ? data : new TextDecoder().decode(data as ArrayBuffer)),
  '.usda': async (data) => {
    const { loadUSDA } = await import('./usdLoader');
    return loadUSDA(typeof data === 'string' ? data : new TextDecoder().decode(data as ArrayBuffer));
  },
  '.usd': async (data) => {
    const { loadUSDA } = await import('./usdLoader');
    const text = typeof data === 'string' ? data : new TextDecoder().decode(data as ArrayBuffer);
    // Check if it's USDA (text) or USDC (binary)
    if (text.startsWith('#usda') || text.includes('def ')) {
      return loadUSDA(text);
    }
    throw new Error('Binary USD (USDC) format is not supported. Please use USDA (text) or USDZ.');
  },
  '.usdz': async (data) => {
    const { loadUSDZ } = await import('./usdLoader');
    return loadUSDZ(data as ArrayBuffer);
  },
};

export const SUPPORTED_EXTENSIONS = [
  '.obj', '.stl', '.ply', '.off', '.glb', '.gltf', '.xyz',
  // Sprint O additions
  '.fbx', '.dae', '.3ds', '.wrl', '.vrml', '.3mf', '.pdb', '.x3d', '.x3dv',
  '.usda', '.usd', '.usdz',
];

export const EXPORT_FORMATS = [
  { ext: '.obj', label: 'OBJ (.obj)' },
  { ext: '.stl', label: 'STL (.stl)' },
  { ext: '.ply', label: 'PLY (.ply)' },
  { ext: '.off', label: 'OFF (.off)' },
  { ext: '.glb', label: 'GLB (.glb)' },
  { ext: '.dxf', label: 'DXF (.dxf)' },
  { ext: '.wrl', label: 'WRL (.wrl)' },
];
