import type { SubshapeRef, SubshapeSignature, TopologyAdjacency } from './types'

export type { SubshapeSignature } from './types'

export type FaceMeshInput = {
  vertexCoord: number[]
  normalCoord: number[]
  triIndexes: number[]
}

export type AnalyticFaceInput = {
  surfaceType: string
  area: number
  centroid?: [number, number, number]
  tolerance?: number
  adjacentFaceCount: number
  edgeCount: number
  wireCount?: number
  naturalRestriction?: boolean
}

export type AnalyticEdgeInput = {
  curveType: string
  length: number
  centroid?: [number, number, number]
  bounds?: { min: [number, number, number]; max: [number, number, number] }
  tolerance?: number
  parameterRange?: [number, number]
  adjacentFaceTypes: string[]
  startVertex?: number
  endVertex?: number
  degenerated?: boolean
}

export type AnalyticVertexInput = {
  point: [number, number, number]
  tolerance?: number
  incidentEdgeCount: number
}

export type SubshapeMatch = {
  current: SubshapeRef
  previousId?: string
  score: number
  status: 'stable' | 'ambiguous' | 'new' | 'deleted'
}

export type TopologyAdjacencyMatch = SubshapeMatch & {
  adjacencyScore: number
}

const quantize = (value: number, tolerance: number) => Math.round(value / tolerance)

const hashString = (value: string) => {
  let hash = 1469598103934665603n
  for (let index = 0; index < value.length; index += 1) {
    hash ^= BigInt(value.charCodeAt(index))
    hash = BigInt.asUintN(64, hash * 1099511628211n)
  }
  return hash.toString(16).padStart(16, '0')
}

const cross = (left: [number, number, number], right: [number, number, number]): [number, number, number] => [left[1] * right[2] - left[2] * right[1], left[2] * right[0] - left[0] * right[2], left[0] * right[1] - left[1] * right[0]]
const magnitude = (value: [number, number, number]) => Math.hypot(value[0], value[1], value[2])

const facePoint = (coordinates: number[], index: number): [number, number, number] => [coordinates[index * 3], coordinates[index * 3 + 1], coordinates[index * 3 + 2]]

export const signatureForFace = (face: FaceMeshInput, tolerance = 1e-5): SubshapeSignature => {
  if (face.vertexCoord.length === 0 || face.vertexCoord.length % 3 !== 0) throw new RangeError('A face signature requires vertex coordinates in triples.')
  const pointCount = face.vertexCoord.length / 3
  const min: [number, number, number] = [Infinity, Infinity, Infinity]
  const max: [number, number, number] = [-Infinity, -Infinity, -Infinity]
  const sum: [number, number, number] = [0, 0, 0]
  for (let index = 0; index < pointCount; index += 1) {
    const point = facePoint(face.vertexCoord, index)
    for (let axis = 0; axis < 3; axis += 1) { min[axis] = Math.min(min[axis], point[axis]); max[axis] = Math.max(max[axis], point[axis]); sum[axis] += point[axis] }
  }
  const centroid: [number, number, number] = sum.map((value) => value / pointCount) as [number, number, number]
  let area = 0
  const normalSum: [number, number, number] = [0, 0, 0]
  for (let index = 0; index + 2 < face.triIndexes.length; index += 3) {
    const a = facePoint(face.vertexCoord, face.triIndexes[index])
    const b = facePoint(face.vertexCoord, face.triIndexes[index + 1])
    const c = facePoint(face.vertexCoord, face.triIndexes[index + 2])
    const normal = cross([b[0] - a[0], b[1] - a[1], b[2] - a[2]], [c[0] - a[0], c[1] - a[1], c[2] - a[2]])
    area += magnitude(normal) / 2
    normalSum[0] += normal[0]; normalSum[1] += normal[1]; normalSum[2] += normal[2]
  }
  const normalLength = magnitude(normalSum)
  const normal: [number, number, number] = normalLength > tolerance ? normalSum.map((value) => value / normalLength) as [number, number, number] : [0, 0, 0]
  const canonical = [
    'face',
    `bounds=${min.map((value) => quantize(value, tolerance)).join(',')}:${max.map((value) => quantize(value, tolerance)).join(',')}`,
    `centroid=${centroid.map((value) => quantize(value, tolerance)).join(',')}`,
    `area=${quantize(area, tolerance)}`,
    `normal=${normal.map((value) => quantize(value, tolerance)).join(',')}`,
  ].join('|')
  return { kind: 'face', canonical, hash: hashString(canonical), centroid, bounds: { min, max }, area, normal }
}

