import { createMockFacade } from './facade/mockFacade'
import type { BitBybitWebCadFacade, DocumentObjectSnapshot, MultiTransformValue, RecomputeResult } from './facade/types'

type TransformEvidence = {
  command: string
  objectId: string
  typeId: string
  base: unknown
  tip: unknown
  recompute: RecomputeResult['status']
  shapeCount: number
  kernelReferenceCount: number
  shapeId: string
  volume: number
  solids: number
  structuralValid: boolean
  transformMode?: unknown
  originals?: unknown
  threaded?: unknown
  modelThread?: unknown
  threadType?: unknown
  threadSize?: unknown
  threadDirection?: unknown
}

type TransformReport = {
  schemaVersion: 1
  status: 'pass' | 'failed'
  browserId: 'chrome'
  crossOriginIsolated: boolean
  operations: TransformEvidence[]
  topologyMigration?: { editedPadLength: number; migrated: Array<{ command: string; objectId: string; kind: string; status: string; persistentId: string }>; stable: number; ambiguous: number; ambiguityDiagnostics: number; wrongBindings: number }
  ambiguityRecovery?: { command: string; status: string; code: string; retainedShapeId: string; previousShapeId: string; restoredStatus: string; restoredShapeId: string }
  persistence?: { mode: string; savedVersion: number; reopenedVersion: number; reopenedTip: unknown; objectCount: number }
  opfs?: { markerSuite: string; markerOperations: number; markerRemoved: boolean }
  beforeRelease?: { shapeCount: number; kernelReferenceCount: number }
  afterRelease?: { shapeCount: number; kernelReferenceCount: number }
  error?: string
}

const rectangle = [
  { id: 'transform-edge-1', type: 'line' as const, start: { x: 0, y: 0 }, end: { x: 4, y: 0 } },
  { id: 'transform-edge-2', type: 'line' as const, start: { x: 4, y: 0 }, end: { x: 4, y: 3 } },
  { id: 'transform-edge-3', type: 'line' as const, start: { x: 4, y: 3 }, end: { x: 0, y: 3 } },
  { id: 'transform-edge-4', type: 'line' as const, start: { x: 0, y: 3 }, end: { x: 0, y: 0 } },
]

const property = (facade: BitBybitWebCadFacade, objectId: string, name: string): unknown => facade.app.document.getObject(objectId)?.properties.find((entry) => entry.name === name)?.value ?? null

const objectIds = (facade: BitBybitWebCadFacade) => new Set(facade.app.document.getActive().objects.map((object) => object.id))

const latestObject = (facade: BitBybitWebCadFacade, previous: Set<string>): DocumentObjectSnapshot => {
  const object = facade.app.document.getActive().objects.find((candidate) => !previous.has(candidate.id))
  if (!object) throw new Error('Transform task did not append a document object.')
  return object
}

const selectSubshape = (facade: BitBybitWebCadFacade, objectId: string, kind: 'edge' | 'face', index = 0) => {
  const object = facade.app.document.getObject(objectId)
  const available = object?.topology?.entries.filter((entry) => entry.ref.kind === kind && entry.ref.status !== 'deleted') ?? []
  const stable = available.filter((entry) => entry.ref.status === 'stable')
  const fresh = available.filter((entry) => entry.ref.status === 'new')
  const candidates = stable.length > 0 ? stable : fresh.length > 0 ? fresh : available
  const selected = candidates[index]
  if (!selected || selected.ref.status === 'ambiguous' || selected.ref.status === 'deleted') throw new Error(`${objectId} does not expose an unambiguous selectable ${kind} ${index}: ${JSON.stringify(available.map((entry) => ({ persistentId: entry.ref.persistentId, status: entry.ref.status, candidates: entry.ref.candidates })))}`)
  facade.selection.selectSubshape({ objectId, kind, persistentId: selected.ref.persistentId })
  return selected.ref
}

