import { createAnalyticSubshapeRefs, matchSubshapesWithAdjacency, type AnalyticFaceInput } from './topologyNaming'
import type { TopologyAdjacency } from './types'

export type TopologyMutationReplayOptions = {
  models?: number
  mutationsPerModel?: number
  seed?: number
}

export type TopologyMutationReplayReport = {
  seed: number
  models: number
  mutationsPerModel: number
  cases: number
  stable: number
  ambiguous: number
  new: number
  deleted: number
  wrongBindings: number
  mutationCounts: Record<'rigid' | 'parameter' | 'split' | 'delete' | 'shuffle', number>
}

const emptyAdjacency = (): TopologyAdjacency => ({ faceNeighbors: {}, faceEdges: {}, edgeFaces: {}, edgeVertices: {}, vertexEdges: {} })

const nextRandom = (state: number) => {
  let value = state >>> 0
  value ^= value << 13
  value ^= value >>> 17
  value ^= value << 5
  return value >>> 0
}

const baseFaces = (model: number): AnalyticFaceInput[] => Array.from({ length: 6 }, (_, index) => ({
  surfaceType: index === 0 ? 'Cylinder' : 'Plane',
  area: 100 + model * 0.01 + index * 17,
  adjacentFaceCount: index === 0 ? 5 : 1,
  edgeCount: index === 0 ? 4 : 4,
  wireCount: 1,
}))

const entries = (shapeId: string, version: number, faces: AnalyticFaceInput[]) => {
  const topology = createAnalyticSubshapeRefs(shapeId, version, faces, [], [])
  return { entries: topology.faces.refs.map((ref, index) => ({ ref, signature: topology.faces.signatures[index] })), adjacency: emptyAdjacency() }
}

export const runTopologyMutationReplay = (options: TopologyMutationReplayOptions = {}): TopologyMutationReplayReport => {
  const models = options.models ?? 100
  const mutationsPerModel = options.mutationsPerModel ?? 10
  const seed = options.seed ?? 0x5eed1234
  if (!Number.isSafeInteger(models) || models < 1 || models > 1000) throw new RangeError('models must be between 1 and 1000.')
  if (!Number.isSafeInteger(mutationsPerModel) || mutationsPerModel < 1 || mutationsPerModel > 100) throw new RangeError('mutationsPerModel must be between 1 and 100.')
  const report: TopologyMutationReplayReport = { seed, models, mutationsPerModel, cases: models * mutationsPerModel, stable: 0, ambiguous: 0, new: 0, deleted: 0, wrongBindings: 0, mutationCounts: { rigid: 0, parameter: 0, split: 0, delete: 0, shuffle: 0 } }
  let random = seed >>> 0
  const mutationNames: Array<keyof TopologyMutationReplayReport['mutationCounts']> = ['rigid', 'parameter', 'split', 'delete', 'shuffle']
  for (let model = 0; model < models; model += 1) {
    const previous = entries(`replay-previous-${model}`, model, baseFaces(model))
    for (let mutation = 0; mutation < mutationsPerModel; mutation += 1) {
      random = nextRandom(random)
      const mutationName = mutationNames[(random + mutation + model) % mutationNames.length]
      report.mutationCounts[mutationName] += 1
      const faces = baseFaces(model).map((face) => ({ ...face }))
      const target = (random >>> 8) % faces.length
      if (mutationName === 'parameter') faces[target].area *= 2
      if (mutationName === 'split') faces.splice(target, 0, { ...faces[target] })
      if (mutationName === 'delete') faces.splice(target, 1)
      if (mutationName === 'shuffle') faces.reverse()
      const current = entries(`replay-current-${model}-${mutation}`, model + mutation + 1, faces)
      const matches = matchSubshapesWithAdjacency(previous.entries, current.entries, previous.adjacency, current.adjacency)
      for (const match of matches) report[match.status] += 1
      for (let index = 0; index < current.entries.length; index += 1) {
        const match = matches[index]
        if (!match || match.status !== 'stable' || !match.previousId) continue
        const previousEntry = previous.entries.find((entry) => entry.ref.persistentId === match.previousId)
        const currentEntry = current.entries[index]
        if (!previousEntry || previousEntry.signature.hash !== currentEntry.signature.hash) report.wrongBindings += 1
      }
    }
  }
  return report
}