const pointCanonical = (point: [number, number, number], tolerance: number) => point.map((value) => quantize(value, tolerance)).join(',')

export const signatureForEdge = (start: [number, number, number], end: [number, number, number], tolerance = 1e-5): SubshapeSignature => {
  if (![...start, ...end].every(Number.isFinite)) throw new RangeError('An edge signature requires finite endpoint coordinates.')
  const ordered = pointCanonical(start, tolerance) <= pointCanonical(end, tolerance) ? [start, end] : [end, start]
  const midpoint: [number, number, number] = [(ordered[0][0] + ordered[1][0]) / 2, (ordered[0][1] + ordered[1][1]) / 2, (ordered[0][2] + ordered[1][2]) / 2]
  const length = Math.hypot(ordered[1][0] - ordered[0][0], ordered[1][1] - ordered[0][1], ordered[1][2] - ordered[0][2])
  const min: [number, number, number] = [Math.min(ordered[0][0], ordered[1][0]), Math.min(ordered[0][1], ordered[1][1]), Math.min(ordered[0][2], ordered[1][2])]
  const max: [number, number, number] = [Math.max(ordered[0][0], ordered[1][0]), Math.max(ordered[0][1], ordered[1][1]), Math.max(ordered[0][2], ordered[1][2])]
  const canonical = ['edge', `ends=${pointCanonical(ordered[0], tolerance)}:${pointCanonical(ordered[1], tolerance)}`, `length=${quantize(length, tolerance)}`].join('|')
  return { kind: 'edge', canonical, hash: hashString(canonical), centroid: midpoint, bounds: { min, max }, area: 0, normal: [0, 0, 0] }
}

export const signatureForVertex = (point: [number, number, number], tolerance = 1e-5): SubshapeSignature => {
  if (!point.every(Number.isFinite)) throw new RangeError('A vertex signature requires finite coordinates.')
  const canonical = `vertex|point=${pointCanonical(point, tolerance)}`
  return { kind: 'vertex', canonical, hash: hashString(canonical), centroid: [...point] as [number, number, number], bounds: { min: [...point] as [number, number, number], max: [...point] as [number, number, number] }, area: 0, normal: [0, 0, 0] }
}

const analyticCanonical = (kind: string, fields: string[]) => [kind, ...fields].join('|')

export const signatureForAnalyticFace = (face: AnalyticFaceInput, tolerance = 1e-5): SubshapeSignature => {
  if (!face.surfaceType.trim() || !Number.isFinite(face.area) || face.area < 0) throw new RangeError('An analytic face signature requires a surface type and non-negative area.')
  if (!Number.isSafeInteger(face.adjacentFaceCount) || face.adjacentFaceCount < 0 || !Number.isSafeInteger(face.edgeCount) || face.edgeCount < 0) throw new RangeError('Analytic face adjacency counts must be non-negative integers.')
  const canonical = analyticCanonical('face', [
    `surface=${face.surfaceType.trim().toLowerCase()}`,
    `area=${quantize(face.area, tolerance)}`,
    `adjacent=${face.adjacentFaceCount}`,
    `edges=${face.edgeCount}`,
    `wires=${face.wireCount ?? 0}`,
    `natural=${face.naturalRestriction === true ? 1 : 0}`,
  ])
  const centroid: [number, number, number] = face.centroid ? [...face.centroid] as [number, number, number] : [0, 0, 0]
  return { kind: 'face', canonical, hash: hashString(canonical), centroid, bounds: { min: [...centroid], max: [...centroid] }, area: face.area, normal: [0, 0, 0], analytic: { type: face.surfaceType.trim(), tolerance: face.tolerance ?? tolerance, adjacencyDegree: face.adjacentFaceCount, incidentCount: face.edgeCount } }
}

