import type { NativeOcctHistoryOperation, NativeOcctHistoryResponse, NativeOcctHistoryStepProvider, NativeOcctMultiTransformStep } from './nativeHistoryProvider'
import { captureFreeCadPrivateNamingEvidence, createFreeCadPrivateNamingAbiRequest, probeFreeCadPrivateNamingAbi } from './nativeNamingAbi'
import type { NativeStageNamingEvidence } from './nativeNamingEvidence'

export const NATIVE_OCCT_HISTORY_PROTOCOL_VERSION = 1 as const

export type NativeNamingAbiCapabilities = {
  mappedNameRef: 'available' | 'optional' | 'unavailable'
  stringHasher: 'available' | 'opaque-preserved' | 'unavailable'
  elementMap2: 'available' | 'opaque-preserved' | 'unavailable'
  tokenGeneration: 'native-only' | 'forbidden'
  abiVersion?: number
  freecadVersion?: string
  sourceCommit?: string
  operations?: NativeOcctHistoryOperation[]
  reason?: string
}

export const NATIVE_OCCT_NAMING_ABI_UNAVAILABLE: NativeNamingAbiCapabilities = Object.freeze({
  mappedNameRef: 'unavailable',
  stringHasher: 'unavailable',
  elementMap2: 'unavailable',
  tokenGeneration: 'forbidden',
})

export type NativeOcctHistoryCapabilities = {
  providerId: string
  providerVersion: string
  occtVersion: string
  availability: 'available' | 'unavailable'
  operations: NativeOcctHistoryOperation[]
  transport: 'step-text'
  /** Private FreeCAD naming callbacks are intentionally never synthesized by this bridge. */
  naming?: NativeNamingAbiCapabilities
  reason?: string
}

export const nativeNamingAbiCapabilities = (capabilities: NativeOcctHistoryCapabilities): NativeNamingAbiCapabilities => ({
  ...NATIVE_OCCT_NAMING_ABI_UNAVAILABLE,
  ...(capabilities.naming ?? {}),
  ...(capabilities.naming?.operations ? { operations: [...capabilities.naming.operations] } : {}),
})

export const nativeNamingCapabilitiesForModule = (module: NativeOcctHistoryStepProvider): NativeNamingAbiCapabilities => {
  const probe = probeFreeCadPrivateNamingAbi(module)
  if (probe.availability !== 'available' || !probe.descriptor) return { ...NATIVE_OCCT_NAMING_ABI_UNAVAILABLE, ...(probe.reason ? { reason: probe.reason } : {}), ...(probe.abiVersion === null ? {} : { abiVersion: probe.abiVersion }) }
  return {
    mappedNameRef: 'available',
    stringHasher: 'available',
    elementMap2: 'available',
    tokenGeneration: 'native-only',
    abiVersion: probe.abiVersion ?? undefined,
    freecadVersion: probe.descriptor.freecadVersion,
    sourceCommit: probe.descriptor.sourceCommit,
    operations: [...probe.descriptor.operations],
  }
}

export type NativeOcctHistoryRequest = {
  protocolVersion: typeof NATIVE_OCCT_HISTORY_PROTOCOL_VERSION
  requestId: string
  documentId: string
  documentVersion: number
  operationId: string
  /** Document object produced by this native stage; distinct from the request operation ID. */
  resultObjectId?: string
  operation: NativeOcctHistoryOperation
  transformKind?: 'linear' | 'polar' | 'mirrored'
  transforms?: NativeOcctMultiTransformStep[]
  objectStep: string
  toolStep?: string
  /** Explicit ordered inputs for multi-input and staged operations. */
  inputs?: NativeOcctHistoryInputTransport[]
  stages?: NativeOcctHistoryStageTransport[]
  resultStepByStage?: Record<string, string>
  resultObjectTag?: number
  direction?: [number, number, number]
  axisOrigin?: [number, number, number]
  angle?: number
  radius?: number
  distance?: number
  depth?: number
  position?: [number, number, number]
  neutralPlaneDirection?: [number, number, number]
  faceIndex?: number
  reversed?: boolean
  ruled?: boolean
  offset?: number
  joinType?: 'Arc' | 'Intersection'
}

