#!/usr/bin/env bash
# PMOS portable kit — file-backed Initiative Gate (D54). Fail-closed enforcement of the loop,
# same contract as the hosted gate but reading pmos/state/ instead of a database. A PR is
# "anchored" only when:
#   1. the branch is initiative/<id>  (or the PR body declares 'Initiative: <id>'), and
#   2. pmos/state/initiatives/<id>.md EXISTS in the checkout, and
#   3. the file satisfies its LANE's artifact requirements (D56) — `type:` frontmatter picks it:
#        feature (default) -> prd_path + rubric_path set and both files exist (the original contract)
#        discovery         -> prd_path = the spike brief, set and exists; rubric optional
#        chore             -> a non-empty `intent:` line in the initiative file; no PRD/rubric
#        incident          -> prd_path = the incident record, set and exists (post-hoc is sanctioned)
#        content           -> prd_path = the content brief, set and exists; rubric recommended (D58)
#        prototype         -> prd_path = the quick prototype brief, set and exists; NO rubric BY
#                             DESIGN (leg A is grilled as dialogue, never scored — D60)
#      Unknown/missing type = feature: the strictest lane. Lane variation is never a skip.
# Escape hatch (explicit, logged): the `no-initiative` label — anchor-free trivia ONLY, real work
# takes a lane (D56).
# Inputs via env: HEAD_REF, PR_BODY, PR_LABELS. Optional: KIT_ROOT (default: pmos).
set -euo pipefail

KIT_ROOT="${KIT_ROOT:-pmos}"
fail() { echo "::error::initiative-gate: $*"; echo "❌ initiative gate FAILED — $*"; exit 1; }
note() { echo "::notice::initiative-gate: $*"; echo "ℹ️  $*"; }

# ---- escape hatch (anchor-free trivia only — real work takes a lane) --------
case " ${PR_LABELS:-} " in
  *" no-initiative "*) note "PR labelled 'no-initiative' — gate skipped (anchor-free trivia only; a chore/incident/discovery belongs in its lane, D56)."; exit 0 ;;
esac

# ---- fail closed if the state layer is missing ------------------------------
[ -d "$KIT_ROOT/state/initiatives" ] || fail "$KIT_ROOT/state/initiatives/ not found — the kit state layer is required and the gate fails closed (a hard gate must not be silently disableable)."

# ---- derive the initiative id ----------------------------------------------
id=""
case "${HEAD_REF:-}" in
  initiative/*) id="${HEAD_REF#initiative/}" ;;
esac
if [ -z "$id" ]; then
  # `|| true`: a no-match grep exits 1, which under set -e would abort BEFORE the fail() guidance
  # below ever prints (silent exit 1 on the most common failure path).
  id="$(printf '%s' "${PR_BODY:-}" | { grep -ioE 'Initiative:[[:space:]]*[A-Za-z0-9._-]+' || true; } | head -1 | sed -E 's/.*:[[:space:]]*//')"
fi
[ -n "$id" ] || fail "Not anchored. Name the branch 'initiative/<id>' or add 'Initiative: <id>' to the PR body, or label the PR 'no-initiative' for anchor-free trivia. See $KIT_ROOT/AGENTS.md."

# ---- validate the id (also guards the path interpolation below) -------------
printf '%s' "$id" | grep -qE '^[a-z0-9][a-z0-9._-]*$' || fail "Initiative id '$id' is malformed (expected ^[a-z0-9][a-z0-9._-]*$)."

# ---- read the initiative file ------------------------------------------------
init_file="$KIT_ROOT/state/initiatives/$id.md"
[ -f "$init_file" ] || fail "No initiative file at $init_file. Create it first — an initiative must exist before its PR. This is the loop rule the gate enforces."

