#!/usr/bin/env bash
# od-worktree — create/remove git worktrees INSIDE the repo, never in $HOME.
#
# CONTRACT
#   od-worktree add <name> [<committish>]   -> <repo-root>/.worktrees/<name> on branch wt/<name>
#   od-worktree list                        -> every worktree of this repo, flagging out-of-repo ones
#   od-worktree remove <name> [--force]
#   od-worktree adopt <path>                -> move an existing out-of-repo worktree into .worktrees/
#
# <name> is a bare slug — never a path. A path, an absolute destination, or any escape via ".."
# is refused. There is no flag to place a worktree outside the repo.

set -euo pipefail

die() { printf 'od-worktree: %s\n' "$1" >&2; exit 2; }

REPO_ROOT="$(git rev-parse --path-format=absolute --show-toplevel 2>/dev/null)" || die "not inside a git repository"
COMMON_DIR="$(git rev-parse --path-format=absolute --git-common-dir)"
MAIN_ROOT="$(cd "$COMMON_DIR/.." && pwd)"
WT_DIR="$MAIN_ROOT/.worktrees"
DISK_FLOOR_KIB=${OD_WORKTREE_MIN_FREE_KIB:-$((1024 * 1024))}
[[ "$DISK_FLOOR_KIB" =~ ^[0-9]{1,12}$ ]] || die "OD_WORKTREE_MIN_FREE_KIB must be an integer from 0 to 999999999999 KiB"
DISK_FLOOR_KIB=$((10#$DISK_FLOOR_KIB))

admit_disk() {
  local free_kib
  free_kib=$(df -Pk "$MAIN_ROOT" | awk 'NR == 2 { print $4 }') || die "disk capacity unknown for $MAIN_ROOT"
  [[ "$free_kib" =~ ^[0-9]+$ ]] || die "disk capacity unknown for $MAIN_ROOT"
  if ((free_kib < DISK_FLOOR_KIB)); then
    printf 'od-worktree: disk admission refused on %s: free=%s KiB required=%s KiB\n' "$MAIN_ROOT" "$free_kib" "$DISK_FLOOR_KIB" >&2
    exit 75
  fi
}

# shim-drift-check runs from a deploy clone with no link back to a dev checkout, so the
# checkouts that hold worktrees are recorded where it can find them.
register_checkout() {
  local file="${OD_SHIM_REPOS_FILE:-$HOME/.local/state/overdeck/shim-repos}" tmp ok=1
  if [[ -r "$file" ]] && grep -qxF "$MAIN_ROOT" "$file"; then
    return 0
  fi
  tmp="$file.$$"
  if mkdir -p "$(dirname "$file")"; then
    if [[ -f "$file" ]]; then
      cp "$file" "$tmp" || ok=0
    else
      : >"$tmp" || ok=0
    fi
    if ((ok)); then
      printf '%s\n' "$MAIN_ROOT" >>"$tmp" && mv "$tmp" "$file" || ok=0
    fi
  else
    ok=0
  fi
  if ((ok == 0)); then
    rm -f "$tmp"
    printf 'od-worktree: cannot record %s in %s — shim-drift-check will not see this checkout\n' "$MAIN_ROOT" "$file" >&2
  fi
}
register_checkout

require_slug() {
  local name="${1:-}"
  [[ -n "$name" ]] || die "name required"
  [[ "$name" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || die "name must be a bare slug (got '$name'); worktrees are always created under $WT_DIR"
}

cmd_add() {
  local name="${1:-}" base="${2:-}"
  require_slug "$name"
  local dest="$WT_DIR/$name"
  [[ -e "$dest" ]] && die "$dest already exists"
  admit_disk
  mkdir -p "$WT_DIR"
  if [[ -z "$base" ]]; then
    base="$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null || echo origin/main)"
  fi
  local branch="wt/$name"
  if git show-ref --verify --quiet "refs/heads/$branch"; then
    git worktree add "$dest" "$branch"
  else
    git worktree add -b "$branch" "$dest" "$base"
  fi
  associate_request
  printf '%s\n' "$dest"
}

# S4: a worktree claim, by itself, is not enough evidence to CREATE a request row with an
# owner's-words title (no paraphrase — see docs/plans/2026-08-14-request-intake.md S1). The
# worktree SLUG is a stable, deterministic identity for "this piece of work" though, and is
# passed as the request's work key (`--work-key`, the extended `plan_ref` seam — see
# requests-dedup.ts's findMatch): a matching key wins over title similarity, always.
#
# 2026-08-16, first fix: the original design gated row-creation on "the project has ANY other
# open `asked` row at all" (claimed or not) — meant to protect against duplicating an obvious
# single match, but in a project that permanently carries dozens of open rows (overdeck itself)
# that condition was ALWAYS true, so this branch never created a row, ever.
#
# 2026-08-16, second fix (supersedes the first): the first fix replaced the row-count gate with
# a client-side "list, then count unclaimed rows" pre-check. Owner ruling: never pre-check by
# listing rows at all — attempt the write with the work key and let the server's own exact-key
# dedup (409) be the ONLY convergence mechanism, the same lesson `/fire`'s exact-signature claim
# already encodes. `od-requests add --work-key "$slug"` either creates a fresh row (201, then
# claimed) or 409s on an EXACT key match (parsed from the CLI's own "already tracked: #<id>"
# line, then that row is associated/claimed instead) — never a fuzzy title guess either way.
#
# Named behavior change from the pre-work_key design: this writer no longer associates with a
# pre-existing OWNER-authored row that merely resembles this worktree's slug by title alone (an
# owner row has no worktree-slug work key to match against) — deterministic key-or-create is the
# whole contract now, per the owner's explicit "never pre-check by listing" ruling.
associate_request() {
  local project od_requests slug title output id
  project="$(basename "$MAIN_ROOT")"
  slug="$name"
  od_requests="$(command -v od-requests 2>/dev/null || true)"
  [[ -n "$od_requests" ]] || return 0
  title="Agent started work on: ${slug//[-_]/ }"
  local -a attribution=()
  [[ -n ${CLAUDE_SESSION_ID:-} ]] && attribution+=(--session-id "$CLAUDE_SESSION_ID")
  output="$("$od_requests" add "$title" --project "$project" --origin agent-judgement --work-key "$slug" "${attribution[@]}" 2>/dev/null)" || true
  id="$(printf '%s' "$output" | grep -oE '#[^ ;]+' | head -n1 | cut -d'#' -f2)"
  [[ -n "$id" ]] || return 0
  "$od_requests" claim "$id" "${attribution[@]}" >/dev/null 2>&1 || true
}

cmd_list() {
  git worktree list --porcelain | awk -v root="$MAIN_ROOT" '
    /^worktree /{
      path = substr($0, 10)
      inside = (index(path, root "/") == 1 || path == root)
      printf "%s%s\n", path, inside ? "" : "   <- OUTSIDE THE REPO"
    }'
}

cmd_remove() {
  local name="${1:-}"; shift || true
  require_slug "$name"
  git worktree remove "$WT_DIR/$name" "$@"
}

cmd_adopt() {
  local src="${1:-}"
  [[ -n "$src" ]] || die "path required"
  src="$(cd "$src" && pwd)" || die "no such directory"
  [[ "$src" == "$MAIN_ROOT" ]] && die "refusing to move the main checkout"
  case "$src" in "$MAIN_ROOT"/*) die "$src is already inside the repo";; esac
  git worktree list --porcelain | grep -qx "worktree $src" || die "$src is not a worktree of this repo"
  mkdir -p "$WT_DIR"
  local dest="$WT_DIR/$(basename "$src")"
  [[ -e "$dest" ]] && die "$dest already exists"
  git worktree move "$src" "$dest"
  printf '%s\n' "$dest"
}

case "${1:-}" in
  add)    shift; cmd_add "$@";;
  list)   shift; cmd_list "$@";;
  remove) shift; cmd_remove "$@";;
  adopt)  shift; cmd_adopt "$@";;
  *) die "usage: od-worktree {add <name> [<base>]|list|remove <name>|adopt <path>}";;
esac
