#!/usr/bin/env bash

# The pressure valve.
#
# Hooks exist so "can Panama also do X when the theme changes" is a five-line
# file somebody drops in a directory rather than a fork, a feature request, or
# a patch that has to be rebased forever. The upstream ledger defers a plugin
# host as premature and still should; this covers most of what people actually
# want from one and has no API to keep stable beyond "we will run your script
# and tell you what happened".
#
# What must hold:
#
#   1. A failing hook is reported and stepped over. Somebody's broken script
#      must never break a theme change, an upgrade, or a login -- and the
#      hooks after it still run.
#   2. Arguments arrive as written. The first version passed each hook its own
#      path as $1, so every argument landed one place late; a hook reading $1
#      as the colour scheme got a filename.
#   3. Both the single file and the .d directory run, .d in sorted order, so
#      several things can react without fighting over one file.
#   4. A hook name cannot escape the hook directory.
#   5. No hooks at all is the normal case and exits cleanly.
#   6. Samples are copied, never symlinked. ~/.config/panama is the user's --
#      settings.json lives there -- and a symlinked directory would put their
#      scripts in the repository working tree.

set -uo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
hook="$repo_dir/bin/panama-hook"
samples="$repo_dir/config/dot/panama/hooks"
linker="$repo_dir/setup/scripts/link-dotfiles"

findings=()
note() { findings+=("$1"); }

[[ -x "$hook" ]] || { printf 'hooks contract: %s is not executable\n' "$hook" >&2; exit 1; }

work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
hooks="$work/hooks"
out="$work/out"
mkdir -p "$hooks"

run() { PANAMA_HOOK_DIR="$hooks" "$hook" "$@" 2>"$work/err"; }

# ── 5. Nothing to run ───────────────────────────────────────────────────────

run theme-set dark blue || note 'a hook name with no hooks behind it did not exit cleanly'

# ── 2 & 3. Arguments, and both places hooks live ────────────────────────────

: >"$out"
cat >"$hooks/theme-set" <<EOF
#!/usr/bin/env bash
printf 'single:%s,%s\n' "\$1" "\$2" >>"$out"
EOF
mkdir -p "$hooks/theme-set.d"
cat >"$hooks/theme-set.d/20-second" <<EOF
#!/usr/bin/env bash
printf 'second:%s,%s\n' "\$1" "\$2" >>"$out"
EOF
cat >"$hooks/theme-set.d/10-first" <<EOF
#!/usr/bin/env bash
printf 'first:%s,%s\n' "\$1" "\$2" >>"$out"
EOF
chmod +x "$hooks/theme-set" "$hooks/theme-set.d"/*

run theme-set dark orchid || note 'running hooks reported failure when none failed'

grep -qx 'single:dark,orchid' "$out" \
    || note "the single hook did not receive its arguments as written (got: $(grep '^single' "$out" || echo none))"
grep -qx 'first:dark,orchid' "$out" \
    || note 'a hook in the .d directory did not receive its arguments as written'
[[ "$(grep -c . "$out")" == "3" ]] \
    || note "expected three hooks to run, got $(grep -c . "$out")"
[[ "$(sed -n '2p' "$out")" == first:* && "$(sed -n '3p' "$out")" == second:* ]] \
    || note 'the .d hooks did not run in sorted order'

# A file that is not executable is not a hook. Dropping a note in the
# directory should not be an error.
printf 'not a script\n' >"$hooks/theme-set.d/30-readme"
: >"$out"
run theme-set dark orchid || note 'a non-executable file in the .d directory caused a failure'
[[ "$(grep -c . "$out")" == "3" ]] \
    || note 'a non-executable file was treated as a hook'
rm -f "$hooks/theme-set.d/30-readme"

# ── 1. A broken hook is stepped over ────────────────────────────────────────

cat >"$hooks/theme-set.d/05-broken" <<'EOF'
#!/usr/bin/env bash
exit 7
EOF
chmod +x "$hooks/theme-set.d/05-broken"

: >"$out"
run theme-set dark orchid \
    || note 'a failing hook made the whole run fail, so a broken script would break a theme change'
grep -q 'failed' "$work/err" \
    || note 'a failing hook was silent, so nobody would know their script is broken'
[[ "$(grep -c . "$out")" == "3" ]] \
    || note 'a failing hook stopped the hooks after it from running'

# ── 4. A hook name cannot escape ────────────────────────────────────────────

mkdir -p "$work/outside"
printf '#!/usr/bin/env bash\ntouch "%s/escaped"\n' "$work/outside" >"$work/outside/evil"
chmod +x "$work/outside/evil"
run ../outside/evil >/dev/null 2>&1 \
    && note 'a hook name containing a path separator was accepted'
[[ -e "$work/outside/escaped" ]] \
    && note 'a hook outside the hook directory was executed'

# ── 6. Samples ship, and are copied rather than symlinked ───────────────────

[[ -d "$samples" ]] || note 'no hook samples are shipped, so the mechanism is undiscoverable'
for sample in "$samples"/*.sample; do
    [[ -e "$sample" ]] || continue
    head -1 "$sample" | grep -q '^#!' \
        || note "$(basename "$sample") has no shebang, so copying it and adding +x would not run"
done
grep -q 'PANAMA_HOOK_SAMPLES' "$linker" \
    || note 'link-dotfiles does not install the hook samples'
grep -A12 'PANAMA_HOOK_SAMPLES' "$linker" | grep -q 'cp "\$sample"' \
    || note 'the hook samples are not copied; a symlinked hook directory would put user scripts in the repository'

# ── The call sites ──────────────────────────────────────────────────────────

for pair in "config/dot/quickshell/scripts/panama-theme-apps:theme-set" \
            "bin/panama-migrate:post-migrate" \
            "install:post-upgrade"; do
    file="${pair%%:*}"; name="${pair##*:}"
    grep -q "panama-hook\" $name\|panama-hook\" \"$name\"\|hook\" $name" "$repo_dir/$file" \
        || note "$file does not fire the $name hook"
done

if (( ${#findings[@]} > 0 )); then
    printf 'hooks contract: %d finding(s)\n' "${#findings[@]}" >&2
    printf '  - %s\n' "${findings[@]}" >&2
    exit 1
fi

printf 'hooks contract: PASS\n'
