#!/usr/bin/env node

import { execFileSync } from 'node:child_process'

function fail(message) {
  console.error(`release_components=FAIL ${message}`)
  process.exit(1)
}

const base = process.argv[2] || ''
const head = process.argv[3] || 'HEAD'

if (!/^[0-9a-f]{40}$/.test(base)) {
  fail('base must be a full lowercase Git SHA')
}

function git(args) {
  try {
    return execFileSync('git', args, {
      encoding: 'utf8',
      stdio: ['ignore', 'pipe', 'pipe'],
    }).trim()
  } catch (error) {
    const stderr = error.stderr?.toString().trim()
    fail(stderr ? `git_error=${JSON.stringify(stderr)}` : 'git command failed')
  }
}

const resolvedBase = git(['rev-parse', '--verify', `${base}^{commit}`])
const resolvedHead = git(['rev-parse', '--verify', `${head}^{commit}`])
if (resolvedBase !== base) fail('base did not resolve exactly')
if (!/^[0-9a-f]{40}$/.test(resolvedHead)) fail('head did not resolve to a commit')

try {
  execFileSync('git', ['merge-base', '--is-ancestor', base, resolvedHead], {
    stdio: 'ignore',
  })
} catch {
  fail('base is not an ancestor of head')
}

const changed = git([
  'diff',
  '--name-only',
  '--no-renames',
  `${base}..${resolvedHead}`,
]).split('\n').filter(Boolean)

const apiPatterns = [
  /^apps\/api\//,
  /^go\.work(?:\.sum)?$/,
]
const webPatterns = [
  /^apps\/web\//,
  /^packages\/contracts\//,
  /^pnpm-lock\.yaml$/,
  /^pnpm-workspace\.yaml$/,
  /^nx\.json$/,
  /^package\.json$/,
  /^docker\/nginx\.conf$/,
]
const sharedRuntimePatterns = [
  /^\.dockerignore$/,
  /^docker-compose(?:\.[^.]+)?\.ya?ml$/,
]

function dockerfileSections(ref) {
  let contents
  try {
    contents = execFileSync('git', ['show', `${ref}:Dockerfile`], {
      encoding: 'utf8',
      stdio: ['ignore', 'pipe', 'pipe'],
    })
  } catch {
    return null
  }
  const sections = new Map([['global', []]])
  let section = 'global'
  for (const line of contents.split('\n')) {
    const match = line.match(/^FROM\s+.+?\s+AS\s+([A-Za-z0-9._-]+)(?:\s|$)/i)
    if (match) section = match[1].toLowerCase()
    if (!sections.has(section)) sections.set(section, [])
    sections.get(section).push(line)
  }
  return sections
}

function sectionChanged(before, after, name) {
  return (before.get(name) || []).join('\n') !== (after.get(name) || []).join('\n')
}

function classifyDockerfile() {
  const before = dockerfileSections(base)
  const after = dockerfileSections(resolvedHead)
  if (!before || !after) return { apiChanged: true, webChanged: true }
  const knownApi = new Set(['api-builder', 'api'])
  const knownWeb = new Set(['web-builder', 'web'])
  let apiChanged = false
  let webChanged = false

  const beforeGlobal = before.get('global') || []
  const afterGlobal = after.get('global') || []
  const globalLines = (lines, pattern) => lines.filter((line) => pattern.test(line)).join('\n')
  const apiGlobal = /\b(?:GO_VERSION|GO_BUILD_IMAGE|API_RUNTIME_IMAGE)\b/
  const webGlobal = /\b(?:NODE_VERSION|NODE_BUILD_IMAGE|WEB_RUNTIME_IMAGE)\b/
  if (globalLines(beforeGlobal, apiGlobal) !== globalLines(afterGlobal, apiGlobal)) apiChanged = true
  if (globalLines(beforeGlobal, webGlobal) !== globalLines(afterGlobal, webGlobal)) webChanged = true
  const sharedGlobal = (lines) => lines.filter((line) => !apiGlobal.test(line) && !webGlobal.test(line)).join('\n')
  if (sharedGlobal(beforeGlobal) !== sharedGlobal(afterGlobal)) {
    apiChanged = true
    webChanged = true
  }

  const sectionNames = new Set([...before.keys(), ...after.keys()])
  sectionNames.delete('global')
  for (const name of sectionNames) {
    if (!sectionChanged(before, after, name)) continue
    if (knownApi.has(name)) apiChanged = true
    else if (knownWeb.has(name)) webChanged = true
    else {
      apiChanged = true
      webChanged = true
    }
  }
  return { apiChanged, webChanged }
}

let api = false
let web = false
let migrationsChanged = false

if (changed.includes('Dockerfile')) {
  const dockerfile = classifyDockerfile()
  api = dockerfile.apiChanged
  web = dockerfile.webChanged
}

for (const file of changed) {
  if (file === 'Dockerfile') continue
  if (/^apps\/api\/migrations\//.test(file)) migrationsChanged = true
  if (sharedRuntimePatterns.some((pattern) => pattern.test(file))) {
    api = true
    web = true
    continue
  }
  if (apiPatterns.some((pattern) => pattern.test(file))) api = true
  if (webPatterns.some((pattern) => pattern.test(file))) web = true
}

const components = api && web ? 'all' : api ? 'api' : web ? 'web' : 'none'
console.log(JSON.stringify({
  base,
  head: resolvedHead,
  components,
  migrationsChanged,
  changedFiles: changed,
}))
