export type InspectionPoint = [number, number, number]
export type InspectionVector = InspectionPoint
export type InspectionTopoRef = { persistentId: string; kind: 'face' | 'edge' | 'vertex'; status: 'stable' | 'ambiguous' | 'deleted' }
export type InspectionShape = { id: string; label: string; volume: number; area: number; structuralValid: boolean; bounds: { min: InspectionPoint; max: InspectionPoint }; dependencies?: string[]; topoRefs?: InspectionTopoRef[] }
export type InspectionMeasurement = { id: string; kind: 'distance' | 'angle' | 'area' | 'volume' | 'deviation'; value: number; unit: 'mm' | 'deg' | 'mm2' | 'mm3'; status: 'resolved' | 'invalid' }
export type InspectionSection = { shapeId: string; axis: 'z'; position: number; width: number; height: number; area: number }
export type InspectionSnapshot = { id: string; label: string; shapes: InspectionShape[]; measurements: InspectionMeasurement[]; dependencyIssues: string[]; version: number }
export type InspectionApi = {
  snapshot(): InspectionSnapshot
  registerShape(shape: InspectionShape): InspectionShape
  measureDistance(id: string, first: InspectionPoint, second: InspectionPoint): InspectionMeasurement
  measureAngle(id: string, first: InspectionVector, second: InspectionVector): InspectionMeasurement
  measureShape(id: string, shapeId: string, kind: 'area' | 'volume'): InspectionMeasurement
  measureDeviation(id: string, nominal: number, actual: number, tolerance: number): InspectionMeasurement
  section(shapeId: string, position: number): InspectionSection
  topoRefReport(shapeId: string): { total: number; stable: number; unresolved: string[] }
  checkDependencies(): string[]
  exportCsv(): string
}

