#!/usr/bin/env bash
# Machine-written release stamps in a plugin's own docs.
#
# release.sh publishes a zip to plugins.bowden.works but wrote nothing back to the
# plugin, so the "Not released" line a session had just written stayed there after the
# release landed hours later. A later reader then had to reconstruct the published state
# from tools/release-history.json and the dist host directory before it was safe to act.
# These helpers write the fact back at publish time.
#
# Two files, two deliberately different jobs:
#
#   docs/SESSION-LOG.md    a LEDGER — one line per publish, newest first, added to.
#                          Matches the file's append-only, newest-at-the-top convention.
#   docs/HANDOFF-NOTES.md  a BANNER — the currently published version only, replaced
#                          each publish. Handoff notes are read for "where do I land",
#                          so what belongs at the top is current state, not history.
#
# Everything written sits between HTML-comment markers and is regenerated wholesale, so
# hand-written prose in either file is never read, moved or rewritten, and a block can be
# removed by deleting from BEGIN to END. Nothing here may abort a release: by the time it
# runs the zip is already on the dist host, so a doc-write problem warns and the release
# stands.

BW_STAMP_LEDGER_BEGIN='<!-- BW-RELEASES:BEGIN — written by tools/release.sh; do not hand-edit -->'
BW_STAMP_LEDGER_END='<!-- BW-RELEASES:END -->'
BW_STAMP_BANNER_BEGIN='<!-- BW-RELEASE-STATUS:BEGIN — written by tools/release.sh; do not hand-edit -->'
BW_STAMP_BANNER_END='<!-- BW-RELEASE-STATUS:END -->'

# Return codes used by the two stamp functions.
BW_STAMP_OK=0       # block written
BW_STAMP_FAIL=1     # something went wrong; caller should warn
BW_STAMP_SKIP=3     # the plugin doesn't keep this file — nothing to do

# One ledger line. Short, fixed shape, unmistakably script-written:
#   > RELEASED 1.8.6 — 2026-08-12T04:22:13Z, sha256:ca60652fa8427708
bw_stamp_line() {
	printf '> RELEASED %s — %s, sha256:%s' "$1" "$2" "${3:0:16}"
}

_bw_stamp_ledger_block() {
	printf '%s\n' \
		"$BW_STAMP_LEDGER_BEGIN" \
		'## Releases' \
		'' \
		'Written by `tools/release.sh` at publish time. This block is the only claim about' \
		'release state in this file that stays current — a session entry below saying "not' \
		'released" was true when it was written and nothing updates it. Full history across' \
		'all plugins: `tools/release-history.json`.' \
		'' \
		"$1" \
		"$BW_STAMP_LEDGER_END"
}

_bw_stamp_banner_block() {
	printf '%s\n' \
		"$BW_STAMP_BANNER_BEGIN" \
		"> **PUBLISHED: ${1}** — ${2}, sha256:${3:0:16}" \
		'>' \
		'> Rewritten by `tools/release.sh` on every publish — the only release-state claim in' \
		'> this file that is current. A "not released" note below was true when it was written' \
		'> and nothing updates it. Ledger: `docs/SESSION-LOG.md` → Releases.' \
		"$BW_STAMP_BANNER_END"
}

