#!/bin/bash
# Auto-cleanup worktree when a tmux window closes.
# Called by tmux pane-exited hook with: session_name window_name
#
# If the closed window was in a worktree:
#   - Clean (no commits, no uncommitted): silently remove
#   - Dirty: leave a sticky note instead of prompting

set -euo pipefail

SESSION="${1:-}"
WINDOW="${2:-}"
MUX_LIB="${HOME}/.config/mux/muxlib.py"
MUX_WORKTREES_DIR="${HOME}/.mux/worktrees"

[[ -n "$SESSION" && -n "$WINDOW" ]] || exit 0

# Check if this window was a worktree
is_wt=$(python3 "$MUX_LIB" worktree-is-active "$SESSION" "$WINDOW" 2>/dev/null || echo "no")
[[ "$is_wt" == "yes" ]] || exit 0

proj_path=$(python3 "$MUX_LIB" project-path "$SESSION" 2>/dev/null) || exit 0
wt_path="${MUX_WORKTREES_DIR}/${SESSION}-${WINDOW}"
branch_name="mux/${WINDOW}"

[[ -d "$wt_path" ]] || exit 0

# Check for real changes via the shared dirty detection — single source of
# truth in worktree-dirty-check.sh (`mux worktree cleanup` delegates to the
# same helper, so both paths reach the same verdict from one implementation).
#
# FAIL-DIRTY: this hook silently DELETES worktrees, so a failed or missing
# check must never authorize a deletion. If the helper is absent, errors, or
# its output is unparseable, we classify the worktree dirty and fall through
# to the note-leaving branch. Every fallible step is guarded so that under
# set -e this degrades to fail-DIRTY rather than aborting silently.
DIRTY_CHECK="${HOME}/.config/mux/worktree-dirty-check.sh"
dirty_line=""
if [[ -x "$DIRTY_CHECK" ]]; then
  dirty_line=$("$DIRTY_CHECK" "$wt_path" "$proj_path" 2>/dev/null) || dirty_line=""
fi
has_commits="?"
has_uncommitted="?"
verdict="dirty"
for kv in $dirty_line; do
  case "$kv" in
    commits=*) has_commits="${kv#commits=}" ;;
    uncommitted=*) has_uncommitted="${kv#uncommitted=}" ;;
    verdict=*) verdict="${kv#verdict=}" ;;
  esac
done

if [[ "$verdict" == "clean" ]]; then
  # Clean — silently remove
  # Clean up project identity symlink
  projects_dir="$HOME/.claude/projects"
  wt_slug=$(echo "$wt_path" | sed 's|[/.]|-|g')
  [[ -L "$projects_dir/$wt_slug" ]] && rm -f "$projects_dir/$wt_slug"

  # Reap the lane's docker containers BEFORE the worktree dir (the compose
  # project name source) disappears — single implementation in
  # worktree-session-health.sh --reap, same mode bin/mux's removal paths use.
  # Guarded: fail-open under set -e, a failed reap never blocks the removal.
  # This hook's stdout goes nowhere (tmux discards it), so an incomplete
  # reap routes to the desk's sticky-note channel — a leak on the unattended
  # path must never be silent.
  HEALTH_CHECK="${HOME}/.config/mux/worktree-session-health.sh"
  if [[ -x "$HEALTH_CHECK" ]]; then
    reap_out=$("$HEALTH_CHECK" "$proj_path" "$wt_path" --reap 2>/dev/null) || reap_out=""
    if [[ "$reap_out" == *"reap INCOMPLETE"* || "$reap_out" == *"daemon unreachable"* ]]; then
      python3 "${HOME}/.config/mux/manage-notes.py" add "$SESSION" \
        "⚠ Container reap incomplete for closed worktree '${WINDOW}'. Check: docker ps --filter label=com.docker.compose.project=$(basename "$wt_path" | tr '[:upper:]' '[:lower:]')" \
        2>/dev/null || true
    fi
  fi

  git -C "$proj_path" worktree remove "$wt_path" 2>/dev/null || rm -rf "$wt_path"
  git -C "$proj_path" branch -d "$branch_name" 2>/dev/null || true
  python3 "$MUX_LIB" worktree-remove "$SESSION" "$WINDOW" 2>/dev/null || true