const selectOperableFace = async (facade: BitBybitWebCadFacade, objectId: string, operation: 'draft' | 'thickness') => {
  const object = facade.app.document.getObject(objectId)
  const shape = facade.geometry.getObjectShape(objectId)
  const faces = object?.topology?.entries.filter((entry) => entry.ref.kind === 'face') ?? []
  if (!shape || faces.length === 0) throw new Error(`${objectId} has no Shape or face topology for ${operation}.`)
  const failures: string[] = []
  for (const [index, entry] of faces.entries()) {
    if (entry.ref.status === 'ambiguous' || entry.ref.status === 'deleted') continue
    try {
      const candidate = operation === 'draft'
        ? await facade.geometry.draft({ documentId: shape.documentId, documentVersion: shape.documentVersion, base: shape, angle: 5, direction: [0, 0, 1], neutralPlaneOrigin: [0, 0, 0], neutralPlaneDirection: [0, 0, 1], indexes: [index] })
        : await facade.geometry.thickness({ documentId: shape.documentId, documentVersion: shape.documentVersion, base: shape, offset: -0.4, removeFaceIndexes: [index], joinType: 'Arc' })
      try {
        const mass = await facade.geometry.massProperties(candidate)
        if (!(mass.volume > 0)) throw new Error('candidate volume is not positive')
      } finally {
        await facade.geometry.release(candidate)
      }
      facade.selection.selectSubshape({ objectId, kind: 'face', persistentId: entry.ref.persistentId })
      return entry.ref
    } catch (error) {
      failures.push(`${index}:${error instanceof Error ? error.message : String(error)}`)
    }
  }
  throw new Error(`${objectId} has no operable ${operation} face: ${failures.join(' | ')}`)
}

const applyFeature = async (facade: BitBybitWebCadFacade, command: string, draft: Record<string, unknown>): Promise<TransformEvidence> => {
  const previous = objectIds(facade)
  const selected = facade.selection.getObjectId()
  if (!selected) throw new Error(`${command} requires a selected source object.`)
  if (facade.gui.command.getState(command).status !== 'enabled') throw new Error(`${command} is not enabled for ${selected}.`)
  facade.gui.command.execute({ commandId: command })
  const task = facade.task.getActive()
  if (!task || task.commandId !== command || task.status !== 'preview') throw new Error(`${command} did not open a preview task.`)
  facade.task.update(draft)
  facade.task.apply()
  const object = latestObject(facade, previous)
  const recompute = await facade.app.document.recomputeAsync({ dirtyObjectIds: [object.id] })
  if (recompute.status !== 'completed') throw new Error(`${command} recompute failed: ${JSON.stringify(recompute.errors)}`)
  const tip = property(facade, 'body', 'Tip')
  const shapeCount = facade.geometry.capabilities().shapeCount
  const kernelReferenceCount = facade.geometry.capabilities().kernelReferenceCount
  const shape = facade.geometry.getObjectShape(object.id)
  if (tip !== object.id) throw new Error(`${command} did not redirect Body.Tip to ${object.id}; got ${String(tip)}.`)
  if (!shape || shapeCount < 1 || kernelReferenceCount < 1) throw new Error(`${command} did not retain a recomputed ShapeHandle.`)
  const [mass, quality] = await Promise.all([facade.geometry.massProperties(shape), facade.geometry.qualityReport(shape)])
  if (!(mass.volume > 0)) throw new Error(`${command} did not produce a positive-volume Shape.`)
  if (quality.isNull || quality.solids !== 1) throw new Error(`${command} violated the PartDesign single-solid rule: ${JSON.stringify(quality)}`)
  return {
    command,
    objectId: object.id,
    typeId: object.typeId,
    base: property(facade, object.id, 'Base'),
    tip,
    recompute: recompute.status,
    shapeCount,
    kernelReferenceCount,
    shapeId: shape.id,
    volume: mass.volume,
    solids: quality.solids,
    structuralValid: quality.structuralValid,
    transformMode: property(facade, object.id, 'TransformMode') ?? undefined,
    originals: property(facade, object.id, 'Originals') ?? undefined,
    threaded: property(facade, object.id, 'Threaded') ?? undefined,
    modelThread: property(facade, object.id, 'ModelThread') ?? undefined,
    threadType: property(facade, object.id, 'ThreadType') ?? undefined,
    threadSize: property(facade, object.id, 'ThreadSize') ?? undefined,
    threadDirection: property(facade, object.id, 'ThreadDirection') ?? undefined,
  }
}