# Where the ledger goes in a file that doesn't have one yet: just before the horizontal
# rule that separates the preamble from the entries, so it is read before any session
# entry. Falls back to the first `## ` heading, then to 0 meaning "no anchor, append".
#
# Fenced code is skipped: most SESSION-LOGs carry a "## Format" example containing a
# literal `## YYYY-MM-DD` heading. A rule must follow a blank line to count, so a setext
# heading underline (text on one line, `---` on the next) is never split in half.
_bw_stamp_insert_line() {
	awk '
		/^(```|~~~)/ { fence = !fence; prev = $0; next }
		fence        { prev = $0; next }
		NR > 1 && !rule && /^-{3,}[[:space:]]*$/ && prev ~ /^[[:space:]]*$/ { rule = NR }
		NR > 1 && !head && /^## / { head = NR }
		{ prev = $0 }
		END { print (rule ? rule : (head ? head : 0)) }
	' "$1"
}

# Move a rewritten file into place. Copies onto the original rather than mv'ing over it
# so the doc keeps its existing owner, group and mode (plugin docs are group-writable to
# <slug>-dev). Refuses an empty or unexpectedly shrunken result — we only ever add a
# bounded block, so a large shrink means the rewrite went wrong and the original is worth
# more than the change.
_bw_stamp_commit() {
	local file="$1" tmp="$2" was="$3" now
	if [[ ! -s "$tmp" ]]; then
		rm -f "$tmp"
		return $BW_STAMP_FAIL
	fi
	now=$(wc -l < "$tmp")
	if (( now < was - 8 )); then
		rm -f "$tmp"
		return $BW_STAMP_FAIL
	fi
	cat "$tmp" > "$file" || { rm -f "$tmp"; return $BW_STAMP_FAIL; }
	rm -f "$tmp"
	return $BW_STAMP_OK
}

# bw_stamp_session_log <session-log.md> <version> <released-at> <sha256>
#
# Adds this version to the Releases ledger, newest first, creating the block if absent.
# Re-publishing a version replaces that version's line rather than duplicating it — the
# append-only audit trail of every publish attempt lives in release-history.json, which
# is the right place for it; this ledger answers "what is live".
bw_stamp_session_log() {
	local file="$1" version="$2" released_at="$3" sha="$4"
	[[ -f "$file" ]] || return $BW_STAMP_SKIP

	local tmp blocktmp was line ver_re
	was=$(wc -l < "$file")
	line=$(bw_stamp_line "$version" "$released_at" "$sha")
	ver_re="${version//./\\.}"
	tmp=$(mktemp -t bw-stamp-XXXXXX) || return $BW_STAMP_FAIL

	if grep -qF -- "$BW_STAMP_LEDGER_BEGIN" "$file"; then
		awk -v bmark="$BW_STAMP_LEDGER_BEGIN" -v emark="$BW_STAMP_LEDGER_END" \
		    -v line="$line" -v verre="^> RELEASED ${ver_re}([ ,]|\$)" '
			index($0, bmark) == 1 { inblock = 1; print; next }
			index($0, emark) == 1 {
				if (inblock && !placed) print line
				inblock = 0; placed = 1; print; next
			}
			inblock && $0 ~ verre { next }
			inblock && !placed && /^> RELEASED / { print line; placed = 1 }
			{ print }
		' "$file" > "$tmp" || { rm -f "$tmp"; return $BW_STAMP_FAIL; }
	else
		local at
		at=$(_bw_stamp_insert_line "$file")
		blocktmp=$(mktemp -t bw-stamp-block-XXXXXX) || { rm -f "$tmp"; return $BW_STAMP_FAIL; }
		_bw_stamp_ledger_block "$line" > "$blocktmp"
		awk -v at="$at" -v blockfile="$blocktmp" '
			function emit(   l) { while ((getline l < blockfile) > 0) print l }
			NR == at && at > 0 { emit(); print "" }
			{ print }
			END { if (at == 0) { print ""; emit() } }
		' "$file" > "$tmp" || { rm -f "$tmp" "$blocktmp"; return $BW_STAMP_FAIL; }
		rm -f "$blocktmp"
	fi

	_bw_stamp_commit "$file" "$tmp" "$was"
}

# bw_stamp_handoff_notes <handoff-notes.md> <version> <released-at> <sha256>
#
# Replaces the current-state banner directly under the file's title.
bw_stamp_handoff_notes() {
	local file="$1" version="$2" released_at="$3" sha="$4"
	[[ -f "$file" ]] || return $BW_STAMP_SKIP

	local tmp blocktmp was
	was=$(wc -l < "$file")
	tmp=$(mktemp -t bw-stamp-XXXXXX) || return $BW_STAMP_FAIL
	blocktmp=$(mktemp -t bw-stamp-block-XXXXXX) || { rm -f "$tmp"; return $BW_STAMP_FAIL; }
	_bw_stamp_banner_block "$version" "$released_at" "$sha" > "$blocktmp"

	if grep -qF -- "$BW_STAMP_BANNER_BEGIN" "$file"; then
		awk -v bmark="$BW_STAMP_BANNER_BEGIN" -v emark="$BW_STAMP_BANNER_END" \
		    -v blockfile="$blocktmp" '
			function emit(   l) { while ((getline l < blockfile) > 0) print l }
			index($0, bmark) == 1 { emit(); skip = 1; next }
			skip && index($0, emark) == 1 { skip = 0; next }
			skip { next }
			{ print }
		' "$file" > "$tmp" || { rm -f "$tmp" "$blocktmp"; return $BW_STAMP_FAIL; }
	else
		awk -v blockfile="$blocktmp" '
			function emit(   l) { while ((getline l < blockfile) > 0) print l }
			NR == 1 {
				if (/^# /) { print; print ""; emit() }
				else       { emit(); print ""; print }
				next
			}
			{ print }
			END { if (NR == 0) emit() }
		' "$file" > "$tmp" || { rm -f "$tmp" "$blocktmp"; return $BW_STAMP_FAIL; }
	fi

	rm -f "$blocktmp"
	_bw_stamp_commit "$file" "$tmp" "$was"
}

# bw_stamp_release_docs <plugin-dir> <version> <released-at> <sha256>
#
# Stamp both docs for a publish that actually reached the dist host. Prints one line per
# file so the release output says what it touched. Returns non-zero only if a file that
# exists could not be written — a plugin that keeps neither doc is not an error.
bw_stamp_release_docs() {
	local plugin_dir="$1" version="$2" released_at="$3" sha="$4"
	local rc=0 f status

	for f in "docs/SESSION-LOG.md" "docs/HANDOFF-NOTES.md"; do
		status=$BW_STAMP_OK
		case "$f" in
			*SESSION-LOG.md)
				bw_stamp_session_log "${plugin_dir}/${f}" "$version" "$released_at" "$sha" || status=$?
				;;
			*HANDOFF-NOTES.md)
				bw_stamp_handoff_notes "${plugin_dir}/${f}" "$version" "$released_at" "$sha" || status=$?
				;;
		esac
		case "$status" in
			"$BW_STAMP_OK")   printf '  stamped %s\n' "$f" ;;
			"$BW_STAMP_SKIP") : ;;
			*)                printf '  FAILED to stamp %s\n' "$f" >&2; rc=1 ;;
		esac
	done

	return $rc
}