export const signatureForAnalyticEdge = (edge: AnalyticEdgeInput, tolerance = 1e-5): SubshapeSignature => {
  if (!edge.curveType.trim() || !Number.isFinite(edge.length) || edge.length < 0) throw new RangeError('An analytic edge signature requires a curve type and non-negative length.')
  const adjacent = [...edge.adjacentFaceTypes].map((type) => type.trim().toLowerCase()).sort().join(',')
  const canonical = analyticCanonical('edge', [
    `curve=${edge.curveType.trim().toLowerCase()}`,
    `length=${quantize(edge.length, tolerance)}`,
    `range=${edge.parameterRange ? [...edge.parameterRange].sort((left, right) => left - right).map((value) => quantize(value, tolerance)).join(',') : ''}`,
    `adjacent=${adjacent}`,
    `degenerated=${edge.degenerated === true ? 1 : 0}`,
  ])
  const centroid: [number, number, number] = edge.centroid ? [...edge.centroid] as [number, number, number] : [0, 0, 0]
  const bounds = edge.bounds ?? { min: [...centroid] as [number, number, number], max: [...centroid] as [number, number, number] }
  return { kind: 'edge', canonical, hash: hashString(canonical), centroid, bounds, area: 0, normal: [0, 0, 0], analytic: { type: edge.curveType.trim(), tolerance: edge.tolerance ?? tolerance, parameterRange: edge.parameterRange, adjacencyDegree: edge.adjacentFaceTypes.length, incidentCount: edge.adjacentFaceTypes.length } }
}

export const signatureForAnalyticVertex = (vertex: AnalyticVertexInput, tolerance = 1e-5): SubshapeSignature => {
  if (!vertex.point.every(Number.isFinite) || !Number.isSafeInteger(vertex.incidentEdgeCount) || vertex.incidentEdgeCount < 0) throw new RangeError('An analytic vertex signature requires a finite point and edge count.')
  const canonical = analyticCanonical('vertex', [`degree=${vertex.incidentEdgeCount}`])
  return { kind: 'vertex', canonical, hash: hashString(canonical), centroid: [...vertex.point], bounds: { min: [...vertex.point], max: [...vertex.point] }, area: 0, normal: [0, 0, 0], analytic: { type: 'vertex', tolerance: vertex.tolerance ?? tolerance, adjacencyDegree: vertex.incidentEdgeCount, incidentCount: vertex.incidentEdgeCount } }
}

const refsForSignatures = (shapeId: string, topologyVersion: number, signatures: SubshapeSignature[]): SubshapeRef[] => {
  const occurrences = new Map<string, number>()
  signatures.forEach((signature) => occurrences.set(`${signature.kind}:${signature.hash}`, (occurrences.get(`${signature.kind}:${signature.hash}`) ?? 0) + 1))
  const indexes = new Map<string, number>()
  const identities = signatures.map((signature) => {
    const key = `${signature.kind}:${signature.hash}`
    const duplicate = (occurrences.get(key) ?? 0) > 1
    const index = (indexes.get(key) ?? 0) + 1
    indexes.set(key, index)
    return { key, persistentId: `topo-${signature.kind}-${signature.hash}${duplicate ? `~${index}` : ''}` }
  })
  return signatures.map((signature, index) => {
    const identity = identities[index]
    const duplicate = (occurrences.get(identity.key) ?? 0) > 1
    return { shapeId, kind: signature.kind, persistentId: identity.persistentId, topologyVersion, status: duplicate ? 'ambiguous' as const : 'stable' as const, signature: signature.canonical, candidates: duplicate ? identities.filter((candidate) => candidate.key === identity.key).map((candidate) => candidate.persistentId) : undefined }
  })
}

