#!/usr/bin/env bash
#
# Werkbaum — „fertig" auf „in Produktion" befördern.
#
# Der mitgelieferte Plan docs/examples/werkbaum.werkbaum beschreibt
# Werkbaum selbst. SPEC §4 unterscheidet `[x]` fertig (abgeschlossen) von `[^]`
# in Produktion (deployed/live) — und die zweite Aussage kann erst der Deploy
# wahr machen. Konvention deshalb: beim Mergen `[x]`, unmittelbar VOR dem
# Deploy dieser Lauf, der daraus `[^]` macht und das als Commit festhält.
#
# Warum ein Commit und kein Rewrite beim Bauen: siehe docs/DECISIONS.md D30.
# Kurz — der Commit wird von BEIDEN Pipelines gesehen (GitHub Pages baut ihn
# beim Push, prod beim nächsten rsync), das Deployment-Artefakt bleibt
# inhaltsgleich mit dem Repo, und die „Was ist neu?"-Anzeige (D28) bleibt auf
# der Pages-Instanz erlebbar statt dort für immer stumm zu sein.
#
# Verwendung:
#   scripts/promote-shipped.sh [-n] [-y]
#
#   -n, --dry-run   nur zeigen, was befördert würde
#   -y, --yes       ohne Rückfrage befördern und committen
#
# Ohne zu befördernde Knoten endet der Lauf mit 0 und ändert nichts.
# Gepusht wird NICHT — das bleibt eine bewusste Handlung.
#
# UMFANG — bewusst genau EINE Datei, niemals ein Glob:
# Nur der Werkbaum-eigene Plan sagt etwas über das Deployment aus. `[x]` steht
# im Repo an mehreren Stellen, wo eine Beförderung falsch bis unsinnig wäre:
#   * die Legende („Agenda") zeigt `[x] fertig` als ANSCHAUUNGSMATERIAL für die
#     Notation (frontend/index.html, `chip('fertig','[x]')` in app.js) — daraus
#     würde `[^] fertig`, also Quatsch, den beim Diff niemand bemerkt;
#   * das mitgelieferte „Example"-Dokument (INITIAL in app.js) und die übrigen
#     docs/examples/*.werkbaum sind erfunden und beschreiben kein Deployment;
#   * SPEC §10 (kanonisches Beispiel, zugleich Test-Fixture) und die Checkboxen
#     in docs/TASKS.md.
# Wer diesen Lauf erweitern will, erweitert ihn deshalb NICHT auf ein Muster.
# Die Prüfung weiter unten bricht ab, sobald mehr als die Plandatei anders ist.

set -euo pipefail

DRY=0
YES=0
for arg in "$@"; do
  case "$arg" in
    -n|--dry-run) DRY=1 ;;
    -y|--yes)     YES=1 ;;
    -h|--help)
      awk 'NR>2 { if ($0 ~ /^#/) { sub(/^# ?/, ""); print } else exit }' "$0"
      exit 0 ;;
    *) echo "Unbekannte Option: $arg" >&2; exit 2 ;;
  esac
done

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"

PLAN="docs/examples/werkbaum.werkbaum"
[ -f "$PLAN" ] || { echo "Plan nicht gefunden: $PLAN" >&2; exit 1; }

# Nur Statusboxen am Zeilenanfang — nach optionalem Zeichen -/+/|/= und
# optionaler Faltmarke >/< (SPEC §1) —, damit ein „[x]" mitten im Label
# unangetastet bleibt. `x` auch als `X` (SPEC §4).
MATCH='^([[:space:]]*([-+|=][[:space:]]*)?([<>][[:space:]]+)?)\[[xX]\]'

mapfile -t HITS < <(grep -nE "$MATCH" "$PLAN" || true)

if [ "${#HITS[@]}" -eq 0 ]; then
  echo "==> Nichts zu befördern — kein [x] im Plan."
  exit 0
fi

echo "==> ${#HITS[@]} Knoten würden auf [^] befördert:"
printf '    %s\n' "${HITS[@]}"

[ "$DRY" -eq 1 ] && exit 0

# Der Commit fasst genau diese eine Datei an. Ist sie schon geändert, lässt sich
# die Beförderung nicht sauber von der Änderung trennen — dann lieber abbrechen.
if git rev-parse HEAD >/dev/null 2>&1 && [ -n "$(git status --porcelain -- "$PLAN")" ]; then
  echo "   ! $PLAN hat uncommittete Änderungen — erst committen, dann befördern." >&2
  exit 1
fi

if [ "$YES" -ne 1 ]; then
  printf '==> Befördern und committen? [y/N] '
  read -r ANS
  case "$ANS" in y|Y|j|J) : ;; *) echo "Abgebrochen."; exit 1 ;; esac
fi

# Labels für den Commit-Text sammeln (ohne Zeichen, Statusbox und Größe).
mapfile -t LABELS < <(printf '%s\n' "${HITS[@]}" \
  | sed -E 's/^[0-9]+:[[:space:]]*([-+|=][[:space:]]*)?([<>][[:space:]]+)?\[[xX]\][[:space:]]*//' \
  | sed -E 's/[[:space:]]*\((XS|S|M|L|XL|XXL)\)[[:space:]]*$//')

IS_GIT=0
git rev-parse HEAD >/dev/null 2>&1 && IS_GIT=1

BEFORE="$(mktemp)"; AFTER="$(mktemp)"
trap 'rm -f "$BEFORE" "$AFTER"' EXIT
[ "$IS_GIT" -eq 1 ] && git status --porcelain > "$BEFORE"

sed -i -E "s/$MATCH/\1[^]/" "$PLAN"

if [ "$IS_GIT" -eq 0 ]; then
  echo "==> Kein Git-Repo — Datei geändert, nicht committet."
  exit 0
fi

# Sicherung gegen künftige Ausweitung (siehe UMFANG oben): Nach dem Schreiben
# darf GENAU eine Datei neu geändert sein — die Plandatei. Träfe es zusätzlich
# die Legende oder ein fremdes Beispiel, fiele das im Diff kaum auf; hier fällt
# es sofort auf.
git status --porcelain > "$AFTER"
NEU="$(grep -vxF -f "$BEFORE" "$AFTER" | sed -E 's/^.{3}//' || true)"
if [ "$NEU" != "$PLAN" ]; then
  echo "   ! Unerwarteter Umfang — geändert wurde:" >&2
  printf '     %s\n' ${NEU:-"(nichts)"} >&2
  echo "     Erwartet war ausschließlich $PLAN. Nichts committet." >&2
  git checkout -- "$PLAN"
  exit 1
fi

{
  echo "docs: Werkbaum-Plan — ${#HITS[@]} Knoten in Produktion"
  echo
  echo "Beim Deploy der stabilen Instanz gehen diese Knoten live; SPEC §4"
  echo "unterscheidet [x] fertig von [^] in Produktion (D30):"
  echo
  printf -- '- %s\n' "${LABELS[@]}"
} | git commit -q -F - -- "$PLAN"

echo "==> Committet: $(git log --oneline -1)"
echo "   Hinweis: nicht gepusht. Ohne Push zeigt der Footer-Versionslink des"
echo "   Deploys auf einen Commit, den GitHub noch nicht kennt."
