#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
# Copyright (C) Jarkko Sakkinen 2026
#
# Publish crates.io + npm packages from locally packaged binaries.
# Run `make package` (or PACKAGE_STRICT=1 make package) first.

set -euo pipefail

CARGO="${CARGO:-cargo}"
GLAB="${GLAB:-glab}"
NODE="${NODE:-node}"
NPM="${NPM:-npm}"

export NODE_OPTIONS="${NODE_OPTIONS:+$NODE_OPTIONS }--no-deprecation"

platforms=(darwin-arm64 linux-arm64 linux-x64 win32-x64)
lock_files=(
  package-lock.json
  packages/pi-goosedump/package-lock.json
)
license_files=(LICENSE-APACHE-2.0 LICENSE-LGPL-2.1)

cleanup() {
  local status=$?

  if [[ -n "${workdir:-}" ]]; then
    rm -rf "$workdir"
  fi
  return "$status"
}

trap cleanup EXIT

die() {
  printf '%s\n' "$1" >&2
  exit 1
}

require_command() {
  command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}

npm_package_exists() {
  local error_file="$workdir/npm-view-error"
  local package_name="$1"
  local package_version

  if package_version="$($NPM view "$package_name@$version" version --json 2>"$error_file")"; then
    package_version="$(
      $NODE -e '
        const result = JSON.parse(process.argv[1]);
        const versions = Array.isArray(result) ? result : [result];
        if (versions.length !== 1 || typeof versions[0] !== "string") process.exit(1);
        process.stdout.write(versions[0]);
      ' "$package_version"
    )" || die "npm returned invalid version data for $package_name@$version"
    [[ "$package_version" == "$version" ]] \
      || die "npm returned version $package_version for $package_name@$version"
    return 0
  fi
  if grep -q 'E404' "$error_file"; then
    return 1
  fi
  cat "$error_file" >&2
  die "cannot query $package_name@$version from npm"
}

publish_npm_package() {
  local package_dir="$1"
  local package_name

  package_name="$($NODE -p "require('$package_dir/package.json').name")"
  if npm_package_exists "$package_name"; then
    printf '%s\n' "$package_name@$version is already published"
    return
  fi
  $NPM publish "$package_dir" --access public
}

wait_for_npm_package() {
  local package_name="$1"
  local attempt

  for attempt in {1..12}; do
    if npm_package_exists "$package_name"; then
      return
    fi
    sleep 5
  done
  die "$package_name@$version did not become available from npm"
}

publish_cargo_package() {
  local output

  if output="$($CARGO info --registry crates-io "goosedump@$version" 2>&1)"; then
    printf '%s\n' "goosedump@$version is already published"
    return
  fi
  if [[ "$output" != *"could not find \`goosedump@$version\`"* ]]; then
    printf '%s\n' "$output" >&2
    die "cannot query goosedump@$version from crates.io"
  fi
  $CARGO publish
}

platform_binary() {
  local platform="$1"
  if [[ "$platform" == win32-x64 ]]; then
    printf 'goosedump.exe\n'
  else
    printf 'goosedump\n'
  fi
}

for command in "$CARGO" "$NODE" "$NPM" tar; do
  require_command "$command"
done

repo_root="$(git rev-parse --show-toplevel 2>/dev/null)" \
  || die "not inside a Git repository"
cd "$repo_root"

[[ -z "$(git status --porcelain)" ]] || die "working directory is not clean"

version="${1:-$($NODE -p 'require("./package.json").version')}"
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "invalid version: $version"
package_version="$($NODE -p 'require("./package.json").version')"
[[ "$version" == "$package_version" ]] \
  || die "requested version $version does not match package.json $package_version"

tag_commit="$(git rev-parse "$version^{commit}" 2>/dev/null)" \
  || die "tag $version does not exist"
[[ "$tag_commit" == "$(git rev-parse HEAD)" ]] \
  || die "tag $version does not point to HEAD"

workdir="$(mktemp -d)"
mkdir -p "$workdir/packages" "$workdir/release"

printf '%s\n' "assembling platform packages from local npm/*/bin binaries"
missing=()
for platform in "${platforms[@]}"; do
  binary="$(platform_binary "$platform")"
  source_bin="npm/$platform/bin/$binary"
  if [[ ! -f "$source_bin" ]]; then
    missing+=("$source_bin")
    continue
  fi

  package_dir="$workdir/packages/$platform"
  cp -a "npm/$platform" "$package_dir"
  mkdir -p "$package_dir/bin"
  cp "$source_bin" "$package_dir/bin/$binary"
  chmod 755 "$package_dir/bin/$binary" 2>/dev/null || true
  for license_file in "${license_files[@]}"; do
    [[ -f "$license_file" ]] || die "missing $license_file"
    cp "$license_file" "$package_dir/$license_file"
  done

  tar -C "$package_dir/bin" -czf \
    "$workdir/release/goosedump-$version-$platform.tar.gz" "$binary"
done

if ((${#missing[@]} > 0)); then
  printf 'missing platform binaries:\n' >&2
  printf '  %s\n' "${missing[@]}" >&2
  die "run 'PACKAGE_STRICT=1 make package' before publish"
fi

publish_cargo_package
for platform in "${platforms[@]}"; do
  publish_npm_package "$workdir/packages/$platform"
done
publish_npm_package "$repo_root"
publish_npm_package "$repo_root/packages/pi-goosedump"

for package_name in \
  @jarkkojs/goosedump \
  @jarkkojs/goosedump-darwin-arm64 \
  @jarkkojs/goosedump-linux-arm64 \
  @jarkkojs/goosedump-linux-x64 \
  @jarkkojs/goosedump-win32-x64; do
  wait_for_npm_package "$package_name"
done

if command -v "$GLAB" >/dev/null 2>&1; then
  if $GLAB release view "$version" >/dev/null 2>&1; then
    $GLAB release upload "$version" "$workdir"/release/*.tar.gz --clobber
  else
    $GLAB release create "$version" "$workdir"/release/*.tar.gz \
      --name "goosedump $version" \
      --notes "goosedump $version"
  fi
else
  printf '%s\n' "glab not found; skipping GitLab release upload"
  printf '%s\n' "release tarballs are in $workdir/release (copied below)"
  mkdir -p artifacts/release
  cp "$workdir"/release/*.tar.gz artifacts/release/
fi

NPM="$NPM" "$NODE" scripts/update-npm-integrity.mjs "$version"
git add -- "${lock_files[@]}"
if ! git diff --cached --quiet; then
  git commit -s -m "chore: Update package-lock.json files"
fi

printf '%s\n' "published goosedump $version"
printf '%s\n' "push the integrity commit if one was created"