export const createEdgeSubshapeRefs = (shapeId: string, topologyVersion: number, faces: FaceMeshInput[], tolerance = 1e-5): { refs: SubshapeRef[]; signatures: SubshapeSignature[] } => {
  const signatures = new Map<string, SubshapeSignature>()
  for (const face of faces) for (let index = 0; index + 2 < face.triIndexes.length; index += 3) {
    const points = [facePoint(face.vertexCoord, face.triIndexes[index]), facePoint(face.vertexCoord, face.triIndexes[index + 1]), facePoint(face.vertexCoord, face.triIndexes[index + 2])]
    for (let edgeIndex = 0; edgeIndex < 3; edgeIndex += 1) {
      const signature = signatureForEdge(points[edgeIndex], points[(edgeIndex + 1) % 3], tolerance)
      signatures.set(signature.hash, signature)
    }
  }
  const values = [...signatures.values()]
  return { refs: refsForSignatures(shapeId, topologyVersion, values), signatures: values }
}

export const createVertexSubshapeRefs = (shapeId: string, topologyVersion: number, faces: FaceMeshInput[], tolerance = 1e-5): { refs: SubshapeRef[]; signatures: SubshapeSignature[] } => {
  const signatures = new Map<string, SubshapeSignature>()
  for (const face of faces) for (let index = 0; index < face.vertexCoord.length; index += 3) {
    const signature = signatureForVertex(facePoint(face.vertexCoord, index / 3), tolerance)
    signatures.set(signature.hash, signature)
  }
  const values = [...signatures.values()]
  return { refs: refsForSignatures(shapeId, topologyVersion, values), signatures: values }
}

export const createSubshapeRefs = (shapeId: string, topologyVersion: number, faces: FaceMeshInput[], tolerance = 1e-5): { refs: SubshapeRef[]; signatures: SubshapeSignature[] } => {
  const signatures = faces.map((face) => signatureForFace(face, tolerance))
  return { refs: refsForSignatures(shapeId, topologyVersion, signatures), signatures }
}

export const createAnalyticSubshapeRefs = (shapeId: string, topologyVersion: number, faces: AnalyticFaceInput[], edges: AnalyticEdgeInput[], vertices: AnalyticVertexInput[], tolerance = 1e-5): { faces: { refs: SubshapeRef[]; signatures: SubshapeSignature[] }; edges: { refs: SubshapeRef[]; signatures: SubshapeSignature[] }; vertices: { refs: SubshapeRef[]; signatures: SubshapeSignature[] } } => {
  const faceSignatures = faces.map((face) => signatureForAnalyticFace(face, tolerance))
  const edgeSignatures = edges.map((edge) => signatureForAnalyticEdge(edge, tolerance))
  const vertexSignatures = vertices.map((vertex) => signatureForAnalyticVertex(vertex, tolerance))
  return {
    faces: { refs: refsForSignatures(shapeId, topologyVersion, faceSignatures), signatures: faceSignatures },
    edges: { refs: refsForSignatures(shapeId, topologyVersion, edgeSignatures), signatures: edgeSignatures },
    vertices: { refs: refsForSignatures(shapeId, topologyVersion, vertexSignatures), signatures: vertexSignatures },
  }
}

type AdjacencyIndexInput = {
  faceNeighbors?: number[][]
  faceEdges?: number[][]
  edgeFaces?: number[][]
  edgeVertices?: number[][]
  vertexEdges?: number[][]
}

const resolveIndexes = (indexes: number[] | undefined, refs: SubshapeRef[], label: string) => {
  const values = indexes ?? []
  if (!values.every((index) => Number.isSafeInteger(index) && index >= 0 && index < refs.length)) throw new RangeError(`${label} adjacency index is out of range.`)
  return [...new Set(values)].map((index) => refs[index].persistentId).sort()
}