else
  # Dirty — create watchtower inbox item + sticky note.
  # Counts may be "?" when the check could not determine them (fail-DIRTY).
  detail=""
  if [[ "$has_uncommitted" != "0" && "$has_uncommitted" != "?" ]]; then
    detail="${has_uncommitted} uncommitted"
  fi
  if [[ "$has_commits" != "0" && "$has_commits" != "?" ]]; then
    detail="${detail:+$detail, }${has_commits} commit(s)"
  fi
  if [[ -z "$detail" ]]; then
    detail="changes the dirty-check could not verify (treated as dirty)"
  fi

  # Earned-urgency split, mirroring ring1's worktree-unmerged registers
  # (noise-immunity merge-tail reconciliation): data-loss urgency is earned
  # by unmerged COMMITS; a fully-merged branch with only uncommitted files
  # is a normal review nudge. An unverifiable commit count ("?") stays
  # urgent — fail toward attention, never toward silence.
  if [[ "$has_commits" == "0" ]]; then
    wt_urgency="normal"
    wt_unmerged="false"
    wt_title="Worktree \"${branch_name}\" closed with uncommitted files"
    wt_summary="Window closed with ${detail}. The branch itself is fully merged — review or commit the files. Run: mux worktree cleanup ${SESSION} ${WINDOW}"
  else
    wt_urgency="urgent"
    wt_unmerged="true"
    wt_title="Worktree \"${branch_name}\" closed with unmerged work"
    wt_summary="Window closed with ${detail}. Merge to main or the work may be lost. Run: mux worktree cleanup ${SESSION} ${WINDOW}"
  fi

  # Watchtower inbox item (if watchtower is installed)
  WATCHTOWER_QUEUE="${HOME}/.claude-cabinet/watchtower/scripts/watchtower-queue.mjs"
  WATCHTOWER_LIB="${HOME}/.claude-cabinet/watchtower/scripts/watchtower-lib.mjs"
  if [[ -f "$WATCHTOWER_QUEUE" ]]; then
    NODE_BIN="${WATCHTOWER_NODE_PATH:-$(command -v node 2>/dev/null || true)}"
    if [[ -n "$NODE_BIN" ]]; then
      # All dynamic values reach node via ENV, never spliced into the JS
      # source: an apostrophe in a window/desk/branch name was a silent
      # SyntaxError (the '|| true' swallowed it — the urgent item never
      # filed, only the sticky note survived), and a crafted name was JS
      # injection. Same class ring1's CP3 commit closed for filenames
      # (QA drain 2026-07-13). Only $HOME-derived import paths are spliced.
      MUX_PROJ_PATH="$proj_path" MUX_SESSION="$SESSION" MUX_WINDOW="$WINDOW" \
      MUX_BRANCH="$branch_name" MUX_WT_PATH="$wt_path" \
      MUX_URGENCY="$wt_urgency" MUX_TITLE="$wt_title" MUX_SUMMARY="$wt_summary" \
      MUX_UNMERGED="$wt_unmerged" \
      "$NODE_BIN" --input-type=module -e "
        import { createItem, listPending } from '${WATCHTOWER_QUEUE}';
        // project must be the watchtower config key, not the mux desk name —
        // /inbox and the rings group by config key; desk-name keying
        // ('cabinet' vs 'claude-cabinet') filed items nobody could find.
        // The desk name is preserved in its own field.
        // Dynamic import with fallback: this script (mux-setup) and the lib
        // (/watchtower install) ship on different installers — a static named
        // import against an older lib without the export is an ESM hard
        // failure that would kill the whole filing silently. Degrade to the
        // old desk-name keying instead; the key migration re-keys it later.
        let resolveProjectIdentity, loadConfig;
        try { ({ resolveProjectIdentity, loadConfig } = await import('${WATCHTOWER_LIB}')); } catch {}
        let config = null;
        try { config = loadConfig?.() ?? null; } catch {}
        const env = process.env;
        const identity = resolveProjectIdentity?.(env.MUX_PROJ_PATH, config) ?? null;
        const existing = listPending({ category: 'worktree-unmerged' });
        const isDup = existing.some(i =>
          i.evidence?.branch === env.MUX_BRANCH &&
          i.evidence?.worktree_path === env.MUX_WT_PATH
        );
        if (!isDup) {
          createItem({
            project: identity?.name || env.MUX_SESSION,
            project_path: identity?.path || env.MUX_PROJ_PATH,
            desk: env.MUX_SESSION,
            ...(identity ? {} : { project_unresolved: true }),
            filed_by: 'pane-close',
            category: 'worktree-unmerged',
            urgency: env.MUX_URGENCY,
            title: env.MUX_TITLE,
            summary: env.MUX_SUMMARY,
            context_anchor: 'git log main..' + env.MUX_BRANCH + ' in ' + env.MUX_WT_PATH,
            evidence: { branch: env.MUX_BRANCH, worktree_path: env.MUX_WT_PATH, window: env.MUX_WINDOW, unmerged: env.MUX_UNMERGED === 'true' },
            options: [
              { key: 'merge', label: 'Merge to main now' },
              { key: 'keep', label: 'Keep branch for later' },
              { key: 'dismiss', label: 'Dismiss (already handled)' },
            ],
          });
        }
      " 2>/dev/null || true
    fi
  fi

  # Sticky note as fallback
  python3 "${HOME}/.config/mux/manage-notes.py" add "$SESSION" \
    "⚠ Worktree '${WINDOW}' has ${detail}. Run: mux worktree cleanup ${SESSION} ${WINDOW}" \
    2>/dev/null || true
fi
