/// <reference lib="webworker" />

import createBitbybitDevOcct from '@bitbybit-dev/occt/bitbybit-dev-occt/bitbybit-dev-occt'
import type { MainModule, TopoDS_Shape } from '@bitbybit-dev/occt/bitbybit-dev-occt/bitbybit-dev-occt'
import { initializationComplete, onMessageInput } from '@bitbybit-dev/occt-worker'

const workerScope = self as DedicatedWorkerGlobalScope

const createWebCadPlugins = (occt: MainModule) => ({
  dependencies: {},
  boolean: {
    intersection: (inputs: { shapes: TopoDS_Shape[]; keepEdges: boolean }) => {
      if (inputs.shapes.length < 2) throw new Error('Intersection requires at least two shapes.')
      let current = inputs.shapes[0]
      let ownsCurrent = false
      try {
        for (const next of inputs.shapes.slice(1)) {
          const operation = new occt.BRepAlgoAPI_Common(current, next)
          operation.Build()
          if (!operation.IsDone() || operation.HasErrors()) {
            operation.delete()
            throw new Error('OCCT common operation failed.')
          }
          const common = operation.Shape()
          operation.delete()
          if (ownsCurrent) current.delete()
          current = common
          ownsCurrent = true
        }
        if (!inputs.keepEdges) {
          const unified = occt.ShapeUpgrade_UnifySameDomain_Perform(current, true, true, false)
          current.delete()
          current = unified
        }
        return current
      } catch (error) {
        if (ownsCurrent) current.delete()
        throw error
      }
    },
  },
  feature: {
    revolution: (inputs: { shape: TopoDS_Shape; axisOrigin: [number, number, number]; axisDirection: [number, number, number]; angle: number }) => {
      const point = new occt.gp_Pnt(inputs.axisOrigin[0], inputs.axisOrigin[1], inputs.axisOrigin[2])
      const direction = new occt.gp_Dir(inputs.axisDirection[0], inputs.axisDirection[1], inputs.axisDirection[2])
      const axis = new occt.gp_Ax1(point, direction)
      const operation = inputs.angle >= 360
        ? new occt.BRepPrimAPI_MakeRevol(inputs.shape, axis)
        : new occt.BRepPrimAPI_MakeRevol(inputs.shape, axis, inputs.angle * Math.PI / 180, false)
      try {
        if (!operation.IsDone()) throw new Error('OCCT revolution failed.')
        return operation.Shape()
      } finally {
        operation.delete()
        axis.delete()
        direction.delete()
        point.delete()
      }
    },
  },
  io: {
    exportBrep: (inputs: { shape: TopoDS_Shape }) => occt.WriteBREPToString(inputs.shape),
    exportIges: (inputs: { shape: TopoDS_Shape }) => occt.WriteIGESToString(inputs.shape),
    importShape: (inputs: { format: 'step' | 'iges' | 'brep'; text: string }) => {
      if (!inputs.text.trim()) throw new Error('Geometry import payload is empty.')
      if (inputs.format === 'step') return occt.ReadSTEPFromString(inputs.text)
      if (inputs.format === 'iges') return occt.ReadIGESFromString(inputs.text)
      if (inputs.format === 'brep') return occt.ReadBREPFromString(inputs.text)
      throw new Error(`Unsupported geometry import format: ${String(inputs.format)}`)
    },
  },
  quality: {
    massProperties: (inputs: { shape: TopoDS_Shape }) => {
      const volume = occt.ComputeVolumeProperties(inputs.shape)
      const surface = occt.ComputeSurfaceProperties(inputs.shape)
      const center = volume.CentreOfMass
      const result = { volume: volume.Mass, surfaceArea: surface.Mass, centerOfMass: [center.X(), center.Y(), center.Z()] as [number, number, number] }
      volume.delete()
      surface.delete()
      return result
    },
  },
  topology: {
    describe: (inputs: { shape: TopoDS_Shape }) => {
      const parse = (value: string) => JSON.parse(value) as Record<string, unknown>
      const faceInfo = parse(occt.BRepGraphFaceInfo(inputs.shape)) as { faces: Array<Record<string, unknown>> }
      const edgeInfo = parse(occt.BRepGraphEdgeInfo(inputs.shape)) as { edges: Array<Record<string, unknown>> }
      const adjacency = parse(occt.BRepGraphFaceAdjacency(inputs.shape)) as { faces: Array<Record<string, unknown>> }
      const edgeFaceMap = parse(occt.BRepGraphEdgeFaceMap(inputs.shape)) as { edges: Array<Record<string, unknown>> }
      const vertexEdgeMap = parse(occt.BRepGraphVertexEdgeMap(inputs.shape)) as { vertices: Array<Record<string, unknown>> }
      const faceMetrics = new Map<number, { area: number; centroid: [number, number, number] }>()
      const faceExplorer = new occt.TopExp_Explorer(inputs.shape, occt.TopAbs_ShapeEnum.FACE)
      while (faceExplorer.More()) {
        const face = occt.CastToFace(faceExplorer.Current())
        const properties = occt.ComputeSurfaceProperties(face)
        const center = properties.CentreOfMass
        faceMetrics.set(faceMetrics.size, { area: properties.Mass, centroid: [center.X(), center.Y(), center.Z()] })
        properties.delete()
        faceExplorer.Next()
      }
      faceExplorer.delete()
      const edgeMetrics = new Map<number, { length: number; centroid: [number, number, number] }>()
      const edgeExplorer = new occt.TopExp_Explorer(inputs.shape, occt.TopAbs_ShapeEnum.EDGE)
      while (edgeExplorer.More()) {
        const edge = occt.CastToEdge(edgeExplorer.Current())
        const properties = occt.ComputeLinearProperties(edge)
        const center = properties.CentreOfMass
        edgeMetrics.set(edgeMetrics.size, { length: properties.Mass, centroid: [center.X(), center.Y(), center.Z()] })
        properties.delete()
        edgeExplorer.Next()
      }
      edgeExplorer.delete()
      const surfaces: Array<Record<string, unknown>> = faceInfo.faces.map((face, index) => ({ ...face, ...(faceMetrics.get(index) ?? {}), adjacentFaceCount: (adjacency.faces[index]?.adjacent as unknown[] | undefined)?.length ?? 0, edgeCount: (adjacency.faces[index]?.edges as unknown[] | undefined)?.length ?? Number(face.nbWires ?? 0) }))
      const surfaceByFace = new Map(surfaces.map((face) => [Number(face.index), String(face.surfaceType ?? 'Other')]))
      const curves: Array<Record<string, unknown>> = edgeInfo.edges.map((edge, index) => ({ ...edge, ...(edgeMetrics.get(index) ?? {}), adjacentFaceTypes: ((edgeFaceMap.edges[index]?.faces as unknown[] | undefined) ?? []).map((faceIndex) => surfaceByFace.get(Number(faceIndex)) ?? 'Other') }))
      const vertices: Array<Record<string, unknown>> = (vertexEdgeMap.vertices ?? []).map((vertex) => ({ ...vertex, point: vertex.point, incidentEdgeCount: (vertex.edges as unknown[] | undefined)?.length ?? 0 }))
      return {
        faces: surfaces,
        edges: curves,
        vertices,
        adjacency: {
          faceNeighbors: adjacency.faces.map((face) => (face.adjacent as number[] | undefined) ?? []),
          faceEdges: adjacency.faces.map((face) => (face.edges as number[] | undefined) ?? []),
          edgeFaces: edgeFaceMap.edges.map((edge) => (edge.faces as number[] | undefined) ?? []),
          edgeVertices: edgeFaceMap.edges.map((edge) => [edge.startVertex, edge.endVertex].filter((index): index is number => Number.isSafeInteger(index))),
          vertexEdges: (vertexEdgeMap.vertices ?? []).map((vertex) => (vertex.edges as number[] | undefined) ?? []),
        },
      }
    },
  },
})

const initialize = async () => {
  try {
    const occt = await createBitbybitDevOcct()
    initializationComplete(occt, createWebCadPlugins(occt))
    workerScope.onmessage = ({ data }) => onMessageInput(data, (message) => workerScope.postMessage(message))
  } catch (error) {
    workerScope.postMessage({
      type: 'occ-initialization-failed',
      error: error instanceof Error ? error.message : String(error),
    })
  }
}

void initialize()

export {}