export const createTopologyAdjacency = (
  topology: { faces: SubshapeRef[]; edges: SubshapeRef[]; vertices: SubshapeRef[] },
  indexes: AdjacencyIndexInput,
): TopologyAdjacency => {
  // Keep the source maps explicit: this prevents a transient OCCT index from entering a persisted graph.
  return {
    faceNeighbors: Object.fromEntries(topology.faces.map((ref, index) => [ref.persistentId, resolveIndexes(indexes.faceNeighbors?.[index], topology.faces, 'face')])),
    faceEdges: Object.fromEntries(topology.faces.map((ref, index) => [ref.persistentId, resolveIndexes(indexes.faceEdges?.[index], topology.edges, 'face-edge')])),
    edgeFaces: Object.fromEntries(topology.edges.map((ref, index) => [ref.persistentId, resolveIndexes(indexes.edgeFaces?.[index], topology.faces, 'edge-face')])),
    edgeVertices: Object.fromEntries(topology.edges.map((ref, index) => [ref.persistentId, resolveIndexes(indexes.edgeVertices?.[index], topology.vertices, 'edge-vertex')])),
    vertexEdges: Object.fromEntries(topology.vertices.map((ref, index) => [ref.persistentId, resolveIndexes(indexes.vertexEdges?.[index], topology.edges, 'vertex-edge')])),
  }
}

type AdjacencyRelation = 'faceNeighbors' | 'faceEdges' | 'edgeFaces' | 'edgeVertices' | 'vertexEdges'

const adjacencyRelations: Array<{ kind: SubshapeRef['kind']; relation: AdjacencyRelation }> = [
  { kind: 'face', relation: 'faceNeighbors' },
  { kind: 'face', relation: 'faceEdges' },
  { kind: 'edge', relation: 'edgeFaces' },
  { kind: 'edge', relation: 'edgeVertices' },
  { kind: 'vertex', relation: 'vertexEdges' },
]

const adjacencyKey = (relation: AdjacencyRelation, persistentId: string) => `${relation}:${persistentId}`

export const scoreAdjacencyCompatibility = (
  previousRef: SubshapeRef,
  currentRef: SubshapeRef,
  previous: TopologyAdjacency,
  current: TopologyAdjacency,
  currentToPrevious: ReadonlyMap<string, string>,
): number => {
  if (previousRef.kind !== currentRef.kind) return 0
  const expected = new Set<string>()
  const observed = new Set<string>()
  for (const relation of adjacencyRelations.filter((candidate) => candidate.kind === previousRef.kind)) {
    const previousIds = previous[relation.relation][previousRef.persistentId] ?? []
    const currentIds = current[relation.relation][currentRef.persistentId] ?? []
    for (const id of previousIds) expected.add(adjacencyKey(relation.relation, id))
    for (const id of currentIds) {
      const mapped = currentToPrevious.get(id)
      if (mapped) observed.add(adjacencyKey(relation.relation, mapped))
    }
  }
  if (expected.size === 0 && observed.size === 0) return 1
  let overlap = 0
  for (const value of observed) if (expected.has(value)) overlap += 1
  return overlap / Math.max(expected.size, observed.size, 1)
}

