#!/usr/bin/env bash
# PreToolUse gate — ONE OWNER PER WORKTREE. A worktree under <main_root>/.worktrees/
# is claimed by the first agent session that mutates it. A second, DIFFERENT session
# attempting to mutate the same worktree while the owner is still running is refused.
# A lock whose owning session's `claude` process is gone is stale and reclaimed
# automatically — a dead session must never permanently block work.
#
# Mutating actions (the same set main-checkout-guard.sh treats as mutation):
#   - Edit / Write, always
#   - Bash, only when it runs one of the destructive git verbs (see worktree-guard-lib.sh)
# Read-only ops, and Bash outside that verb set, are never subject to the lock.
#
# Lock file: <main_root>/.worktrees/.locks/<slug>.json = {"session_id","claimed_at"}.
# Claim/check/reclaim is one flock-guarded critical section per worktree.
set -euo pipefail

LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/lib" && pwd)"
# shellcheck source=lib/worktree-guard-lib.sh
source "$LIB_DIR/worktree-guard-lib.sh"

INPUT=$(cat)
TOOL=$(printf '%s' "$INPUT" | jq -r '.tool_name // empty')
CWD=$(printf '%s' "$INPUT" | jq -r '.cwd // empty')
SESSION_ID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty')
[[ -z "$TOOL" || -z "$CWD" || -z "$SESSION_ID" ]] && exit 0

deny() {
  jq -n --arg r "$1" '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: $r
    }
  }'
  exit 0
}

case "$TOOL" in
  Edit|Write)
    TARGET=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')
    [[ -z "$TARGET" ]] && exit 0
    [[ "$TARGET" != /* ]] && TARGET="$CWD/$TARGET"
    MUTATING=1
    ;;
  Bash)
    TARGET="$CWD"
    CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty')
    [[ -z "$CMD" ]] && exit 0
    if wt_has_destructive_git "$CMD"; then MUTATING=1; else MUTATING=0; fi
    ;;
  *)
    exit 0
    ;;
esac
[[ "$MUTATING" -eq 1 ]] || exit 0

CWD=$(wt_resolve "$CWD")
MAIN_ROOT=$(wt_main_root "$CWD") || exit 0
MAIN_ROOT=$(wt_resolve "$MAIN_ROOT")
SLUG=$(wt_slug_for "$(wt_resolve "$TARGET")" "$MAIN_ROOT")
[[ -z "$SLUG" ]] && exit 0

LOCKS_DIR="$MAIN_ROOT/.worktrees/.locks"
mkdir -p "$LOCKS_DIR"
LOCK_FILE="$LOCKS_DIR/$SLUG.json"
FLOCK_FILE="$LOCKS_DIR/$SLUG.flock"

exec 9>"$FLOCK_FILE"
flock -w 5 9 || exit 0   # cannot get the arbitration lock in time — fail-open, never hang the agent

claim() {
  jq -n --arg s "$SESSION_ID" --argjson t "$(date +%s)" \
    '{session_id:$s, claimed_at:$t}' > "$LOCK_FILE"
}

if [[ ! -s "$LOCK_FILE" ]]; then
  claim
  exit 0
fi

OWNER=$(jq -r '.session_id // empty' "$LOCK_FILE" 2>/dev/null || true)
if [[ -z "$OWNER" || "$OWNER" == "$SESSION_ID" ]]; then
  claim
  exit 0
fi

if wt_session_alive "$OWNER"; then
  deny "worktree-lock-gate: $SLUG is owned by session $OWNER, which is still running — mutating it now would race that session's uncommitted edits. Work in a different worktree: \`~/.claude/bin/od-worktree add <slug>\`. A lock whose session has exited is reclaimed automatically, so never delete the lock file to get past this."
fi

# Owner's claude process is gone — stale lock, reclaim automatically.
claim
exit 0