export type NativeOcctHistoryInputTransport = {
  inputId: string
  /** Stable document object identity represented by this transport input. */
  objectId?: string
  role?: string
  stageId?: string
  step: string
  objectTag?: number
  namingEvidence?: NativeStageNamingEvidence
}

export type NativeOcctHistoryStageTransport = {
  stageId: string
  operation?: NativeOcctHistoryOperation
  inputIds: string[]
  resultStageId?: string
  ordinal: number
}

export type NativeOcctHistoryProtocolResponse = {
  protocolVersion: typeof NATIVE_OCCT_HISTORY_PROTOCOL_VERSION
  requestId: string
  documentId: string
  documentVersion: number
  operationId: string
  provider: NativeOcctHistoryCapabilities
  history: NativeOcctHistoryResponse
}

export interface NativeOcctHistoryProvider {
  capabilities(): NativeOcctHistoryCapabilities
  capture(request: NativeOcctHistoryRequest, signal: AbortSignal): Promise<NativeOcctHistoryProtocolResponse>
}

export class NativeOcctHistoryUnavailableError extends Error {
  readonly code = 'NATIVE_OCCT_HISTORY_UNAVAILABLE'

  constructor(readonly provider: NativeOcctHistoryCapabilities) {
    super(provider.reason || `Native OCCT history provider '${provider.providerId}' is unavailable.`)
    this.name = 'NativeOcctHistoryUnavailableError'
  }
}

const abortError = () => new DOMException('Native OCCT history request cancelled.', 'AbortError')
const booleanOperations: readonly NativeOcctHistoryOperation[] = ['fuse', 'cut', 'common']