export const matchSubshapesWithAdjacency = (
  previous: Array<{ ref: SubshapeRef; signature: SubshapeSignature }>,
  current: Array<{ ref: SubshapeRef; signature: SubshapeSignature }>,
  previousAdjacency: TopologyAdjacency | undefined,
  currentAdjacency: TopologyAdjacency | undefined,
  tolerance = 1e-4,
  minimumAdjacencyScore = 0.5,
): TopologyAdjacencyMatch[] => {
  const base = matchSubshapes(previous, current, tolerance)
  if (!previousAdjacency || !currentAdjacency || current.length === 0 || previous.length === 0) return base.map((match) => ({ ...match, adjacencyScore: 1 }))

  // Duplicate analytic signatures are common on boxes, cylinders and
  // symmetric Boolean results. Resolve only when the neighboring persistent
  // signatures form a one-to-one fingerprint; a symmetric graph remains
  // ambiguous and is never guessed by array order.
  const relationForKind: Record<SubshapeRef['kind'], Array<keyof TopologyAdjacency>> = {
    face: ['faceNeighbors', 'faceEdges'],
    edge: ['edgeFaces', 'edgeVertices'],
    vertex: ['vertexEdges'],
  }
  const fingerprint = (
    ref: SubshapeRef,
    adjacency: TopologyAdjacency,
    entries: Array<{ ref: SubshapeRef; signature: SubshapeSignature }>,
  ) => {
    const byId = new Map(entries.map((entry) => [entry.ref.persistentId, entry.signature.hash]))
    return relationForKind[ref.kind].flatMap((relation) => (adjacency[relation][ref.persistentId] ?? []).map((id) => `${relation}:${byId.get(id) ?? id}`).sort()).join('|')
  }
  const resolvedBase = base.map((match) => ({ ...match }))
  const groups = new Map<string, { previous: Array<{ ref: SubshapeRef; signature: SubshapeSignature }>; current: Array<{ ref: SubshapeRef; signature: SubshapeSignature; index: number }> }>()
  for (const entry of previous) {
    const key = `${entry.signature.kind}:${entry.signature.hash}`
    const group = groups.get(key) ?? { previous: [], current: [] }
    group.previous.push(entry); groups.set(key, group)
  }
  for (let index = 0; index < current.length; index += 1) {
    const entry = current[index]
    const key = `${entry.signature.kind}:${entry.signature.hash}`
    const group = groups.get(key)
    if (group) group.current.push({ ...entry, index })
  }
  for (const group of groups.values()) {
    if (group.previous.length < 2 || group.current.length !== group.previous.length) continue
    const previousByFingerprint = new Map<string, typeof group.previous>()
    const currentByFingerprint = new Map<string, typeof group.current>()
    for (const entry of group.previous) {
      const key = fingerprint(entry.ref, previousAdjacency, previous)
      previousByFingerprint.set(key, [...(previousByFingerprint.get(key) ?? []), entry])
    }
    for (const entry of group.current) {
      const key = fingerprint(entry.ref, currentAdjacency, current)
      currentByFingerprint.set(key, [...(currentByFingerprint.get(key) ?? []), entry])
    }
    for (const [key, currentEntries] of currentByFingerprint) {
      const previousEntries = previousByFingerprint.get(key)
      if (!previousEntries || currentEntries.length !== 1 || previousEntries.length !== 1) continue
      const currentEntry = currentEntries[0]
      const previousEntry = previousEntries[0]
      const match = resolvedBase[currentEntry.index]
      if (match?.status !== 'ambiguous') continue
      resolvedBase[currentEntry.index] = {
        current: { ...currentEntry.ref, persistentId: previousEntry.ref.persistentId, status: 'stable', candidates: undefined },
        previousId: previousEntry.ref.persistentId,
        score: Math.max(match.score, 0.9),
        status: 'stable',
      }
    }
  }
  const currentToPrevious = new Map<string, string>()
  for (let index = 0; index < current.length; index += 1) {
    const match = resolvedBase[index]
    if (match?.status === 'stable' && match.previousId) currentToPrevious.set(current[index].ref.persistentId, match.previousId)
  }
  return resolvedBase.map((match, index) => {
    if (index >= current.length || match.status !== 'stable' || !match.previousId) return { ...match, adjacencyScore: 1 }
    const previousRef = previous.find((entry) => entry.ref.persistentId === match.previousId)?.ref
    if (!previousRef) return { ...match, adjacencyScore: 0, status: 'ambiguous' as const, current: { ...match.current, status: 'ambiguous', candidates: [match.previousId] }, previousId: undefined }
    const adjacencyScore = scoreAdjacencyCompatibility(previousRef, current[index].ref, previousAdjacency, currentAdjacency, currentToPrevious)
    if (adjacencyScore < minimumAdjacencyScore) return {
      ...match,
      adjacencyScore,
      status: 'ambiguous' as const,
      current: { ...match.current, status: 'ambiguous', candidates: [match.previousId] },
      previousId: undefined,
      score: Math.min(match.score, adjacencyScore),
    }
    return { ...match, adjacencyScore, score: match.score * 0.75 + adjacencyScore * 0.25 }
  })
}

