#!/usr/bin/env bash
# Create the work-item structure in Gitea: labels, milestones, and the board.
#
#   tools/gitea-setup            create anything missing (idempotent)
#   tools/gitea-setup --dry-run  say what it would create, change nothing
#
# Needs a token with `write:issue`. The existing git credential is scoped
# `write:repository`, which pushes fine and is REFUSED by every issue endpoint --
# checked, not assumed:
#
#   {"message":"token does not have at least one of required scope(s),
#    required=[read:issue], token scope=write:repository"}
#
# So this reads a SECOND token from ~/.sylph-gitea-api-token, deliberately
# separate from the push credential: different blast radius, and rotating one
# does not break the other.
#
# ── Why Gitea rather than a new tracker ─────────────────────────────────────
#
# The failure this replaces is a 1,227-line hand-maintained `BLOCKED.md` whose
# anti-staleness convention turned out constant by construction, plus 21
# inter-agent messages sent into a void with no delivery feedback. Both are
# solved by items that live in a database with state, an owner and dependency
# edges -- and Gitea is already deployed here, so it adds no second store to
# drift out of sync with the first. That drift is this project's defining
# failure mode; adding a tool with its own copy of the truth would be choosing
# more of it.
set -euo pipefail

HOST="${SYLPH_GITEA_HOST:-git.mc02.dev}"
REPO="${SYLPH_GITEA_REPO:-fabi/Sylpheed}"
TOKFILE="${SYLPH_GITEA_API_TOKEN:-$HOME/.sylph-gitea-api-token}"
DRY=0; [ "${1:-}" = "--dry-run" ] && DRY=1

[ -f "$TOKFILE" ] || {
  cat >&2 <<EOF
gitea-setup: no API token at $TOKFILE

  Create one in Gitea: Settings -> Applications -> Generate New Token
  Scopes needed: write:issue        (and read:repository, to see the repo)
  Then:  echo '<token>' > $TOKFILE && chmod 600 $TOKFILE

  This is NOT the push credential. That one is scoped write:repository and is
  refused by every issue endpoint.
EOF
  exit 2
}
TOK=$(tr -d '[:space:]' < "$TOKFILE")
API="https://$HOST/api/v1/repos/$REPO"
AUTH="Authorization: token $TOK"

api() { curl -sS --max-time 30 -H "$AUTH" -H 'Content-Type: application/json' "$@"; }

# Fail loudly and specifically on the one error everyone hits.
probe=$(api "$API/labels" || true)
case "$probe" in
  *'required scope'*)
    echo "🔴 the token at $TOKFILE lacks issue scope:" >&2
    echo "   $probe" >&2
    echo "   Regenerate it with write:issue." >&2
    exit 2 ;;
  '') echo "🔴 no response from $API -- host or network" >&2; exit 2 ;;
esac

say() { [ "$DRY" = 1 ] && echo "  would create $*" || echo "  created $*"; }

# ── Labels ──────────────────────────────────────────────────────────────────
# The state set encodes the working model the human set on 2026-09-02: a human
# defines a bundle, agents decompose it, and each item ends in a HUMAN check.
# `needs-human` is the important one -- it is the state the whole model turns on
# and the one no off-the-shelf agent tool models, because the market has
# converged on removing the human rather than gating on them.
existing=$(printf '%s' "$probe" | python3 -c "import json,sys;print('\n'.join(l['name'] for l in json.load(sys.stdin)))" 2>/dev/null || true)
mklabel() { # name colour description
  printf '%s\n' "$existing" | grep -qxF "$1" && return 0
  if [ "$DRY" = 0 ]; then
    api -X POST "$API/labels" -d "$(python3 -c "
import json,sys; print(json.dumps({'name':sys.argv[1],'color':sys.argv[2],'description':sys.argv[3]}))" "$1" "$2" "$3")" >/dev/null
  fi
  say "label $1"
}

mklabel "state/proposed"    "d4c5f9" "Agent proposed this item; awaiting the human's approval to start"
mklabel "state/approved"    "0e8a16" "Human approved the shape; an agent may start"
mklabel "state/in-progress" "1d76db" "An agent is working it now"
mklabel "state/needs-human" "fbca04" "Done as far as an agent can tell -- a person must look. The body says what to look at"
mklabel "state/blocked"     "b60205" "Waiting on another item; use the Depends-On field, not prose"
mklabel "agent/decoder"     "5319e7" "Owned by the Decoder (disc to meaning; runs the emulator)"
mklabel "agent/port"        "006b75" "Owned by the Port (disc to playable; no RE)"
mklabel "kind/bundle"       "c2e0c6" "A bundle the human defined; agents decompose it into items"
mklabel "kind/item"         "bfd4f2" "One unit of work, small enough to finish in a single session"
mklabel "kind/ask"          "e99695" "One agent asking the other for something it cannot answer in role"
mklabel "kind/defect"       "d93f0b" "Found by a play-test or a check"

# ── Milestones = bundles ────────────────────────────────────────────────────
ms=$(api "$API/milestones?state=all" | python3 -c "import json,sys;print('\n'.join(m['title'] for m in json.load(sys.stdin)))" 2>/dev/null || true)
mkms() {
  printf '%s\n' "$ms" | grep -qxF "$1" && return 0
  if [ "$DRY" = 0 ]; then
    api -X POST "$API/milestones" -d "$(python3 -c "
import json,sys; print(json.dumps({'title':sys.argv[1],'description':sys.argv[2]}))" "$1" "$2")" >/dev/null
  fi
  say "milestone (bundle) $1"
}
mkms "Menus"        "The menu shell: title, main menu, submenus, navigation, audio."
mkms "Title screen" "Title timing and animation: the sweep onset, the plate, what (A) does."
mkms "Graphics pipeline" "Decoder: disc -> decode -> per-frame update -> submitted draws -> Canary -> screen."
mkms "Infrastructure" "Containers, supervision, auth, work tracking. Not game work."

echo
if [ "$DRY" = 1 ]; then
  echo "dry run -- nothing was created."
else
  echo "labels and bundles are in place at https://$HOST/$REPO/issues"
  echo
  echo "Remaining, by hand in the web UI (the API for Projects lags it):"
  echo "  Projects -> New Project -> columns:"
  echo "    Proposed | Approved | In progress | Needs human | Done"
fi