const finitePoint = (point: InspectionPoint, label: string) => { if (point.length !== 3 || point.some((value) => !Number.isFinite(value))) throw new RangeError(`${label} must contain three finite coordinates.`); return [point[0], point[1], point[2]] as InspectionPoint }
const vectorLength = (vector: InspectionVector) => Math.hypot(vector[0], vector[1], vector[2])
const distance = (first: InspectionPoint, second: InspectionPoint) => Math.hypot(first[0] - second[0], first[1] - second[1], first[2] - second[2])
const cloneShape = (shape: InspectionShape): InspectionShape => ({ ...shape, bounds: { min: [...shape.bounds.min] as InspectionPoint, max: [...shape.bounds.max] as InspectionPoint }, dependencies: shape.dependencies ? [...shape.dependencies] : undefined, topoRefs: shape.topoRefs?.map((reference) => ({ ...reference })) })
const cloneMeasurement = (measurement: InspectionMeasurement): InspectionMeasurement => ({ ...measurement })
const csvEscape = (value: string) => /[",\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value

export const createInspection = (id = 'inspection', label = 'Inspection'): InspectionApi => {
  const shapes = new Map<string, InspectionShape>(); const measurements = new Map<string, InspectionMeasurement>(); let version = 0
  const snapshot = (): InspectionSnapshot => ({ id, label, shapes: [...shapes.values()].map(cloneShape), measurements: [...measurements.values()].map(cloneMeasurement), dependencyIssues: checkDependencies(), version })
  const registerShape = (shape: InspectionShape) => { if (!shape.id.trim() || shapes.has(shape.id)) throw new RangeError(`Inspection shape already exists: ${shape.id}`); if (![shape.volume, shape.area].every((value) => Number.isFinite(value) && value >= 0)) throw new RangeError('Inspection shape mass values must be finite and non-negative.'); finitePoint(shape.bounds.min, 'Inspection bounds.min'); finitePoint(shape.bounds.max, 'Inspection bounds.max'); if (!shape.structuralValid) throw new RangeError(`Inspection shape is structurally invalid: ${shape.id}`); shapes.set(shape.id, cloneShape(shape)); version += 1; return cloneShape(shape) }
  const setMeasurement = (measurement: InspectionMeasurement) => { if (measurements.has(measurement.id)) throw new RangeError(`Inspection measurement already exists: ${measurement.id}`); measurements.set(measurement.id, measurement); version += 1; return cloneMeasurement(measurement) }
  const measureDistance = (id: string, first: InspectionPoint, second: InspectionPoint) => { finitePoint(first, 'Inspection distance first'); finitePoint(second, 'Inspection distance second'); return setMeasurement({ id, kind: 'distance', value: distance(first, second), unit: 'mm', status: 'resolved' }) }
  const measureAngle = (id: string, first: InspectionVector, second: InspectionVector) => { finitePoint(first, 'Inspection angle first'); finitePoint(second, 'Inspection angle second'); const firstLength = vectorLength(first); const secondLength = vectorLength(second); if (firstLength <= 1e-12 || secondLength <= 1e-12) return setMeasurement({ id, kind: 'angle', value: NaN, unit: 'deg', status: 'invalid' }); const cosine = Math.max(-1, Math.min(1, (first[0] * second[0] + first[1] * second[1] + first[2] * second[2]) / (firstLength * secondLength))); return setMeasurement({ id, kind: 'angle', value: Math.acos(cosine) * 180 / Math.PI, unit: 'deg', status: 'resolved' }) }
  const measureShape = (id: string, shapeId: string, kind: 'area' | 'volume') => { const shape = shapes.get(shapeId); if (!shape) throw new RangeError(`Inspection shape does not exist: ${shapeId}`); return setMeasurement({ id, kind, value: kind === 'area' ? shape.area : shape.volume, unit: kind === 'area' ? 'mm2' : 'mm3', status: 'resolved' }) }
  const measureDeviation = (id: string, nominal: number, actual: number, tolerance: number) => { if (![nominal, actual, tolerance].every(Number.isFinite) || tolerance < 0) throw new RangeError('Inspection deviation inputs must be finite and tolerance non-negative.'); const value = actual - nominal; return setMeasurement({ id, kind: 'deviation', value, unit: 'mm', status: Math.abs(value) <= tolerance ? 'resolved' : 'invalid' }) }
  const section = (shapeId: string, position: number): InspectionSection => { const shape = shapes.get(shapeId); if (!shape) throw new RangeError(`Inspection shape does not exist: ${shapeId}`); if (!Number.isFinite(position) || position < shape.bounds.min[2] || position > shape.bounds.max[2]) throw new RangeError('Inspection section position is outside shape bounds.'); const width = shape.bounds.max[0] - shape.bounds.min[0]; const height = shape.bounds.max[1] - shape.bounds.min[1]; return { shapeId, axis: 'z', position, width, height, area: width * height } }
  const topoRefReport = (shapeId: string) => { const shape = shapes.get(shapeId); if (!shape) throw new RangeError(`Inspection shape does not exist: ${shapeId}`); const references = shape.topoRefs ?? []; return { total: references.length, stable: references.filter((reference) => reference.status === 'stable').length, unresolved: references.filter((reference) => reference.status !== 'stable').map((reference) => reference.persistentId).sort() } }
  const checkDependencies = () => { const issues: string[] = []; for (const shape of shapes.values()) for (const dependency of shape.dependencies || []) if (!shapes.has(dependency)) issues.push(`${shape.id}->${dependency}`); return issues.sort() }
  const exportCsv = () => [['id', 'kind', 'value', 'unit', 'status'], ...[...measurements.values()].map((measurement) => [measurement.id, measurement.kind, Number.isFinite(measurement.value) ? measurement.value.toFixed(6) : '', measurement.unit, measurement.status])].map((row) => row.map(csvEscape).join(',')).join('\n') + '\n'
  return { snapshot, registerShape, measureDistance, measureAngle, measureShape, measureDeviation, section, topoRefReport, checkDependencies, exportCsv }
}
