export type MeshPoint = [number, number, number]
export type MeshTriangle = [number, number, number]
export type MeshSnapshot = { id: string; label: string; vertices: MeshPoint[]; triangles: MeshTriangle[]; version: number }
export type MeshQuality = { vertices: number; triangles: number; boundaryEdges: number; nonManifoldEdges: number; degenerateTriangles: number; selfIntersections: number; surfaceArea: number; bounds: { min: MeshPoint; max: MeshPoint } }
export type MeshLod = { level: number; vertices: number; triangles: number; indices: number[] }
export type MeshApi = {
  snapshot(): MeshSnapshot
  analyze(): MeshQuality
  weldVertices(tolerance?: number): MeshSnapshot
  removeDegenerate(tolerance?: number): MeshSnapshot
  fillBoundaryTriangle(edge: [number, number], vertex: number): MeshSnapshot
  detectSelfIntersections(): number
  lod(targetTriangles: number): MeshLod
  transform(input: { translation?: MeshPoint; scale?: number }): MeshSnapshot
  exportObj(): string
  exportPly(): string
  exportStl(): string
}

const clonePoint = (point: MeshPoint): MeshPoint => [point[0], point[1], point[2]]
const cloneTriangle = (triangle: MeshTriangle): MeshTriangle => [triangle[0], triangle[1], triangle[2]]
const finitePoint = (point: MeshPoint, label: string) => { if (point.length !== 3 || point.some((value) => !Number.isFinite(value))) throw new RangeError(`${label} must contain three finite coordinates.`) }
const crossLength = (a: MeshPoint, b: MeshPoint, c: MeshPoint) => { const ab: MeshPoint = [b[0] - a[0], b[1] - a[1], b[2] - a[2]]; const ac: MeshPoint = [c[0] - a[0], c[1] - a[1], c[2] - a[2]]; return Math.hypot(ab[1] * ac[2] - ab[2] * ac[1], ab[2] * ac[0] - ab[0] * ac[2], ab[0] * ac[1] - ab[1] * ac[0]) }
const keyFor = (point: MeshPoint, tolerance: number) => point.map((value) => Math.round(value / tolerance)).join(':')
const orientation2d = (a: MeshPoint, b: MeshPoint, c: MeshPoint) => (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0])
const segmentsCross2d = (a: MeshPoint, b: MeshPoint, c: MeshPoint, d: MeshPoint) => { const ab1 = orientation2d(a, b, c); const ab2 = orientation2d(a, b, d); const cd1 = orientation2d(c, d, a); const cd2 = orientation2d(c, d, b); return ((ab1 > 1e-12 && ab2 < -1e-12) || (ab1 < -1e-12 && ab2 > 1e-12)) && ((cd1 > 1e-12 && cd2 < -1e-12) || (cd1 < -1e-12 && cd2 > 1e-12)) }

