#!/usr/bin/env bash
# Master-skill session-start hook — injects the available-masters list into
# the conversation context. Compatible with Claude Code, Cursor, Copilot CLI.
#
# The work is in session_start.py. This was a bash loop that started python3
# once per master to sanitize a single `lineage:` value, plus once more to
# encode JSON: 17 interpreter starts, 0.37s measured, on a hook the harness
# runs with "async": false at every startup / clear / compact.
set -euo pipefail

# Path resolution, as a function so it can be driven directly by
# hooks/tests/test_session_start.sh. The Windows case cannot be reproduced by
# invocation on Linux — `bash "/tmp/x\hooks\session-start"` simply fails to
# open, because a backslash is an ordinary filename character here, not a
# separator. Testing it therefore needs a seam, the same way the Rust side
# split `resolve_interpreter` out rather than mutating the environment. The
# alternative was a `grep` for the substitution in this file, which passed
# just as happily when the substitution was deleted and left in a comment.
#
# run-hook.cmd invokes this as `bash "C:\...\hooks\session-start"`, and under
# Git Bash `dirname` on that returns "." — SCRIPT_DIR silently became the
# working directory, session_start.py was not found, and the hook emitted `{}`.
resolve_script_dir() {
    local self="${1//\\//}"
    local dir
    dir="$(cd "$(dirname "$self")" 2>/dev/null && pwd)" || dir=""
    if [ -z "$dir" ] || [ ! -f "$dir/session_start.py" ]; then
        # Second chance, normalised the same way: the plugin root the host
        # hands us is a backslash path on exactly the platform that needs it.
        local root="${CLAUDE_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}"
        dir="${root//\\//}/hooks"
    fi
    printf '%s' "$dir"
}

# Sourced by the test suite to reach resolve_script_dir without running the
# hook. Nothing else sets this.
if [ -n "${MASTER_SKILL_HOOK_TEST_ONLY:-}" ]; then
    return 0 2>/dev/null || exit 0
fi

SCRIPT_DIR="$(resolve_script_dir "${BASH_SOURCE[0]}")"
PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." 2>/dev/null && pwd)" || PLUGIN_ROOT=""

# The floor: the five mode commands, always. They are static text — nothing
# about them can fail — so any degraded payload that drops them is losing
# something for free. The bash version this file replaced printed them
# unconditionally; the first rewrite made the whole payload contingent on
# locating a second file, and its "fallback" emitted a bare
# "Master-skill plugin loaded." with zero modes while a comment right above
# it claimed otherwise.
#
# Written as a pure-ASCII JSON string (\u2014 for the em dash) so it survives
# any stdout encoding, and wrapped in the shape THIS host reads — the earlier
# literal hardcoded the top-level `additionalContext` key, which Claude Code
# does not read at all, making it exactly the `{}` it was meant to replace.
MODES_JSON='"Master-skill plugin loaded.\n  /master-help \u2014 not sure which master or mode? start here\n  /compare-masters \u2014 multi-tradition comparison\n  /master-debate \u2014 4-round adversarial dialectic between masters\n  /master-curriculum \u2014 staged learning path within a tradition\n  /create-master \u2014 generate new master from FoJin knowledge graph"'

emit_modes_only() {
    [ -n "${1:-}" ] && echo "master-skill session-start: $1; listing modes only" >&2
    if [ -n "${CURSOR_PLUGIN_ROOT:-}" ]; then
        printf '{"additional_context": %s}\n' "$MODES_JSON"
    elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -z "${COPILOT_CLI:-}" ]; then
        # hookEventName is required; without it Claude Code rejects the payload.
        printf '{"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": %s}}\n' "$MODES_JSON"
    else
        printf '{"additionalContext": %s}\n' "$MODES_JSON"
    fi
}

# Find an interpreter that actually runs. On Windows `python3` is a Microsoft
# Store alias stub: it exists, so `command -v python3` succeeds, and running it
# exits without executing anything. Measured 2026-09-18 through the cmd.exe
# wrapper on Windows 11 — every session fell back to "listing modes only", so
# the fifteen masters never reached the model there. The real interpreter is
# python.exe or the py launcher; desktop/src/cli.rs already knew this.
PY_BIN=""
PY_ARG=""
for candidate in ${MASTER_SKILL_PYTHON:-} python3 python py; do
    command -v "$candidate" >/dev/null 2>&1 || continue
    if [ "$candidate" = "py" ]; then
        if py -3 -c "import sys" >/dev/null 2>&1; then
            PY_BIN="py"
            PY_ARG="-3"
            break
        fi
    elif "$candidate" -c "import sys" >/dev/null 2>&1; then
        PY_BIN="$candidate"
        break
    fi
done

if [ -z "$PY_BIN" ]; then
    emit_modes_only "no working python3/python/py"
    exit 0
fi

if [ -z "$PLUGIN_ROOT" ] || [ ! -f "$SCRIPT_DIR/session_start.py" ]; then
    emit_modes_only "could not locate session_start.py"
elif ! "$PY_BIN" $PY_ARG "$SCRIPT_DIR/session_start.py" "$PLUGIN_ROOT"; then
    emit_modes_only "session_start.py failed"
fi