frontval() { # frontval <key> — the key's value from the LEADING frontmatter block, trimmed.
  # The LEADING block only, mirroring workflow-state.js / file-mode-parse.js / metrics.py: the gate
  # used to grep the whole file, so a body line starting `parent_kr: ...` (a pasted example) could
  # fake the declaration the gate exists to check. Same rules as the canonical parsers: an opening
  # --- fence (before any key) is skipped and the next --- ends the block; blank lines are skipped;
  # the first non-key line ends the block (fenceless records, e.g. the kit-ci smoke fixture, keep
  # parsing from line 1); first value wins; ONE pair of surrounding double quotes is stripped. An
  # absent key prints nothing and falls through to the explicit fail() below.
  awk -v want="$1" '
    /^---/ { if (fence || seen) exit; fence = 1; next }
    /^[[:space:]]*$/ { next }
    {
      i = index($0, ":")
      key = (i > 0) ? substr($0, 1, i - 1) : ""
      if (key !~ /^[a-z_]+$/) exit
      seen = 1
      if (key != want) next
      val = substr($0, i + 1)
      gsub(/\r/, "", val)
      gsub(/^[[:space:]]+|[[:space:]]+$/, "", val)
      if (length(val) >= 2 && val ~ /^".*"$/) val = substr(val, 2, length(val) - 2)
      print val
      exit
    }' "$init_file"
}
prd="$(frontval prd_path)"
rubric="$(frontval rubric_path)"
lane="$(frontval type)"
intent="$(frontval intent)"
anchor="$(frontval parent_kr)"
[ -n "$lane" ] || lane="feature"

# ---- kernel invariant: an initiative anchors (D21/D59) -----------------------
# Hosted parity: create_initiative rejects a missing parent_kr at row creation; in the kit the
# file IS the row, so the gate is where the same rule lands. A KR or health:<id> — never blank.
[ -n "$anchor" ] || fail "initiative '$id' has no parent_kr in $init_file — anchor it to a KR, or to a declared health budget (parent_kr: health:<id> from $KIT_ROOT/planning/okrs/health-budgets.md, D59). Unanchored work is rejected (D21)."

# ---- lane-parameterized artifact checks (D56) --------------------------------
# The kernel invariants already held above (anchored record; the human merge stays the Acceptance
# Gate); what varies per lane is the spec/eval artifact. In EVERY lane a rubric_path that is set
# must point at a real file — optional never means dangling.
case "$lane" in
  discovery|incident|content|prototype)
    # if-form, not `[ … ] && …`: under set -e a false test as a bare && would kill the script.
    what="spike brief"
    if [ "$lane" = incident ]; then what="incident record (post-hoc within 48h is the sanctioned lane rule)"; fi
    if [ "$lane" = content ]; then what="content brief (rubric recommended, not required — D58)"; fi
    if [ "$lane" = prototype ]; then what="quick prototype brief (no rubric BY DESIGN — leg A is grilled, never scored; D60)"; fi
    [ -n "$prd" ] || fail "initiative '$id' (type $lane) has no prd_path in $init_file — set it to the $what (see $KIT_ROOT/okf/core/templates/)."
    [ -f "$prd" ] || fail "prd_path '$prd' (from $init_file, type $lane) does not exist in the repo."
    [ -z "$rubric" ] || [ -f "$rubric" ] || fail "rubric_path '$rubric' (from $init_file) does not exist in the repo (a rubric is optional in the $lane lane — set the path only to a real file)."
    echo "✅ initiative gate PASSED — '$id' ($lane lane): $init_file exists, $what ($prd) present."
    ;;
  chore)
    [ -n "$intent" ] || fail "initiative '$id' (type chore) has no intent in $init_file — the chore lane requires a one-paragraph 'intent:' line. Anchor-free trivia takes the no-initiative label; real work states its intent."
    [ -z "$prd" ]    || [ -f "$prd" ]    || fail "prd_path '$prd' (from $init_file) does not exist in the repo (not required in the chore lane — set the path only to a real file)."
    [ -z "$rubric" ] || [ -f "$rubric" ] || fail "rubric_path '$rubric' (from $init_file) does not exist in the repo (not required in the chore lane — set the path only to a real file)."
    echo "✅ initiative gate PASSED — '$id' (chore lane): $init_file exists, intent stated."
    ;;
  *)
    [ "$lane" = "feature" ] || note "unknown initiative type '$lane' on '$id' — enforcing the feature lane (strictest, fail-closed). The enum widens only with a ratified lane decision (D56)."
    [ -n "$prd" ]    || fail "initiative '$id' has no prd_path in $init_file. Author a PRD and set prd_path. Feature lane: PRD + rubric before build (eval-first, D13); other work shapes take their lane (D56)."
    [ -f "$prd" ]    || fail "prd_path '$prd' (from $init_file) does not exist in the repo."
    [ -n "$rubric" ] || fail "initiative '$id' has no rubric_path in $init_file. Author an eval rubric and set rubric_path."
    [ -f "$rubric" ] || fail "rubric_path '$rubric' (from $init_file) does not exist in the repo."
    echo "✅ initiative gate PASSED — '$id' (feature lane): $init_file exists, PRD ($prd) and rubric ($rubric) present."
    ;;
esac