const run = async (): Promise<TransformReport> => {
  const facade = createMockFacade()
  const report: TransformReport = { schemaVersion: 1, status: 'failed', browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true, operations: [] }
  try {
    if (!report.crossOriginIsolated || !navigator.storage?.getDirectory) throw new Error('Chrome PartDesign transform evidence requires isolated OPFS.')
    const initialized = await facade.geometry.initialize()
    if (initialized.status !== 'ready') throw new Error(initialized.reason || 'Bitbybit geometry runtime is unavailable.')
    for (const edge of rectangle) facade.app.sketcher.addGeometry('sketch', edge)
    facade.app.document.setProperty({ objectId: 'pocket', propertyName: 'Suppressed', value: true })
    facade.app.document.setProperty({ objectId: 'fillet', propertyName: 'Suppressed', value: true })
    const initialRecompute = await facade.app.document.recomputeAsync()
    if (initialRecompute.status !== 'completed') throw new Error(`Initial Pad recompute failed: ${JSON.stringify(initialRecompute.errors)}`)

    facade.selection.select('pad')
    const asymmetricBase = await applyFeature(facade, 'hole', { base: 'pad', diameter: 1, depth: 8, type: 'Dimension', position: { x: 1.3, y: 0.8, z: 0 }, direction: { x: 0, y: 0, z: 1 }, holeCutType: 'None' })
    report.operations.push(asymmetricBase)
    selectSubshape(facade, asymmetricBase.objectId, 'edge', 0)
    report.operations.push(await applyFeature(facade, 'fillet', { radius: 0.4 }))
    selectSubshape(facade, asymmetricBase.objectId, 'edge', 1)
    report.operations.push(await applyFeature(facade, 'chamfer', { size: 0.3, chamferType: 'Equal distance' }))
    await selectOperableFace(facade, asymmetricBase.objectId, 'draft')
    report.operations.push(await applyFeature(facade, 'draft', { angle: 5, direction: { x: 0, y: 0, z: 1 }, neutralPlaneOrigin: { x: 0, y: 0, z: 0 }, neutralPlaneDirection: { x: 0, y: 0, z: 1 } }))
    await selectOperableFace(facade, asymmetricBase.objectId, 'thickness')
    report.operations.push(await applyFeature(facade, 'thickness', { value: 0.4, reversed: true, join: 'Arc' }))

    const fillet = report.operations[1]
    const originalFilletBase = property(facade, fillet.objectId, 'Base') as { persistentId: string; objectId: string; kind: string; status: string }
    const retainedFilletShape = facade.geometry.getObjectShape(fillet.objectId)
    facade.app.document.setProperty({ objectId: fillet.objectId, propertyName: 'Base', value: { ...originalFilletBase, status: 'ambiguous', candidates: [originalFilletBase.persistentId, 'ambiguous-edge-candidate'] } })
    const ambiguous = await facade.app.document.recomputeAsync({ dirtyObjectIds: [fillet.objectId] })
    const ambiguityDiagnostic = facade.diagnostics.list().find((entry) => entry.objectId === fillet.objectId && entry.code === 'DRESSUP_EDGE_REFERENCE_UNRESOLVED')
    const shapeDuringAmbiguity = facade.geometry.getObjectShape(fillet.objectId)
    if (ambiguous.status !== 'failed' || !ambiguityDiagnostic || !retainedFilletShape || shapeDuringAmbiguity?.id !== retainedFilletShape.id) throw new Error('Fillet ambiguity did not retain the last valid Shape and report a stable diagnostic.')
    facade.app.document.setProperty({ objectId: fillet.objectId, propertyName: 'Base', value: originalFilletBase })
    const restoredFillet = await facade.app.document.recomputeAsync({ dirtyObjectIds: [fillet.objectId] })
    const restoredFilletShape = facade.geometry.getObjectShape(fillet.objectId)
    if (restoredFillet.status !== 'completed' || !restoredFilletShape) throw new Error('Fillet did not recover after resolving the ambiguous edge.')
    report.ambiguityRecovery = { command: 'fillet', status: ambiguous.status, code: ambiguityDiagnostic.code, retainedShapeId: shapeDuringAmbiguity.id, previousShapeId: retainedFilletShape.id, restoredStatus: restoredFillet.status, restoredShapeId: restoredFilletShape.id }

    facade.app.document.setProperty({ objectId: 'pad', propertyName: 'Length', value: 31 })
    const migratedRecompute = await facade.app.document.recomputeAsync()
    if (migratedRecompute.status !== 'completed') throw new Error(`Dress-up topology migration recompute failed: ${JSON.stringify(migratedRecompute.errors)}`)
    const selectionProperties = [
      { operation: report.operations[1], propertyName: 'Base' },
      { operation: report.operations[2], propertyName: 'Base' },
      { operation: report.operations[3], propertyName: 'Base' },
      { operation: report.operations[4], propertyName: 'RemoveFaces' },
    ]
    const migrated = selectionProperties.map(({ operation, propertyName }) => {
      const ref = property(facade, operation.objectId, propertyName) as { objectId?: string; kind?: string; status?: string; persistentId?: string }
      return { command: operation.command, objectId: String(ref.objectId), kind: String(ref.kind), status: String(ref.status), persistentId: String(ref.persistentId) }
    })
    const stable = migrated.filter((entry) => entry.status === 'stable').length
    const ambiguousCount = migrated.filter((entry) => entry.status === 'ambiguous').length
    const topologyDiagnostics = facade.diagnostics.list().filter((entry) => entry.code === 'TOPOLOGY_REFERENCE_AMBIGUOUS' && migrated.some((candidate) => candidate.objectId === asymmetricBase.objectId && candidate.status === 'ambiguous' && candidate.command && entry.objectId === report.operations.find((operation) => operation.command === candidate.command)?.objectId))
    const wrongBindings = migrated.filter((entry) => entry.objectId !== asymmetricBase.objectId || !['stable', 'ambiguous'].includes(entry.status) || !entry.persistentId).length
    if (wrongBindings !== 0 || stable + ambiguousCount !== migrated.length || topologyDiagnostics.length !== ambiguousCount) throw new Error(`Dress-up topology migration produced unsafe bindings or incomplete diagnostics: ${JSON.stringify({ migrated, topologyDiagnostics })}`)
    report.topologyMigration = { editedPadLength: Number(property(facade, 'pad', 'Length')), migrated, stable, ambiguous: ambiguousCount, ambiguityDiagnostics: topologyDiagnostics.length, wrongBindings }

    facade.selection.select(asymmetricBase.objectId)
    const cosmeticThread = await applyFeature(facade, 'hole', { base: asymmetricBase.objectId, diameter: 0.75, depth: 8, type: 'Dimension', position: { x: 0.8, y: 0.8, z: 0 }, direction: { x: 0, y: 0, z: 1 }, threaded: true, modelThread: false, threadType: 'ISOMetricProfile', threadSize: 'M1x0.25', threadDiameter: 1, threadPitch: 0.25, threadDirection: 'Right' })
    report.operations.push(cosmeticThread)
    facade.selection.select(cosmeticThread.objectId)
    report.operations.push(await applyFeature(facade, 'hole', { base: cosmeticThread.objectId, diameter: 1.6, depth: 8, type: 'Dimension', position: { x: 3.1, y: 2.2, z: 0 }, direction: { x: 0, y: 0, z: 1 }, threaded: true, modelThread: true, threadType: 'ISOMetricProfile', threadSize: 'M2x0.4', threadDiameter: 2, threadPitch: 0.4, threadDirection: 'Left', threadDepthType: 'Dimension', threadDepth: 1.2 }))
    facade.selection.select(cosmeticThread.objectId)
    report.operations.push(await applyFeature(facade, 'linear-pattern', { base: cosmeticThread.objectId, transformMode: 'Features', originals: [asymmetricBase.objectId], occurrences: 2, length: 1, offset: 1, direction: 'Horizontal', mode: 'Extent' }))
    facade.selection.select(report.operations.at(-1)!.objectId)
    report.operations.push(await applyFeature(facade, 'mirrored', { base: report.operations.at(-1)!.objectId, transformMode: 'Features', originals: [asymmetricBase.objectId], plane: 'YZ plane', planeOrigin: { x: 2, y: 0, z: 0 }, planeNormal: { x: 1, y: 0, z: 0 }, fuse: true }))
    facade.selection.select(report.operations.at(-1)!.objectId)
    report.operations.push(await applyFeature(facade, 'mirrored', { base: report.operations.at(-1)!.objectId, plane: 'YZ plane', fuse: false }))
    facade.selection.select(report.operations.at(-1)!.objectId)
    report.operations.push(await applyFeature(facade, 'linear-pattern', { base: report.operations.at(-1)!.objectId, occurrences: 3, length: 2, offset: 2, direction: 'Horizontal', mode: 'Extent', reversed: false }))
    facade.selection.select(report.operations.at(-1)!.objectId)
    report.operations.push(await applyFeature(facade, 'polar-pattern', { base: report.operations.at(-1)!.objectId, occurrences: 3, angle: 270, axis: 'Normal', mode: 'Extent', reversed: false }))
    facade.selection.select(report.operations.at(-1)!.objectId)
    const transformations: MultiTransformValue = { steps: [{ id: 'linear-1', type: 'linear', occurrences: 2, length: 1, direction: 'Horizontal' }, { id: 'polar-1', type: 'polar', occurrences: 2, angle: 180, axis: 'Normal' }] }
    const multi = await applyFeature(facade, 'multi-transform', { base: report.operations.at(-1)!.objectId, transformations })
    report.operations.push({ ...multi, base: property(facade, multi.objectId, 'Base') })
    facade.selection.select(report.operations.at(-1)!.objectId)
    report.operations.push(await applyFeature(facade, 'hole', { base: report.operations.at(-1)!.objectId, diameter: 2, depth: 8, type: 'Dimension', position: { x: 2, y: 1.5, z: 0 }, direction: { x: 0, y: 0, z: 1 }, holeCutType: 'Counterbore', holeCutDiameter: 3.5, holeCutDepth: 1.5 }))

    const transformed = facade.app.document.getActive()
    const linear = report.operations.find((entry) => entry.command === 'linear-pattern' && entry.transformMode === 'Whole shape')
    const polar = report.operations.find((entry) => entry.command === 'polar-pattern')
    const multiObject = report.operations.find((entry) => entry.command === 'multi-transform')
    const hole = report.operations.find((entry) => entry.command === 'hole' && entry.modelThread === true)
    if (!linear || property(facade, linear.objectId, 'Occurrences') !== 3 || property(facade, linear.objectId, 'Direction') !== 'Horizontal') throw new Error('Linear Pattern parameter contract was not persisted.')
    if (!polar || property(facade, polar.objectId, 'Occurrences') !== 3 || property(facade, polar.objectId, 'Angle') !== 270) throw new Error('Polar Pattern parameter contract was not persisted.')
    if (!multiObject || !property(facade, multiObject.objectId, 'Transformations') || JSON.stringify(property(facade, multiObject.objectId, 'Transformations')) !== JSON.stringify(transformations)) throw new Error('MultiTransform ordered step contract was not persisted.')
    const featureTransforms = report.operations.filter((entry) => entry.transformMode === 'Features')
    const threadedHoles = report.operations.filter((entry) => entry.command === 'hole' && entry.threaded === true)
    if (featureTransforms.length !== 2 || featureTransforms.some((entry) => JSON.stringify(entry.originals) !== JSON.stringify([asymmetricBase.objectId]))) throw new Error('Feature-list transform contracts were not persisted.')
    if (threadedHoles.length !== 2 || threadedHoles[0].modelThread !== false || threadedHoles[1].modelThread !== true || threadedHoles[1].threadDirection !== 'Left') throw new Error('Hole thread standard/model contracts were not persisted.')
    if (!hole || property(facade, hole.objectId, 'ThreadType') !== 'ISOMetricProfile' || property(facade, hole.objectId, 'ThreadSize') !== 'M2x0.4') throw new Error('Hole thread parameter contract was not persisted.')
    const saved = await facade.project.save(transformed)
    const loaded = await facade.project.load(transformed.id)
    if (!loaded) throw new Error('PartDesign transform document did not load from OPFS.')
    await facade.app.document.load(transformed.id)
    const reopened = facade.app.document.getActive()
    const reopenedTip = property(facade, 'body', 'Tip')
    if (reopened.objects.length !== transformed.objects.length || reopenedTip !== report.operations.at(-1)?.objectId) throw new Error('PartDesign transform OPFS round-trip changed object count or Body.Tip.')
    const directory = await navigator.storage.getDirectory()
    const markerName = 'partdesign-transform-chrome-marker.json'
    const marker = await directory.getFileHandle(markerName, { create: true })
    const writable = await marker.createWritable()
    await writable.write(JSON.stringify({ suite: 'PD-TRANSFORM', operations: report.operations.length }))
    await writable.close()
    const markerPayload = JSON.parse(await (await marker.getFile()).text()) as { suite: string; operations: number }
    await directory.removeEntry(markerName)
    let markerRemoved = false
    try { await directory.getFileHandle(markerName) } catch (error) { markerRemoved = error instanceof DOMException && error.name === 'NotFoundError' }
    report.persistence = { mode: facade.project.capabilities().mode, savedVersion: saved.documentVersion, reopenedVersion: reopened.version, reopenedTip, objectCount: reopened.objects.length }
    report.opfs = { markerSuite: markerPayload.suite, markerOperations: markerPayload.operations, markerRemoved }
    report.beforeRelease = { shapeCount: facade.geometry.capabilities().shapeCount, kernelReferenceCount: facade.geometry.capabilities().kernelReferenceCount }
    report.status = 'pass'
  } catch (error) {
    report.error = error instanceof Error ? error.stack || error.message : String(error)
  } finally {
    facade.geometry.dispose()
    await new Promise((resolve) => setTimeout(resolve, 0))
    report.afterRelease = { shapeCount: facade.geometry.capabilities().shapeCount, kernelReferenceCount: facade.geometry.capabilities().kernelReferenceCount }
    if (report.afterRelease.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) { report.status = 'failed'; report.error = `${report.error ? `${report.error} ` : ''}Shape ownership gate failed: ${JSON.stringify(report.afterRelease)}` }
  }
  return report
}

run().then((report) => {
  ;(window as Window & { __bitbybitPartdesignTransformReport?: TransformReport }).__bitbybitPartdesignTransformReport = report
  document.documentElement.dataset.status = report.status
  document.querySelector('#result')!.textContent = JSON.stringify(report, null, 2)
}).catch((error) => {
  document.documentElement.dataset.status = 'failed'
  document.querySelector('#result')!.textContent = error instanceof Error ? error.stack || error.message : String(error)
})

export {}
