#!/usr/bin/env bash
# PreToolUse gate — DENY agent mutation of the SHARED main checkout.
# Incident: two agent sessions both had cwd == /home/user/Projects/overdeck (the main
# checkout). One ran `git restore`/`git checkout --`, which acts on the whole working
# tree and silently destroyed the other session's uncommitted edits.
#
# Scope: only repos that have opted into worktree isolation (a <main_root>/.worktrees
# directory, created by `od-worktree add`) are enforced. Repos with no such convention
# are left alone — this must never block a single-checkout project.
#
# Blocks, when the effective location is the main checkout (not a path under
# <main_root>/.worktrees/<slug>):
#   - Edit/Write to any file_path resolving inside the main checkout tree
#   - Bash: git restore | git checkout -- <path> | git checkout . |
#           git stash (push/pop/drop/clear) | git clean -f* | git reset --hard
#   - Bash: non-git writes whose TARGET lands inside a protected tree — `>`/`>>`,
#           tee, cp/mv/install/rsync destination, sed -i, perl -i, touch, truncate, dd of=
# Read-only ops (status/log/diff/show/grep/read, any non-matching Bash) pass through.
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')
[[ -z "$TOOL" || -z "$CWD" ]] && exit 0

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

FIX='Create/claim a worktree and work there: `~/.claude/bin/od-worktree add <slug>`, then cd into /home/user/Projects/overdeck/.worktrees/<slug>.'
DEPLOY_FIX='Change the repo and land it — the clone is rebuilt from main by packaging/deploy-local.sh: `~/.claude/bin/od-worktree add <slug>`, edit under /home/user/Projects/overdeck/.worktrees/<slug>, then `bash /home/user/Projects/overdeck/.claude/scripts/ship.sh land wt/<slug> <worktree>`.'

CWD=$(wt_resolve "$CWD")
DEPLOY_ROOT=$(wt_deploy_root)

ENFORCE_MAIN=0
if MAIN_ROOT=$(wt_main_root "$CWD"); then
  MAIN_ROOT=$(wt_resolve "$MAIN_ROOT")
  [[ -d "$MAIN_ROOT/.worktrees" ]] && ENFORCE_MAIN=1
else
  MAIN_ROOT=""
fi

case "$TOOL" in
  Edit|Write)
    TARGET=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')
    [[ -z "$TARGET" ]] && exit 0
    [[ "$TARGET" != /* ]] && TARGET="$CWD/$TARGET"
    TARGET=$(wt_resolve "$TARGET")
    if wt_is_within "$TARGET" "$DEPLOY_ROOT"; then
      deny "main-checkout-guard: writing to $TARGET mutates the LIVE deploy clone ($DEPLOY_ROOT) — every agent's hooks, skills and landers execute from it, and the next deploy overwrites the edit. $DEPLOY_FIX"
    fi
    if [[ "$ENFORCE_MAIN" -eq 1 ]] && wt_is_main_checkout_path "$TARGET" "$MAIN_ROOT"; then
      deny "main-checkout-guard: writing to $TARGET mutates the SHARED main checkout ($MAIN_ROOT) — another session's uncommitted work lives there too. $FIX"
    fi
    ;;
  Bash)
    CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty')
    [[ -z "$CMD" ]] && exit 0

    # Candidate execution dirs: the tool's cwd, plus any `cd <dir>` target in the
    # command — a leading `cd <main_root> && git restore .` from a worktree session
    # must be caught too, not just a bare invocation already sitting in the main root.
    BASES=("$CWD")
    while IFS= read -r target; do
      [[ -z "$target" ]] && continue
      [[ "$target" != /* && "$target" != "~"* ]] && target="$CWD/$target"
      BASES+=("$(wt_resolve "$target")")
    done < <(wt_cd_targets "$CMD")

    if wt_has_destructive_git "$CMD"; then
      CANDIDATES=("${BASES[@]}")
      # git's own -C / --work-tree point the working tree away from cwd, so a command
      # typed from a worktree can still mutate a protected root.
      while IFS= read -r target; do
        [[ -z "$target" ]] && continue
        [[ "$target" != /* && "$target" != "~"* ]] && target="$CWD/$target"
        CANDIDATES+=("$(wt_resolve "$target")")
      done < <(wt_git_dir_targets "$CMD")

      for target in "${CANDIDATES[@]}"; do
        if wt_is_within "$target" "$DEPLOY_ROOT"; then
          deny "main-checkout-guard: this git command discards/overwrites working-tree state in the LIVE deploy clone ($DEPLOY_ROOT), which every agent executes from. $DEPLOY_FIX"
        fi
        if [[ "$ENFORCE_MAIN" -eq 1 ]] && wt_is_main_checkout_path "$target" "$MAIN_ROOT"; then
          deny "main-checkout-guard: this git command discards/overwrites working-tree state in the SHARED main checkout ($MAIN_ROOT) — it can silently destroy another session's uncommitted edits. $FIX"
        fi
      done
    fi

    # Non-git writes dirty a protected tree just as effectively: one stray file in the
    # deploy clone makes every deploy on this machine abort deploy-clone-dirty.
    # wt_write_targets already prefixes a relative target with the `cd` chain in force at
    # that point, so the tool cwd is the only base — resolving against every `cd` target
    # would deny `cd <main> && cd .worktrees/x && … > f`, which writes into the worktree.
    while IFS= read -r target; do
      [[ -z "$target" ]] && continue
      # An unexpanded variable cannot be resolved from command text. Judge only the
      # literal prefix before it: `$DEPLOY_ROOT_LITERAL/$f` is still a protected write,
      # while `$TMP/f` is unknowable and must not be denied — a false positive here
      # blocks legitimate tooling for every agent on the machine.
      if [[ "$target" == *'$'* ]]; then
        target="${target%%\$*}"
        [[ "$target" != /* && "$target" != "~"* ]] && continue
      fi
      abs="$target"
      [[ "$abs" != /* && "$abs" != "~"* ]] && abs="$CWD/$abs"
      abs=$(wt_resolve "$abs")
      if wt_is_within "$abs" "$DEPLOY_ROOT"; then
        deny "main-checkout-guard: this command writes $abs inside the LIVE deploy clone ($DEPLOY_ROOT) — every agent's hooks, skills and landers execute from it, and one stray file there aborts every deploy on this machine. $DEPLOY_FIX"
      fi
      if [[ "$ENFORCE_MAIN" -eq 1 ]] && wt_is_main_checkout_path "$abs" "$MAIN_ROOT"; then
        deny "main-checkout-guard: this command writes $abs inside the SHARED main checkout ($MAIN_ROOT) — another session's uncommitted work lives there too. $FIX"
      fi
    done < <(wt_write_targets "$CMD")
    ;;
esac

exit 0
