import { spawnSync } from 'node:child_process'
import { createHash } from 'node:crypto'
import { readFile, writeFile } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'

const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const shardCount = Number(process.env.FREECAD_GUI_COMMAND_SHARD_COUNT || 100)
if (!Number.isInteger(shardCount) || shardCount < 2 || shardCount > 256) throw new Error('FREECAD_GUI_COMMAND_SHARD_COUNT must be an integer from 2 through 256.')
const mergeOnly = process.argv.includes('--merge-only')
const shardArgument = process.argv.find((argument) => argument.startsWith('--shard='))
const requestedShard = shardArgument ? Number(shardArgument.slice('--shard='.length)) : null
if (mergeOnly && shardArgument) throw new Error('--merge-only and --shard cannot be used together.')
if (shardArgument && (!Number.isInteger(requestedShard) || requestedShard < 0 || requestedShard >= shardCount)) throw new Error(`--shard must be an integer from 0 through ${shardCount - 1}.`)

if (!mergeOnly) {
  const shardIndexes = requestedShard == null ? Array.from({ length: shardCount }, (_, index) => index) : [requestedShard]
  for (const index of shardIndexes) {
    console.log(`[freecad-gui-command-shards] execute ${index + 1}/${shardCount}`)
    const execution = spawnSync(process.execPath, ['scripts/run-freecad-reference-probe.mjs'], {
      cwd: root,
      env: {
        ...process.env,
        FREECAD_ORACLE_PROFILE: 'desktop',
        FREECAD_REFERENCE_SCOPE: 'gui-commands',
        FREECAD_GUI_COMMAND_SHARD: `${index}/${shardCount}`,
      },
      stdio: 'inherit',
    })
    if (execution.status !== 0) throw new Error(`FreeCAD GUI command shard ${index}/${shardCount} failed with exit ${execution.status}.`)
  }
}
if (requestedShard != null) {
  console.log(JSON.stringify({ status: 'freecad-gui-command-shard-pass', shard: requestedShard, shardCount }, null, 2))
  process.exit(0)
}

const shardReports = []
for (let index = 0; index < shardCount; index += 1) {
  const relativePath = `.cache/freecad/reference-desktop-gui-commands-${index}-of-${shardCount}.json`
  const bytes = await readFile(resolve(root, relativePath))
  shardReports.push({ relativePath, bytes, report: JSON.parse(bytes) })
}

const first = shardReports[0].report
const universeCount = first.guiCommands?.commandUniverseCount
const universeSha256 = first.guiCommands?.commandUniverseSha256
const commands = []
let expectedStart = 0
for (const [index, { report }] of shardReports.entries()) {
  const gui = report.guiCommands
  if (report.baselineId !== 'freecad-1.1.1' || report.freecadVersion !== '1.1.1' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.probeScope !== 'gui-commands') throw new Error(`GUI command shard ${index} has the wrong baseline.`)
  if (gui?.shard?.index !== index || gui.shard.count !== shardCount || gui.shard.start !== expectedStart || gui.shard.end <= gui.shard.start) throw new Error(`GUI command shard ${index} has a non-contiguous range.`)
  if (gui.commandUniverseCount !== universeCount || gui.commandUniverseSha256 !== universeSha256) throw new Error(`GUI command shard ${index} has a different command universe.`)
  if (gui.stateCommandCount !== gui.commands?.length || gui.stateCommandCount !== gui.shard.end - gui.shard.start) throw new Error(`GUI command shard ${index} has an incomplete state range.`)
  if (JSON.stringify(report.modules) !== JSON.stringify(first.modules) || JSON.stringify(gui.workbenches) !== JSON.stringify(first.guiCommands.workbenches)) throw new Error(`GUI command shard ${index} changed module or workbench registration evidence.`)
  commands.push(...gui.commands)
  expectedStart = gui.shard.end
}
if (expectedStart !== universeCount || commands.length !== universeCount || new Set(commands.map((command) => command.id)).size !== universeCount) throw new Error('Merged GUI command shards do not cover the command universe exactly once.')
const mergedUniverseSha256 = createHash('sha256').update(commands.map((command) => command.id).join('\n')).digest('hex')
if (mergedUniverseSha256 !== universeSha256) throw new Error('Merged GUI command order does not match the declared command universe hash.')

const merged = {
  ...first,
  guiCommands: {
    ...first.guiCommands,
    stateCommandCount: commands.length,
    shard: null,
    commands,
    mergedShards: shardReports.map(({ relativePath, bytes, report }) => ({
      path: relativePath,
      sha256: createHash('sha256').update(bytes).digest('hex'),
      start: report.guiCommands.shard.start,
      end: report.guiCommands.shard.end,
    })),
  },
}
const output = resolve(root, '.cache/freecad/reference-desktop-gui-commands.json')
await writeFile(output, `${JSON.stringify(merged, null, 2)}\n`)
console.log(JSON.stringify({ status: 'freecad-gui-command-shards-merged', shardCount, commandCount: commands.length, commandUniverseSha256: universeSha256, output }, null, 2))