const signatureScore = (left: SubshapeSignature, right: SubshapeSignature, tolerance: number) => {
  if (left.kind !== right.kind || left.kind === 'vertex') return 0
  const scale = Math.max(Math.sqrt(Math.max(left.area, right.area)), magnitude(left.bounds.max.map((value, axis) => value - left.bounds.min[axis]) as [number, number, number]), magnitude(right.bounds.max.map((value, axis) => value - right.bounds.min[axis]) as [number, number, number]), tolerance)
  const areaDelta = Math.abs(left.area - right.area) / Math.max(left.area, right.area, tolerance * tolerance)
  const leftExtent = left.bounds.max.map((value, axis) => value - left.bounds.min[axis])
  const rightExtent = right.bounds.max.map((value, axis) => value - right.bounds.min[axis])
  const boundsDelta = Math.hypot(...leftExtent.map((value, axis) => value - rightExtent[axis])) / scale
  const normalDelta = Math.hypot(...left.normal.map((value, axis) => value - right.normal[axis]))
  return Math.max(0, 1 - (areaDelta + boundsDelta + normalDelta) / 4)
}

export const matchSubshapes = (previous: Array<{ ref: SubshapeRef; signature: SubshapeSignature }>, current: Array<{ ref: SubshapeRef; signature: SubshapeSignature }>, tolerance = 1e-4): SubshapeMatch[] => {
  const matches: SubshapeMatch[] = []
  const used = new Set<string>()
  const contested = new Set<string>()
  const currentHashCounts = new Map<string, number>()
  for (const entry of current) currentHashCounts.set(`${entry.signature.kind}:${entry.signature.hash}`, (currentHashCounts.get(`${entry.signature.kind}:${entry.signature.hash}`) ?? 0) + 1)
  for (const candidate of current) {
    const hashKey = `${candidate.signature.kind}:${candidate.signature.hash}`
    const exact = previous.filter((entry) => entry.signature.kind === candidate.signature.kind && entry.signature.hash === candidate.signature.hash && !used.has(entry.ref.persistentId))
    if (exact.length === 1 && currentHashCounts.get(hashKey) === 1 && candidate.ref.status !== 'ambiguous') { used.add(exact[0].ref.persistentId); matches.push({ current: { ...candidate.ref, persistentId: exact[0].ref.persistentId, status: 'stable', candidates: undefined }, previousId: exact[0].ref.persistentId, score: 1, status: 'stable' }); continue }
    if (exact.length > 0 && (exact.length > 1 || (currentHashCounts.get(hashKey) ?? 0) > 1 || candidate.ref.status === 'ambiguous')) {
      exact.forEach((entry) => contested.add(entry.ref.persistentId))
      matches.push({ current: { ...candidate.ref, status: 'ambiguous', candidates: [...new Set([...exact.map((entry) => entry.ref.persistentId), ...(candidate.ref.candidates ?? [])])] }, score: 1, status: 'ambiguous' })
      continue
    }
    const scored = previous.map((entry) => ({ entry, score: entry.signature.kind === candidate.signature.kind ? signatureScore(entry.signature, candidate.signature, tolerance) : 0 })).filter((entry) => !used.has(entry.entry.ref.persistentId)).sort((left, right) => right.score - left.score || left.entry.ref.persistentId.localeCompare(right.entry.ref.persistentId))
    const best = scored[0]
    const second = scored[1]
    if (best && best.score > 0.9 && (!second || best.score - second.score > 0.05)) {
      used.add(best.entry.ref.persistentId)
      matches.push({ current: { ...candidate.ref, persistentId: best.entry.ref.persistentId, status: 'stable' }, previousId: best.entry.ref.persistentId, score: best.score, status: 'stable' })
    } else if (best && best.score > 0.5 && second && Math.abs(best.score - second.score) <= 0.05) {
      contested.add(best.entry.ref.persistentId); contested.add(second.entry.ref.persistentId)
      matches.push({ current: { ...candidate.ref, status: 'ambiguous', candidates: [best.entry.ref.persistentId, second.entry.ref.persistentId] }, score: best.score, status: 'ambiguous' })
    } else {
      const status = candidate.ref.status === 'ambiguous' ? 'ambiguous' as const : 'new' as const
      matches.push({ current: { ...candidate.ref, status }, score: best?.score ?? 0, status })
    }
  }
  for (const entry of previous) if (!used.has(entry.ref.persistentId) && !contested.has(entry.ref.persistentId)) matches.push({ current: { ...entry.ref, status: 'deleted' }, previousId: entry.ref.persistentId, score: 0, status: 'deleted' })
  return matches
}
