#!/usr/bin/env bash
# handoff-base-gate.sh — the handoff skill's "Base-branch gate (MUST)" as TESTED code.
# audience: AI coding agents first.
#
# WHY THIS EXISTS: the executor cuts its integration branch from meta.base_branch. A wrong/missing
# base makes the implementer rebuild the plan's prerequisites from scratch → a giant bogus diff no
# review can cover. The gate was prose an agent re-derived (and could mis-run rev-parse/ls-tree or
# silently skip a check); this makes it one fixed command the agent invokes by path.
#
# CONTRACT: prints ONE line of JSON on stdout = {"pass":bool,"checks":[{"name","ok","detail"}]}.
#   pass = every check ok. Diagnostics → stderr. Exit 0 on a completed gate (a failed check is DATA
#   in the JSON, not a shell error); exit non-zero ONLY on a usage/environment fault.
# READ-ONLY: never writes a file, never mutates a ref. The single side effect is a best-effort
#   `git fetch` (|| true, offline-safe) so the freshness comparison sees the true origin.
#
# Args: <repoRoot> <jsonlPath> [amendedPath...]
#   amendedPath = a path a task amends/extends an EXISTING module at. Deciding WHICH tasks amend an
#   existing module is CALLER judgment — the script verifies the paths it is given, it does not pick
#   them. Zero paths → the carries-deps check is vacuously satisfied (no amend-existing tasks).
set -uo pipefail

# Escape a string for a JSON double-quoted value (controlled, single-line).
jstr() { local s=${1//\\/\\\\}; s=${s//\"/\\\"}; s=${s//$'\t'/ }; s=${s//$'\n'/ }; printf '%s' "$s"; }

# Read meta.base_branch from the session-state JSONL (first record carrying it; type=="meta").
read_base_branch() {
  python3 - "$1" <<'PY'
import sys, json
base = ""
try:
    with open(sys.argv[1], encoding='utf-8') as f:
        for ln in f:
            ln = ln.strip()
            if not ln:
                continue
            try:
                o = json.loads(ln)
            except Exception:
                continue
            if isinstance(o, dict) and (o.get('type') == 'meta' or 'base_branch' in o):
                b = o.get('base_branch')
                base = b if isinstance(b, str) else ""
                break
except Exception:
    pass
print(base)
PY
}

base_gate() {
  local repoRoot=$1 jsonlPath=$2; shift 2 || true
  local -a paths=("$@")
  [[ -e "$repoRoot/.git" ]] || { echo "base-gate: not a git repo: $repoRoot" >&2; return 3; }
  [[ -f "$jsonlPath" ]]     || { echo "base-gate: jsonl not found: $jsonlPath" >&2; return 3; }

  local N=() OK=() D=()                 # parallel check arrays: name, ok(true|false), detail
  add() { N+=("$1"); OK+=("$2"); D+=("$3"); }

  # Check 1 — Set: meta.base_branch present and non-empty.
  local base; base=$(read_base_branch "$jsonlPath")
  if [[ -z "$base" ]]; then
    add set false "meta.base_branch absent/empty — set it before handing off (never hand off without it)"
  else
    add set true "base_branch=$base"
    git -C "$repoRoot" fetch --quiet origin 2>/dev/null || true   # best-effort; offline is fine

    # Check 2 — Resolves: names a real commit.
    if git -C "$repoRoot" rev-parse --verify --quiet "$base^{commit}" >/dev/null 2>&1; then
      add resolves true "resolves to $(git -C "$repoRoot" rev-parse --short "$base^{commit}" 2>/dev/null)"

      # Check 3 — prefer remote-tracking: FAIL only when a local base PROVABLY lags its origin
      # counterpart (the documented build-from-stale-base failure); advisory otherwise.
      if [[ "$base" == */* ]]; then
        add prefer-remote-tracking true "base names a tracking/qualified ref ($base) — not a lag-prone bare local name"
      elif git -C "$repoRoot" rev-parse --verify --quiet "origin/$base^{commit}" >/dev/null 2>&1; then
        local behind; behind=$(git -C "$repoRoot" rev-list --count "$base..origin/$base" 2>/dev/null || echo 0)
        if [[ "$behind" -gt 0 ]]; then
          add prefer-remote-tracking false "local '$base' lags origin/$base by $behind commit(s) — use origin/$base (a stale base misses merged prerequisites)"
        else
          add prefer-remote-tracking true "local '$base' is level with origin/$base"
        fi
      else
        add prefer-remote-tracking true "local '$base' has no origin counterpart to compare (advisory: prefer a remote-tracking ref)"
      fi

      # Check 4 — Carries the deps: every amended path must already exist on the base.
      local p
      for p in "${paths[@]:-}"; do
        [[ -z "$p" ]] && continue
        if [[ -n "$(git -C "$repoRoot" ls-tree -r --name-only "$base" -- "$p" 2>/dev/null)" ]]; then
          add carries-deps true "base carries $p"
        else
          add carries-deps false "base does NOT carry $p — base predates this prerequisite (WRONG base); find where $p lives (git branch -a --contains / git log --all -- $p) and set that"
        fi
      done
    else
      add resolves false "does not name a real commit: $base"
    fi
  fi

  # emit
  local pass=true i
  for ((i=0; i<${#OK[@]}; i++)); do [[ "${OK[$i]}" == "true" ]] || pass=false; done
  local out='{"pass":'"$pass"',"checks":['
  for ((i=0; i<${#N[@]}; i++)); do
    [[ $i -gt 0 ]] && out+=','
    out+='{"name":"'"$(jstr "${N[$i]}")"'","ok":'"${OK[$i]}"',"detail":"'"$(jstr "${D[$i]}")"'"}'
  done
  out+=']}'
  printf '%s\n' "$out"
}

# ── dispatch (only when executed, not when sourced by the test harness) ───────
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
  base_gate "$@"
fi