export const createMeshDocument = (id = 'mesh', label = 'Mesh', input?: { positions: ArrayLike<number>; indices: ArrayLike<number> }): MeshApi => {
  const vertices: MeshPoint[] = []
  const triangles: MeshTriangle[] = []
  if (input) {
    if (input.positions.length % 3 !== 0 || input.indices.length % 3 !== 0) throw new RangeError('Mesh positions and indices must be divisible by 3.')
    for (let index = 0; index < input.positions.length; index += 3) { const point: MeshPoint = [Number(input.positions[index]), Number(input.positions[index + 1]), Number(input.positions[index + 2])]; finitePoint(point, 'Mesh vertex'); vertices.push(point) }
    for (let index = 0; index < input.indices.length; index += 3) { const triangle: MeshTriangle = [Number(input.indices[index]), Number(input.indices[index + 1]), Number(input.indices[index + 2])]; if (triangle.some((value) => !Number.isSafeInteger(value) || value < 0 || value >= vertices.length)) throw new RangeError('Mesh triangle index is outside the vertex array.'); triangles.push(triangle) }
  }
  let version = 0
  const snapshot = (): MeshSnapshot => ({ id, label, vertices: vertices.map(clonePoint), triangles: triangles.map(cloneTriangle), version })
  const detectSelfIntersections = () => {
    let intersections = 0
    for (let first = 0; first < triangles.length; first += 1) for (let second = first + 1; second < triangles.length; second += 1) {
      if (triangles[first].some((index) => triangles[second].includes(index))) continue
      const a = triangles[first].map((index) => vertices[index]); const b = triangles[second].map((index) => vertices[index])
      const edgesA = [[a[0], a[1]], [a[1], a[2]], [a[2], a[0]]] as const; const edgesB = [[b[0], b[1]], [b[1], b[2]], [b[2], b[0]]] as const
      if (edgesA.some(([start, end]) => edgesB.some(([otherStart, otherEnd]) => segmentsCross2d(start, end, otherStart, otherEnd)))) intersections += 1
    }
    return intersections
  }
  const analyze = (): MeshQuality => {
    const edgeCounts = new Map<string, number>(); let degenerateTriangles = 0; let surfaceArea = 0
    const min: MeshPoint = [Infinity, Infinity, Infinity]; const max: MeshPoint = [-Infinity, -Infinity, -Infinity]
    for (const point of vertices) for (let axis = 0; axis < 3; axis += 1) { min[axis] = Math.min(min[axis], point[axis]); max[axis] = Math.max(max[axis], point[axis]) }
    for (const triangle of triangles) { const [a, b, c] = triangle.map((index) => vertices[index]); const doubled = crossLength(a, b, c); if (doubled <= 1e-12) degenerateTriangles += 1; surfaceArea += doubled / 2; for (const [first, second] of [[triangle[0], triangle[1]], [triangle[1], triangle[2]], [triangle[2], triangle[0]]]) { const edge = first < second ? `${first}:${second}` : `${second}:${first}`; edgeCounts.set(edge, (edgeCounts.get(edge) || 0) + 1) } }
    return { vertices: vertices.length, triangles: triangles.length, boundaryEdges: [...edgeCounts.values()].filter((count) => count === 1).length, nonManifoldEdges: [...edgeCounts.values()].filter((count) => count > 2).length, degenerateTriangles, selfIntersections: detectSelfIntersections(), surfaceArea, bounds: { min: clonePoint(min), max: clonePoint(max) } }
  }
  const weldVertices = (tolerance = 1e-6) => { if (!Number.isFinite(tolerance) || tolerance <= 0) throw new RangeError('Mesh weld tolerance must be positive and finite.'); const indexByKey = new Map<string, number>(); const remap: number[] = []; const next: MeshPoint[] = []; vertices.forEach((point, index) => { const key = keyFor(point, tolerance); const existing = indexByKey.get(key); if (existing === undefined) { indexByKey.set(key, next.length); remap[index] = next.length; next.push(clonePoint(point)) } else remap[index] = existing }); vertices.splice(0, vertices.length, ...next); triangles.forEach((triangle, index) => { triangles[index] = [remap[triangle[0]], remap[triangle[1]], remap[triangle[2]]] }); version += 1; return snapshot() }
  const removeDegenerate = (tolerance = 1e-12) => { if (!Number.isFinite(tolerance) || tolerance < 0) throw new RangeError('Mesh degenerate tolerance must be finite and non-negative.'); for (let index = triangles.length - 1; index >= 0; index -= 1) { const [a, b, c] = triangles[index].map((entry) => vertices[entry]); if (crossLength(a, b, c) <= tolerance || new Set(triangles[index]).size !== 3) triangles.splice(index, 1) } version += 1; return snapshot() }
  const fillBoundaryTriangle = (edge: [number, number], vertex: number) => { if (![edge[0], edge[1], vertex].every((value) => Number.isSafeInteger(value) && value >= 0 && value < vertices.length)) throw new RangeError('Mesh boundary repair references an unknown vertex.'); const existing = triangles.some((triangle) => triangle.includes(edge[0]) && triangle.includes(edge[1])); if (existing) throw new RangeError('Mesh boundary repair edge is not available for this minimal repair contract.'); if (new Set([edge[0], edge[1], vertex]).size !== 3) throw new RangeError('Mesh boundary repair requires three distinct vertices.'); triangles.push([edge[0], edge[1], vertex]); version += 1; return snapshot() }
  const lod = (targetTriangles: number): MeshLod => { if (!Number.isSafeInteger(targetTriangles) || targetTriangles < 1 || targetTriangles > triangles.length) throw new RangeError('Mesh LOD target must be a positive integer no greater than the triangle count.'); const stride = Math.max(1, Math.ceil(triangles.length / targetTriangles)); const selected = triangles.filter((_, index) => index % stride === 0).slice(0, targetTriangles); return { level: targetTriangles === triangles.length ? 0 : 1, vertices: vertices.length, triangles: selected.length, indices: selected.flatMap((triangle) => [...triangle]) } }
  const transform = (input: { translation?: MeshPoint; scale?: number }) => { const translation = input.translation ?? [0, 0, 0] as MeshPoint; finitePoint(translation, 'Mesh translation'); const scale = input.scale ?? 1; if (!Number.isFinite(scale) || scale === 0) throw new RangeError('Mesh scale must be finite and non-zero.'); vertices.forEach((point) => { point[0] = point[0] * scale + translation[0]; point[1] = point[1] * scale + translation[1]; point[2] = point[2] * scale + translation[2] }); version += 1; return snapshot() }
  const exportObj = () => { const lines = vertices.map((point) => `v ${point.map((value) => value.toFixed(6)).join(' ')}`); const faces = triangles.map((triangle) => `f ${triangle.map((value) => value + 1).join(' ')}`); return `${lines.concat(faces).join('\n')}\n` }
  const exportPly = () => { const lines = ['ply', 'format ascii 1.0', `element vertex ${vertices.length}`, 'property float x', 'property float y', 'property float z', `element face ${triangles.length}`, 'property list uchar int vertex_indices', 'end_header']; return `${lines.concat(vertices.map((point) => point.map((value) => value.toFixed(6)).join(' ')), triangles.map((triangle) => `3 ${triangle.join(' ')}`)).join('\n')}\n` }
  const exportStl = () => { const lines = ['solid bitbybit']; for (const triangle of triangles) { const [a, b, c] = triangle.map((index) => vertices[index]); const normal: MeshPoint = [0, 0, 0]; lines.push(` facet normal ${normal.join(' ')}`, '  outer loop', `   vertex ${a.join(' ')}`, `   vertex ${b.join(' ')}`, `   vertex ${c.join(' ')}`, '  endloop', ' endfacet') } lines.push('endsolid bitbybit'); return `${lines.join('\n')}\n` }
  return { snapshot, analyze, weldVertices, removeDegenerate, fillBoundaryTriangle, detectSelfIntersections, lod, transform, exportObj, exportPly, exportStl }
}
