#!/usr/bin/env bash
set -euo pipefail

# Pre-commit hook: keeps dist/ in sync with src/.
#
# Why: vendor/qmd/dist/ is tracked in git. Consumer machines fetch the
# repo and use dist/ AS-IS without rebuilding (cli/build.sh and the fleet
# pull_and_build_and_deploy.sh both consume the pre-built JS). A commit
# that updates src/*.ts without committing the matching dist/ will ship a
# CLI that crashes with MODULE_NOT_FOUND on any new import path.
# Reference: i-9l2ueyyz (post-mortem of i-qkarfffa Stage-3 dist/embedding/
# catch-up bug).
#
# Behaviour:
#   1. If no staged file matches ^src/.*\.ts$ -> exit 0 (fast path for
#      docs/config-only commits).
#   2. Otherwise run `npm run build` (which runs tsc + chmod the CLI shim).
#   3. Then `git diff --quiet -- dist/` -- if dist/ changed as a result of
#      the rebuild AND those changes are not staged, FAIL LOUDLY with a
#      message naming the drifted files and the recovery command.
#
# Bypass: `git commit --no-verify` (standard git escape hatch).

# --- collect staged src/*.ts paths -----------------------------------------
mapfile -t STAGED_SRC < <(git diff --cached --name-only --diff-filter=ACMR | grep -E '^src/.*\.ts$' || true)

if [[ "${#STAGED_SRC[@]}" -eq 0 ]]; then
  # Nothing under src/ in this commit -- no rebuild required.
  exit 0
fi

echo "pre-commit: ${#STAGED_SRC[@]} staged src/*.ts file(s) detected -- rebuilding dist/" >&2

# --- snapshot dist/ before rebuild so we can detect rebuild-induced drift --
# Use SHA-256 of git ls-tree-style content (working tree) for every dist/ file
# we care about. Cheap enough for ~75 files.
DIST_SHA_BEFORE=$(find dist -type f \( -name '*.js' -o -name '*.d.ts' -o -name '*.js.map' -o -name '*.d.ts.map' \) -print0 \
  | sort -z \
  | xargs -0 sha256sum \
  | sha256sum \
  | awk '{print $1}')

# --- run build -- this also gates on tsc errors ----------------------------
if ! npm run --silent build >/tmp/qmd-pre-commit-build.log 2>&1; then
  echo "" >&2
  echo "pre-commit: npm run build FAILED" >&2
  echo "" >&2
  echo "Build output (last 40 lines):" >&2
  tail -n 40 /tmp/qmd-pre-commit-build.log >&2
  echo "" >&2
  echo "Full log: /tmp/qmd-pre-commit-build.log" >&2
  exit 1
fi

DIST_SHA_AFTER=$(find dist -type f \( -name '*.js' -o -name '*.d.ts' -o -name '*.js.map' -o -name '*.d.ts.map' \) -print0 \
  | sort -z \
  | xargs -0 sha256sum \
  | sha256sum \
  | awk '{print $1}')

# --- determine drift relative to git index ---------------------------------
# `git diff --quiet -- dist/` exits 0 if working tree dist/ matches index.
# Non-zero means dist/ has changes that are NOT staged.
if git diff --quiet -- dist/; then
  # dist/ matches index -- everything is in sync, commit may proceed.
  exit 0
fi

# Drift detected. Print the offending files + recovery command.
echo "" >&2
echo "==========================================================================" >&2
echo "ABORT: dist/ out of sync with src/" >&2
echo "==========================================================================" >&2
echo "" >&2
echo "Your commit modifies src/*.ts but dist/ is not in sync after rebuild." >&2
echo "Shipping this commit would push a broken @oivo/qmd to consumer machines" >&2
echo "(see i-9l2ueyyz / i-qkarfffa Stage-3 retrospective)." >&2
echo "" >&2
echo "Drifted files:" >&2
git diff --name-only -- dist/ | sed 's/^/  /' >&2
echo "" >&2

if [[ "$DIST_SHA_BEFORE" == "$DIST_SHA_AFTER" ]]; then
  # The rebuild produced the SAME dist/ as before, but git still reports
  # drift -- this means dist/ was already drifted before this commit
  # (someone hand-edited or forgot to stage a previous build). Tell them.
  echo "Note: rebuild produced no changes -- dist/ was already drifted before this" >&2
  echo "commit. Stage the drift OR revert dist/ to HEAD before retrying:" >&2
  echo "" >&2
  echo "  # Option A: include the drift in this commit" >&2
  echo "  git add dist/ && git commit  # retry" >&2
  echo "" >&2
  echo "  # Option B: discard the drift (use if dist/ was hand-edited)" >&2
  echo "  git checkout HEAD -- dist/ && git commit  # retry" >&2
else
  # The rebuild itself produced new dist/ content. User just needs to stage
  # the freshly-built dist/.
  echo "Recovery (run, then retry the commit):" >&2
  echo "" >&2
  echo "  git add dist/" >&2
  echo "  git commit  # retry" >&2
fi
echo "" >&2
echo "Bypass (NOT recommended -- ships broken @oivo/qmd to fleet):" >&2
echo "  git commit --no-verify" >&2
echo "" >&2
exit 1