const assertRequest = (request: NativeOcctHistoryRequest) => {
  if (request.protocolVersion !== NATIVE_OCCT_HISTORY_PROTOCOL_VERSION) throw new RangeError(`Unsupported native OCCT history protocol version: ${request.protocolVersion}.`)
  if (request.resultObjectId !== undefined && !request.resultObjectId.trim()) throw new TypeError('Native OCCT history resultObjectId must be non-empty when provided.')
  if (!request.requestId.trim() || !request.documentId.trim() || !request.operationId.trim()) throw new TypeError('Native OCCT history request IDs must be non-empty strings.')
  if (!Number.isSafeInteger(request.documentVersion) || request.documentVersion < 0) throw new RangeError('Native OCCT history documentVersion must be a non-negative integer.')
  if (!['fuse', 'cut', 'common', 'rotate', 'pad', 'pocket', 'loft', 'pipe', 'revolution', 'groove', 'fillet', 'chamfer', 'hole', 'draft', 'thickness', 'linear-pattern', 'polar-pattern', 'mirrored', 'multi-transform'].includes(request.operation)) throw new RangeError(`Unsupported native OCCT history operation: ${String(request.operation)}.`)
  if (!request.objectStep.startsWith('ISO-10303-21;')) throw new TypeError('Native OCCT history transport requires ISO-10303-21 STEP text.')
  if (request.inputs) {
    if (request.inputs.length === 0 || request.inputs.length > 64) throw new RangeError('Native OCCT history inputs must contain between one and 64 entries.')
    const ids = new Set<string>()
    for (const input of request.inputs) {
      if (!input.inputId.trim() || ids.has(input.inputId)) throw new TypeError('Native OCCT history input IDs must be unique and non-empty.')
      ids.add(input.inputId)
      if (!input.step.startsWith('ISO-10303-21;')) throw new TypeError(`Native OCCT history input '${input.inputId}' requires STEP text.`)
      if (input.stageId !== undefined && !input.stageId.trim()) throw new TypeError('Native OCCT history input stageId must be non-empty.')
    }
  }
  if (request.stages) {
    if (request.stages.length === 0 || request.stages.length > 64) throw new RangeError('Native OCCT history stages must contain between one and 64 entries.')
    const ids = new Set<string>()
    const inputIds = new Set((request.inputs ?? []).map((input) => input.inputId))
    for (const stage of request.stages) {
      if (!stage.stageId.trim() || ids.has(stage.stageId)) throw new TypeError('Native OCCT history stage IDs must be unique and non-empty.')
      ids.add(stage.stageId)
      if (!Number.isSafeInteger(stage.ordinal) || stage.ordinal < 0) throw new RangeError('Native OCCT history stage ordinal must be a non-negative integer.')
      if (stage.inputIds.length === 0) throw new TypeError(`Native OCCT history stage '${stage.stageId}' requires at least one input.`)
      if (request.inputs && stage.inputIds.some((id) => !inputIds.has(id))) throw new TypeError(`Native OCCT history stage '${stage.stageId}' references an unknown input.`)
      if (stage.resultStageId !== undefined && !stage.resultStageId.trim()) throw new TypeError('Native OCCT history resultStageId must be non-empty.')
    }
  }
  if (request.resultStepByStage) {
    for (const [stageId, step] of Object.entries(request.resultStepByStage)) {
      if (!request.stages?.some((stage) => stage.stageId === stageId)) throw new TypeError(`Native OCCT result step references unknown stage '${stageId}'.`)
      if (!step.startsWith('ISO-10303-21;')) throw new TypeError(`Native OCCT result step for '${stageId}' requires STEP text.`)
    }
  }
  if (request.resultObjectTag !== undefined && (!Number.isSafeInteger(request.resultObjectTag) || request.resultObjectTag <= 0)) throw new RangeError('Native OCCT history resultObjectTag must be a positive safe integer.')
  for (const input of request.inputs ?? []) {
    if (input.objectId !== undefined && !input.objectId.trim()) throw new TypeError(`Native OCCT history input ${input.inputId} objectId must be non-empty when provided.`)
    if (input.objectTag !== undefined && (!Number.isSafeInteger(input.objectTag) || input.objectTag <= 0)) throw new RangeError(`Native OCCT history input ${input.inputId} objectTag must be a positive safe integer.`)
  }
  if (booleanOperations.includes(request.operation)) {
    if (!request.toolStep?.startsWith('ISO-10303-21;')) throw new TypeError('Native Boolean history transport requires object and tool STEP text.')
  } else if (request.operation === 'pocket') {
    if (!request.toolStep?.startsWith('ISO-10303-21;')) throw new TypeError('Native Pocket history transport requires base and profile STEP text.')
    if (!request.direction || request.direction.some((value) => !Number.isFinite(value)) || Math.hypot(...request.direction) <= 0) throw new TypeError('Native Pocket history requires a finite non-zero direction vector.')
  } else if (request.operation === 'loft') {
    if (!request.toolStep?.startsWith('ISO-10303-21;')) throw new TypeError('Native Loft history transport requires two section STEP texts.')
    if (request.ruled !== undefined && typeof request.ruled !== 'boolean') throw new TypeError('Native Loft history ruled flag must be boolean.')
  } else if (request.operation === 'pipe') {
    if (!request.toolStep?.startsWith('ISO-10303-21;')) throw new TypeError('Native Pipe history transport requires profile and spine STEP texts.')
  } else if (request.operation === 'rotate') {
    const angle = request.angle
    if (!request.axisOrigin || request.axisOrigin.some((value) => !Number.isFinite(value)) || !request.direction || request.direction.some((value) => !Number.isFinite(value)) || Math.hypot(...request.direction) <= 0 || typeof angle !== 'number' || !Number.isFinite(angle) || angle === 0 || Math.abs(angle) > 360) throw new TypeError('Native Rotate history requires a finite axis and a non-zero angle within 360 degrees.')
  } else if (request.operation === 'revolution') {
    const angle = request.angle
    if (!request.axisOrigin || request.axisOrigin.some((value) => !Number.isFinite(value)) || !request.direction || request.direction.some((value) => !Number.isFinite(value)) || Math.hypot(...request.direction) <= 0 || typeof angle !== 'number' || !Number.isFinite(angle) || angle <= 0 || angle > 360) throw new TypeError('Native Revolution history requires a finite axis and an angle in (0, 360].')
  } else if (request.operation === 'groove') {
    const angle = request.angle
    if (!request.toolStep?.startsWith('ISO-10303-21;')) throw new TypeError('Native Groove history transport requires base and profile STEP text.')
    if (!request.axisOrigin || request.axisOrigin.some((value) => !Number.isFinite(value)) || !request.direction || request.direction.some((value) => !Number.isFinite(value)) || Math.hypot(...request.direction) <= 0 || typeof angle !== 'number' || !Number.isFinite(angle) || angle <= 0 || angle > 360) throw new TypeError('Native Groove history requires a finite axis and an angle in (0, 360].')
  } else if (request.operation === 'fillet') {
    if (typeof request.radius !== 'number' || !Number.isFinite(request.radius) || request.radius <= 0) throw new TypeError('Native Fillet history requires a finite positive radius.')
  } else if (request.operation === 'chamfer') {
    if (typeof request.distance !== 'number' || !Number.isFinite(request.distance) || request.distance <= 0) throw new TypeError('Native Chamfer history requires a finite positive distance.')
  } else if (request.operation === 'hole') {
    if (typeof request.radius !== 'number' || !Number.isFinite(request.radius) || request.radius <= 0 || typeof request.depth !== 'number' || !Number.isFinite(request.depth) || request.depth <= 0 || !request.position || request.position.some((value) => !Number.isFinite(value)) || !request.direction || request.direction.some((value) => !Number.isFinite(value)) || Math.hypot(...request.direction) <= 0) throw new TypeError('Native Hole history requires finite positive radius/depth, position and direction.')
  } else if (request.operation === 'draft') {
    if (typeof request.faceIndex !== 'number' || !Number.isSafeInteger(request.faceIndex) || request.faceIndex < 0 || typeof request.angle !== 'number' || !Number.isFinite(request.angle) || request.angle === 0 || request.angle <= -89.999 || request.angle >= 89.999 || !request.direction || request.direction.some((value) => !Number.isFinite(value)) || Math.hypot(...request.direction) <= 0 || !request.axisOrigin || request.axisOrigin.some((value) => !Number.isFinite(value)) || !request.neutralPlaneDirection || request.neutralPlaneDirection.some((value) => !Number.isFinite(value)) || Math.hypot(...request.neutralPlaneDirection) <= 0) throw new TypeError('Native Draft history requires one face index, finite angle, direction and neutral plane.')
  } else if (request.operation === 'thickness') {
    if (typeof request.faceIndex !== 'number' || !Number.isSafeInteger(request.faceIndex) || request.faceIndex < 0 || typeof request.offset !== 'number' || !Number.isFinite(request.offset) || request.offset === 0 || (request.joinType !== undefined && request.joinType !== 'Arc' && request.joinType !== 'Intersection')) throw new TypeError('Native Thickness history requires one face index, finite non-zero offset and a supported join type.')
  } else if (request.operation === 'linear-pattern') {
    if (!request.direction || request.direction.some((value) => !Number.isFinite(value)) || Math.hypot(...request.direction) <= 0) throw new TypeError('Native LinearPattern history requires a finite non-zero translation vector.')
  } else if (request.operation === 'polar-pattern') {
    if (!request.axisOrigin || request.axisOrigin.some((value) => !Number.isFinite(value)) || !request.direction || request.direction.some((value) => !Number.isFinite(value)) || Math.hypot(...request.direction) <= 0 || typeof request.angle !== 'number' || !Number.isFinite(request.angle) || request.angle === 0 || Math.abs(request.angle) > 360) throw new TypeError('Native PolarPattern history requires a finite axis and a non-zero angle within 360 degrees.')
  } else if (request.operation === 'mirrored') {
    if (!request.axisOrigin || request.axisOrigin.some((value) => !Number.isFinite(value)) || !request.direction || request.direction.some((value) => !Number.isFinite(value)) || Math.hypot(...request.direction) <= 0) throw new TypeError('Native Mirrored history requires a finite mirror plane.')
  } else if (request.operation === 'multi-transform') {
    if (request.transforms) {
      if (request.transforms.length < 2 || request.transforms.length > 6) throw new TypeError('Native ordered MultiTransform history requires between two and six steps.')
      for (const step of request.transforms) {
        if (step.type === 'linear') {
          if (!step.direction || step.direction.some((value) => !Number.isFinite(value)) || Math.hypot(...step.direction) <= 0) throw new TypeError('Native MultiTransform linear history requires a finite non-zero translation vector.')
        } else if (step.type === 'polar') {
          if (!step.axisOrigin || step.axisOrigin.some((value) => !Number.isFinite(value)) || !step.direction || step.direction.some((value) => !Number.isFinite(value)) || Math.hypot(...step.direction) <= 0 || typeof step.angle !== 'number' || !Number.isFinite(step.angle) || step.angle === 0 || Math.abs(step.angle) > 360) throw new TypeError('Native MultiTransform polar history requires a finite axis and a non-zero angle within 360 degrees.')
        } else if (step.type === 'mirrored') {
          if (!step.axisOrigin || step.axisOrigin.some((value) => !Number.isFinite(value)) || !step.direction || step.direction.some((value) => !Number.isFinite(value)) || Math.hypot(...step.direction) <= 0) throw new TypeError('Native MultiTransform mirrored history requires a finite mirror plane.')
        } else throw new TypeError('Native MultiTransform history contains an unsupported transform step.')
      }
    } else if (request.transformKind === 'linear') {
      if (!request.direction || request.direction.some((value) => !Number.isFinite(value)) || Math.hypot(...request.direction) <= 0) throw new TypeError('Native MultiTransform linear history requires a finite non-zero translation vector.')
    } else if (request.transformKind === 'polar') {
      if (!request.axisOrigin || request.axisOrigin.some((value) => !Number.isFinite(value)) || !request.direction || request.direction.some((value) => !Number.isFinite(value)) || Math.hypot(...request.direction) <= 0 || typeof request.angle !== 'number' || !Number.isFinite(request.angle) || request.angle === 0 || Math.abs(request.angle) > 360) throw new TypeError('Native MultiTransform polar history requires a finite axis and a non-zero angle within 360 degrees.')
    } else if (request.transformKind === 'mirrored') {
      if (!request.axisOrigin || request.axisOrigin.some((value) => !Number.isFinite(value)) || !request.direction || request.direction.some((value) => !Number.isFinite(value)) || Math.hypot(...request.direction) <= 0) throw new TypeError('Native MultiTransform mirrored history requires a finite mirror plane.')
    } else throw new TypeError('Native MultiTransform history requires one supported transform step.')
  } else if (!request.direction || request.direction.some((value) => !Number.isFinite(value)) || Math.hypot(...request.direction) <= 0) {
    throw new TypeError('Native Pad history requires a finite non-zero direction vector.')
  }
}

