#!/usr/bin/env node
import { createHash } from 'node:crypto'
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { dirname, relative } from 'node:path'
import { fileURLToPath } from 'node:url'
import { build } from 'esbuild'

const repoRootUrl = new URL('../', import.meta.url)
const repoRoot = fileURLToPath(repoRootUrl)
const requireFromDesktop = createRequire(new URL('apps/desktop/package.json', repoRootUrl))
const semver = requireFromDesktop('semver')
const checkOnly = process.argv.includes('--check')

const desktopPackagePath = new URL('apps/desktop/package.json', repoRootUrl)
const mermaidPackagePath = new URL('apps/desktop/node_modules/mermaid/package.json', repoRootUrl)
const esbuildPackagePath = new URL('node_modules/esbuild/package.json', repoRootUrl)
const sourcePath = 'apps/desktop/node_modules/mermaid/dist/mermaid.esm.min.mjs'
const bundlePath = 'docs/javascripts/vendor/mermaid.min.js'
const manifestPath = 'docs/javascripts/vendor/mermaid-manifest.json'

const desktopPackage = readJson(desktopPackagePath)
const mermaidPackage = readJson(mermaidPackagePath)
const esbuildPackage = readJson(esbuildPackagePath)
const dependencyRange = desktopPackage.dependencies?.mermaid

if (typeof dependencyRange !== 'string' || !semver.satisfies(mermaidPackage.version, dependencyRange)) {
  throw new Error(`apps/desktop/package.json mermaid dependency ${dependencyRange || '<missing>'} does not match resolved mermaid ${mermaidPackage.version}.`)
}

const bundle = await buildBundle()
const manifest = manifestFor(bundle)

if (checkOnly) {
  const existingBundle = readFileSync(new URL(bundlePath, repoRootUrl))
  const existingManifest = readJson(new URL(manifestPath, repoRootUrl))
  assertEqual(`${bundlePath} generated bytes`, existingBundle.toString('utf8'), bundle.toString('utf8'))
  assertEqual(`${manifestPath} generated metadata`, JSON.stringify(existingManifest, null, 2), JSON.stringify(manifest, null, 2))
  process.stdout.write(`[docs-mermaid-vendor] ${bundlePath} matches mermaid ${manifest.packageVersion} (${manifest.sha256})\n`)
} else {
  const bundleUrl = new URL(bundlePath, repoRootUrl)
  const manifestUrl = new URL(manifestPath, repoRootUrl)
  mkdirSync(dirname(fileURLToPath(bundleUrl)), { recursive: true })
  writeFileSync(bundleUrl, bundle)
  writeFileSync(manifestUrl, `${JSON.stringify(manifest, null, 2)}\n`)
  process.stdout.write(`[docs-mermaid-vendor] wrote ${bundlePath} from mermaid ${manifest.packageVersion} (${manifest.sha256})\n`)
}

async function buildBundle() {
  const result = await build({
    absWorkingDir: repoRoot,
    stdin: {
      contents: [
        `import mermaid from './${sourcePath}';`,
        'globalThis.mermaid = mermaid;',
        '',
      ].join('\n'),
      loader: 'js',
      resolveDir: repoRoot,
      sourcefile: 'docs-mermaid-vendor-entry.js',
    },
    bundle: true,
    format: 'iife',
    legalComments: 'eof',
    logLevel: 'silent',
    minify: true,
    outfile: bundlePath,
    platform: 'browser',
    target: ['es2022'],
    write: false,
  })
  const output = result.outputFiles.find((file) => normalizePath(relative(repoRoot, file.path)) === bundlePath)
  if (!output) throw new Error(`esbuild did not produce ${bundlePath}`)
  return Buffer.from(output.contents)
}

function manifestFor(buffer) {
  return {
    package: 'mermaid',
    packageVersion: mermaidPackage.version,
    packageDependency: dependencyRange,
    source: sourcePath,
    bundle: bundlePath,
    generatedBy: 'node scripts/build-docs-mermaid-vendor.mjs',
    bundler: 'esbuild',
    bundlerVersion: esbuildPackage.version,
    bytes: buffer.byteLength,
    sha256: createHash('sha256').update(buffer).digest('hex'),
  }
}

function readJson(path) {
  return JSON.parse(readFileSync(path, 'utf8'))
}

function normalizePath(path) {
  return path.replaceAll('\\', '/')
}

function assertEqual(label, actual, expected) {
  if (actual !== expected) {
    throw new Error(`${label} is stale. Run node scripts/build-docs-mermaid-vendor.mjs to regenerate the docs Mermaid vendor bundle.`)
  }
}