const assertResponse = (request: NativeOcctHistoryRequest, response: NativeOcctHistoryProtocolResponse) => {
  if (response.protocolVersion !== request.protocolVersion || response.requestId !== request.requestId || response.documentId !== request.documentId || response.documentVersion !== request.documentVersion || response.operationId !== request.operationId) throw new Error('Native OCCT history response does not match its request context.')
  if (response.history.provider !== 'occt-native' || response.history.occtVersion !== response.provider.occtVersion) throw new Error('Native OCCT history response provider metadata is inconsistent.')
  return response
}

const attachStageMetadata = (request: NativeOcctHistoryRequest, history: NativeOcctHistoryResponse): NativeOcctHistoryResponse => {
  if (!request.stages?.length) return history
  const orderedStages = [...request.stages].sort((left, right) => left.ordinal - right.ordinal)
  const resultStageId = orderedStages[orderedStages.length - 1].stageId
  const records = history.records.map((record) => {
    const sourceInput = request.inputs?.find((input) => input.inputId === record.sourceId || input.inputId === record.source || input.role === record.source)
    return {
      ...record,
      ...(record.sourceStageId || sourceInput?.stageId ? { sourceStageId: record.sourceStageId ?? sourceInput?.stageId } : {}),
      resultStageId: record.resultStageId ?? resultStageId,
    }
  })
  return {
    ...history,
    records,
    stages: orderedStages.map((stage) => ({
      stageId: stage.stageId,
      operation: stage.operation,
      inputIds: [...stage.inputIds],
      resultStageId: stage.resultStageId,
      ordinal: stage.ordinal,
    })),
  }
}

export class DirectNativeOcctHistoryProvider implements NativeOcctHistoryProvider {
  constructor(private readonly module: NativeOcctHistoryStepProvider, private readonly providerVersion = '8.0.0-embind') {}

  capabilities(): NativeOcctHistoryCapabilities {
    const occtVersion = this.module.occtVersion()
    return {
      providerId: 'occt-native.history-step',
      providerVersion: this.providerVersion,
      occtVersion,
      availability: 'available',
      operations: ['fuse', 'cut', 'common', 'rotate', 'pad', 'pocket', 'loft', 'pipe', 'revolution', 'groove', 'fillet', 'chamfer', 'hole', 'draft', 'thickness', 'linear-pattern', 'polar-pattern', 'mirrored', 'multi-transform'],
      transport: 'step-text',
      naming: nativeNamingCapabilitiesForModule(this.module),
    }
  }

  async capture(request: NativeOcctHistoryRequest, signal: AbortSignal): Promise<NativeOcctHistoryProtocolResponse> {
    assertRequest(request)
    if (signal.aborted) throw abortError()
    const provider = this.capabilities()
    if (!provider.operations.includes(request.operation)) throw new RangeError(`Native OCCT history provider does not declare operation '${request.operation}'.`)
    const history = await Promise.resolve().then(() => {
      if (request.operation === 'pad') {
        if (!this.module.prismHistoryFromStep || !request.direction) throw new NativeOcctHistoryUnavailableError(provider)
        return this.module.prismHistoryFromStep(request.objectStep, ...request.direction)
      }
      if (request.operation === 'pocket') {
        if (!this.module.pocketHistoryFromStep || !request.direction || !request.toolStep) throw new NativeOcctHistoryUnavailableError(provider)
        return this.module.pocketHistoryFromStep(request.objectStep, request.toolStep, ...request.direction)
      }
      if (request.operation === 'loft') {
        if (!this.module.loftHistoryFromStep || !request.toolStep) throw new NativeOcctHistoryUnavailableError(provider)
        return this.module.loftHistoryFromStep(request.objectStep, request.toolStep, request.ruled === true)
      }
      if (request.operation === 'pipe') {
        if (!this.module.pipeHistoryFromStep || !request.toolStep) throw new NativeOcctHistoryUnavailableError(provider)
        return this.module.pipeHistoryFromStep(request.objectStep, request.toolStep)
      }
      if (request.operation === 'rotate') {
        if (!this.module.rotateHistoryFromStep || !request.axisOrigin || !request.direction || request.angle === undefined) throw new NativeOcctHistoryUnavailableError(provider)
        return this.module.rotateHistoryFromStep(request.objectStep, ...request.axisOrigin, ...request.direction, request.angle)
      }
      if (request.operation === 'revolution') {
        if (!this.module.revolutionHistoryFromStep || !request.axisOrigin || !request.direction || request.angle === undefined) throw new NativeOcctHistoryUnavailableError(provider)
        return this.module.revolutionHistoryFromStep(request.objectStep, ...request.axisOrigin, ...request.direction, request.angle)
      }
      if (request.operation === 'groove') {
        if (!this.module.grooveHistoryFromStep || !request.toolStep || !request.axisOrigin || !request.direction || request.angle === undefined) throw new NativeOcctHistoryUnavailableError(provider)
        return this.module.grooveHistoryFromStep(request.objectStep, request.toolStep, ...request.axisOrigin, ...request.direction, request.angle)
      }
      if (request.operation === 'fillet') {
        if (!this.module.filletHistoryFromStep || request.radius === undefined) throw new NativeOcctHistoryUnavailableError(provider)
        return this.module.filletHistoryFromStep(request.objectStep, request.radius)
      }
      if (request.operation === 'chamfer') {
        if (!this.module.chamferHistoryFromStep || request.distance === undefined) throw new NativeOcctHistoryUnavailableError(provider)
        return this.module.chamferHistoryFromStep(request.objectStep, request.distance)
      }
      if (request.operation === 'hole') {
        if (!this.module.holeHistoryFromStep || request.radius === undefined || request.depth === undefined || !request.position || !request.direction) throw new NativeOcctHistoryUnavailableError(provider)
        return this.module.holeHistoryFromStep(request.objectStep, request.radius, request.depth, ...request.position, ...request.direction)
      }
      if (request.operation === 'draft') {
        if (!this.module.draftHistoryFromStep || request.faceIndex === undefined || request.angle === undefined || !request.direction || !request.axisOrigin || !request.neutralPlaneDirection) throw new NativeOcctHistoryUnavailableError(provider)
        return this.module.draftHistoryFromStep(request.objectStep, request.faceIndex, request.angle, ...request.direction, ...request.axisOrigin, ...request.neutralPlaneDirection, request.reversed === true)
      }
      if (request.operation === 'thickness') {
        if (!this.module.thicknessHistoryFromStep || request.faceIndex === undefined || request.offset === undefined) throw new NativeOcctHistoryUnavailableError(provider)
        return this.module.thicknessHistoryFromStep(request.objectStep, request.faceIndex, request.offset, request.joinType === 'Intersection')
      }
      if (request.operation === 'linear-pattern') {
        if (!this.module.linearPatternHistoryFromStep || !request.direction) throw new NativeOcctHistoryUnavailableError(provider)
        return this.module.linearPatternHistoryFromStep(request.objectStep, ...request.direction)
      }
      if (request.operation === 'polar-pattern') {
        if (!this.module.polarPatternHistoryFromStep || !request.axisOrigin || !request.direction || request.angle === undefined) throw new NativeOcctHistoryUnavailableError(provider)
        return this.module.polarPatternHistoryFromStep(request.objectStep, ...request.axisOrigin, ...request.direction, request.angle)
      }
      if (request.operation === 'mirrored') {
        if (!this.module.mirroredHistoryFromStep || !request.axisOrigin || !request.direction) throw new NativeOcctHistoryUnavailableError(provider)
        return this.module.mirroredHistoryFromStep(request.objectStep, ...request.axisOrigin, ...request.direction)
      }
      if (request.operation === 'multi-transform') {
        if (request.transforms && request.transforms.length >= 2) {
          if (!this.module.multiTransformHistoryFromStep) throw new NativeOcctHistoryUnavailableError(provider)
          return this.module.multiTransformHistoryFromStep(request.objectStep, request.transforms)
        }
        if (request.transformKind === 'linear' && this.module.linearPatternHistoryFromStep && request.direction) return this.module.linearPatternHistoryFromStep(request.objectStep, ...request.direction)
        if (request.transformKind === 'polar' && this.module.polarPatternHistoryFromStep && request.axisOrigin && request.direction && request.angle !== undefined) return this.module.polarPatternHistoryFromStep(request.objectStep, ...request.axisOrigin, ...request.direction, request.angle)
        if (request.transformKind === 'mirrored' && this.module.mirroredHistoryFromStep && request.axisOrigin && request.direction) return this.module.mirroredHistoryFromStep(request.objectStep, ...request.axisOrigin, ...request.direction)
        throw new NativeOcctHistoryUnavailableError(provider)
      }
      return this.module.booleanHistoryFromStep(request.objectStep, request.toolStep!, request.operation)
    })
    if (signal.aborted) throw abortError()
    const stagedHistory = attachStageMetadata(request, history)
    const orderedStages = [...(request.stages ?? [])].sort((left, right) => left.ordinal - right.ordinal)
    const finalStage = orderedStages.at(-1)
    const stageId = finalStage?.resultStageId ?? finalStage?.stageId ?? `${request.operationId}:stage:0`
    const namingEvidence = captureFreeCadPrivateNamingEvidence(this.module, createFreeCadPrivateNamingAbiRequest({
      requestId: request.requestId,
      documentId: request.documentId,
      documentVersion: request.documentVersion,
      operationId: request.operationId,
      operation: request.operation,
      stageId,
      resultObjectId: request.resultObjectId ?? request.operationId,
      ...(request.resultObjectTag === undefined ? {} : { resultObjectTag: request.resultObjectTag }),
      inputs: (request.inputs ?? [{ inputId: 'object', role: 'object', step: request.objectStep }, ...(request.toolStep ? [{ inputId: 'tool', role: 'tool', step: request.toolStep }] : [])]).map(({ inputId, objectId, role, stageId: inputStageId, step, objectTag, namingEvidence }) => ({ inputId, step, ...(objectId ? { objectId } : {}), ...(role ? { role } : {}), ...(inputStageId ? { stageId: inputStageId } : {}), ...(objectTag === undefined ? {} : { objectTag }), ...(namingEvidence ? { namingEvidence } : {}) })),
      stages: orderedStages.map((stage) => ({ ...stage, inputIds: [...stage.inputIds] })),
      ...(stagedHistory.resultStep ? { resultStep: stagedHistory.resultStep } : {}),
      ...(stagedHistory.resultBrep ? { resultBrep: stagedHistory.resultBrep } : {}),
      ...(request.resultStepByStage ? { resultStepByStage: { ...request.resultStepByStage } } : {}),
      history: stagedHistory,
    }))
    const enrichedHistory = namingEvidence ? { ...stagedHistory, namingEvidence } : stagedHistory
    return assertResponse(request, { protocolVersion: request.protocolVersion, requestId: request.requestId, documentId: request.documentId, documentVersion: request.documentVersion, operationId: request.operationId, provider, history: enrichedHistory })
  }
}

export class UnavailableNativeOcctHistoryProvider implements NativeOcctHistoryProvider {
  constructor(private readonly message = 'No native OCCT history WASM artifact is installed.') {}

  capabilities(): NativeOcctHistoryCapabilities {
    return { providerId: 'occt-native.history-step', providerVersion: 'unavailable', occtVersion: 'unknown', availability: 'unavailable', operations: [], transport: 'step-text', reason: this.message }
  }

  capture(): Promise<NativeOcctHistoryProtocolResponse> {
    return Promise.reject(new NativeOcctHistoryUnavailableError(this.capabilities()))
  }
}

export type NativeOcctHistoryExecution = {
  status: 'completed' | 'cancelled' | 'stale' | 'timed-out'
  requestId: string
  response?: NativeOcctHistoryProtocolResponse
}

export class NativeOcctHistoryCoordinator {
  private active: { requestId: string; controller: AbortController } | null = null
  private sequence = 0

  constructor(private readonly currentDocumentVersion: (documentId: string) => number | null, private readonly timeoutMs = 120_000) {}

  cancel() { this.active?.controller.abort() }

  async capture(provider: NativeOcctHistoryProvider, input: Omit<NativeOcctHistoryRequest, 'protocolVersion' | 'requestId'>): Promise<NativeOcctHistoryExecution> {
    this.active?.controller.abort()
    const requestId = `native-history-${++this.sequence}`
    const controller = new AbortController()
    this.active = { requestId, controller }
    const request: NativeOcctHistoryRequest = { ...input, protocolVersion: NATIVE_OCCT_HISTORY_PROTOCOL_VERSION, requestId }
    let timeout: ReturnType<typeof setTimeout> | undefined
    try {
      const timeoutPromise = new Promise<never>((_, reject) => { timeout = setTimeout(() => { controller.abort(); reject(new DOMException('Native OCCT history request timed out.', 'TimeoutError')) }, this.timeoutMs) })
      const response = await Promise.race([provider.capture(request, controller.signal), timeoutPromise])
      if (controller.signal.aborted || this.active?.requestId !== requestId) return { status: 'cancelled', requestId }
      if (this.currentDocumentVersion(input.documentId) !== input.documentVersion) return { status: 'stale', requestId }
      return { status: 'completed', requestId, response }
    } catch (error) {
      if (error instanceof Error && error.name === 'TimeoutError') return { status: 'timed-out', requestId }
      if (controller.signal.aborted || (error instanceof Error && error.name === 'AbortError')) return { status: 'cancelled', requestId }
      throw error
    } finally {
      if (timeout) clearTimeout(timeout)
      if (this.active?.requestId === requestId) this.active = null
    }
  }
}
