#!/usr/bin/env bash
# Multi-agent worktree manager.
# EXIT: 0 ok, 1 preflight, 2 operation failed.
set -euo pipefail

trap 'echo "agent.sh: failed at line $LINENO (exit $?)" >&2' ERR

RESERVED_NAMES=(master main HEAD list merge clean new)
MAX_WORKTREES=6

die()    { echo "ERROR: $*" >&2; exit "${2:-2}"; }
prefail(){ echo "PREFLIGHT: $*" >&2; exit 1; }

# ── helpers ──────────────────────────────────────────────────────────────────

is_main_checkout() {
  local git_dir common_dir
  git_dir=$(git rev-parse --git-dir 2>/dev/null) || return 1
  common_dir=$(git rev-parse --git-common-dir 2>/dev/null) || return 1
  [ "$git_dir" = "$common_dir" ]
}

require_main_checkout() {
  is_main_checkout || prefail "must run from the main checkout, not a worktree"
}

require_master_branch() {
  local branch
  branch=$(git rev-parse --abbrev-ref HEAD)
  [ "$branch" = "master" ] || prefail "must be on master (currently on '$branch')"
}

require_clean_tree() {
  local dirty
  dirty=$(git status --porcelain)
  [ -z "$dirty" ] || prefail "working tree is not clean — stash or commit first"
}

worktree_paths() {
  # list worktree paths (excluding main); || true prevents grep exit-1 when empty
  local main_path
  main_path=$(git rev-parse --show-toplevel)
  git worktree list --porcelain \
    | awk '/^worktree /{p=$2} /^$/{print p}' \
    | grep -v "^${main_path}$" \
    || true
}

worktree_count() {
  worktree_paths | wc -l
}

branch_exists_local()  { git show-ref --verify --quiet "refs/heads/$1"; }
branch_exists_remote() { git ls-remote --exit-code origin "$1" >/dev/null 2>&1; }

utc_now() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }

age_str() {
  local created_utc="$1"
  local now_ts created_ts diff_s
  now_ts=$(date -u +%s)
  # strip Z, replace T with space for `date -d`
  created_ts=$(date -u -d "${created_utc//T/ }" +%s 2>/dev/null) || { echo "?"; return; }
  diff_s=$(( now_ts - created_ts ))
  if   (( diff_s < 60 ));   then echo "${diff_s}s"
  elif (( diff_s < 3600 )); then echo "$(( diff_s/60 ))m"
  elif (( diff_s < 86400 )); then echo "$(( diff_s/3600 ))h"
  else echo "$(( diff_s/86400 ))d"
  fi
}

validate_name() {
  local name="$1"
  if ! [[ "$name" =~ ^[a-z][a-z0-9-]*$ ]]; then
    prefail "name '$name' must match ^[a-z][a-z0-9-]*$"
  fi
  for r in "${RESERVED_NAMES[@]}"; do
    if [ "$name" = "$r" ]; then
      prefail "'$name' is a reserved word"
    fi
  done
}

# ── subcommands ───────────────────────────────────────────────────────────────

cmd_new() {
  local name="${1:-}"
  [ -n "$name" ] || { usage; exit 1; }

  validate_name "$name"
  require_main_checkout
  require_master_branch
  require_clean_tree

  # worktree limit
  local count
  count=$(worktree_count)
  if (( count >= MAX_WORKTREES )); then
    echo "ERROR: already at maximum of $MAX_WORKTREES active worktrees:" >&2
    cmd_list
    exit 1
  fi

  # branch collision
  if branch_exists_local "task/$name"; then
    prefail "branch task/$name already exists locally"
  fi
  git fetch origin master --quiet
  if branch_exists_remote "refs/heads/task/$name"; then
    prefail "branch task/$name already exists on origin"
  fi

  # directory collision
  local main_path wt_path
  main_path=$(git rev-parse --show-toplevel)
  wt_path="$(dirname "$main_path")/homelab-codex-ws-${name}"
  [ ! -e "$wt_path" ] || prefail "directory $wt_path already exists"

  # create worktree
  git worktree add -b "task/$name" "$wt_path" origin/master \
    || die "git worktree add failed"

  # write marker
  local parent_commit
  parent_commit=$(git rev-parse origin/master)
  cat > "$wt_path/.agent-task" <<EOF
task: $name
branch: task/$name
parent_commit: $parent_commit
created_utc: $(utc_now)
worktree_path: $wt_path
EOF

  echo ""
  echo "Worktree created: $wt_path"
  echo "Branch:           task/$name"
  echo ""
  echo "── Start Claude Code in this worktree ──────────────────────────────────────"
  echo "cd ~/homelab-codex-ws-${name} && claude --dangerously-skip-permissions \"Jesteś w worktree task '${name}' (branch task/${name}). NAJPIERW przeczytaj .agent-task i .claude/skills/worktree-aware/SKILL.md, dopiero potem zacznij pracę. Commituj wyłącznie na swoją gałąź; nie pushuj origin master.\""
  echo "─────────────────────────────────────────────────────────────────────────────"
}

cmd_list() {
  local main_path
  main_path=$(git rev-parse --show-toplevel)

  # fetch to get up-to-date ahead/behind
  git fetch origin master --quiet 2>/dev/null || true

  local paths
  paths=$(worktree_paths)

  if [ -z "$paths" ]; then
    echo "(no active task worktrees)"
    return
  fi

  printf "%-20s %-25s %-10s %-8s %-8s %-7s %s\n" \
    "NAME" "BRANCH" "CREATED" "AGE" "STATUS" "A/B" "PARENT"

  while IFS= read -r wt_path; do
    [ -z "$wt_path" ] && continue

    local marker="$wt_path/.agent-task"
    local task_name branch parent_commit created_utc
    if [ -f "$marker" ]; then
      task_name=$(  grep '^task:'          "$marker" | awk '{print $2}')
      branch=$(     grep '^branch:'        "$marker" | awk '{print $2}')
      parent_commit=$(grep '^parent_commit:' "$marker" | awk '{print $2}')
      created_utc=$(grep '^created_utc:'   "$marker" | awk '{print $2}')
    else
      task_name="(no marker)"
      branch=$(git -C "$wt_path" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "?")
      parent_commit="?"
      created_utc=""
    fi

    local status="clean"
    local dirty
    dirty=$(git -C "$wt_path" status --porcelain 2>/dev/null || echo "?")
    [ -n "$dirty" ] && status="dirty"

    local ahead behind ab
    ahead=$(git -C "$wt_path" rev-list --count "origin/master..${branch}" 2>/dev/null || echo "?")
    behind=$(git -C "$wt_path" rev-list --count "${branch}..origin/master" 2>/dev/null || echo "?")
    ab="+${ahead}/-${behind}"

    local age=""
    [ -n "$created_utc" ] && age=$(age_str "$created_utc")

    local short_parent="${parent_commit:0:7}"
    local short_created="${created_utc:0:10}"

    printf "%-20s %-25s %-10s %-8s %-8s %-7s %s\n" \
      "$task_name" "$branch" "$short_created" "$age" "$status" "$ab" "$short_parent"
  done <<< "$paths"
}

cmd_merge() {
  local name="${1:-}"
  [ -n "$name" ] || { usage; exit 1; }

  require_main_checkout
  require_master_branch
  require_clean_tree

  git fetch origin --quiet

  branch_exists_local "task/$name" || die "branch task/$name not found locally" 1

  local main_path wt_path
  main_path=$(git rev-parse --show-toplevel)
  wt_path="$(dirname "$main_path")/homelab-codex-ws-${name}"

  # attempt ff-only merge
  local merge_failed=0
  git merge --ff-only "task/$name" || merge_failed=1

  if (( merge_failed )); then
    # abort any partial merge state
    git merge --abort 2>/dev/null || true
    echo ""
    echo "ERROR: task/$name cannot be fast-forwarded into master." >&2
    echo "       The branch has likely diverged from master." >&2
    echo "" >&2
    echo "Diagnose with:" >&2
    echo "  git log master..task/$name        # commits only on task branch" >&2
    echo "  git log task/$name..master        # commits master has that task doesn't" >&2
    echo "" >&2
    echo "Then decide: rebase task/$name onto master, or merge manually." >&2
    echo "Worktree and branch are preserved — no changes made." >&2
    exit 2
  fi

  echo "Merged task/$name into master (fast-forward)."

  git push origin master || die "git push origin master failed"
  echo "Pushed master to origin."

  if [ -d "$wt_path" ]; then
    git worktree remove "$wt_path" || die "git worktree remove $wt_path failed"
    echo "Removed worktree: $wt_path"
  else
    echo "(worktree directory $wt_path not found — skipping worktree remove)"
  fi

  git branch -d "task/$name" || die "git branch -d task/$name failed"
  echo "Deleted local branch task/$name."

  git push origin --delete "task/$name" 2>/dev/null \
    && echo "Deleted remote branch task/$name." \
    || echo "(remote branch task/$name not found — nothing to delete)"

  echo ""
  echo "Done. task/$name merged and cleaned up."
}

cmd_clean() {
  local main_path
  main_path=$(git rev-parse --show-toplevel)
  git fetch origin --quiet 2>/dev/null || true

  local to_remove=()

  # orphaned registered worktrees: branch deleted or fully merged into master
  local paths
  paths=$(worktree_paths)
  while IFS= read -r wt_path; do
    [ -z "$wt_path" ] && continue
    local branch
    branch=$(git -C "$wt_path" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
    [ -z "$branch" ] && { to_remove+=("worktree:$wt_path (unreadable branch)"); continue; }

    # branch gone locally?
    if ! branch_exists_local "$branch"; then
      to_remove+=("worktree:$wt_path (branch $branch no longer exists)")
      continue
    fi

    # branch fully merged into master?
    local ahead
    ahead=$(git rev-list --count "origin/master..${branch}" 2>/dev/null || echo "1")
    if [ "$ahead" = "0" ]; then
      to_remove+=("worktree:$wt_path (branch $branch fully merged into origin/master)")
    fi
  done <<< "$paths"

  # dangling directories: ../homelab-codex-ws-* not registered
  local registered_paths
  registered_paths=$(git worktree list --porcelain | awk '/^worktree /{print $2}')
  local parent_dir
  parent_dir=$(dirname "$main_path")
  while IFS= read -r candidate; do
    [ -d "$candidate" ] || continue
    if ! echo "$registered_paths" | grep -qF "$candidate"; then
      to_remove+=("dangling:$candidate")
    fi
  done < <(find "$parent_dir" -maxdepth 1 -name "homelab-codex-ws-*" -type d 2>/dev/null)

  if [ ${#to_remove[@]} -eq 0 ]; then
    echo "Nothing to clean."
    return 0
  fi

  echo "Found ${#to_remove[@]} item(s) to clean:"
  for entry in "${to_remove[@]}"; do
    echo "  $entry"
  done
  echo ""

  local overall_rc=0
  for entry in "${to_remove[@]}"; do
    local kind="${entry%%:*}"
    local path="${entry#*:}"
    # strip trailing annotation in parens
    local raw_path
    raw_path="${path%% (*}"

    local confirm
    read -r -p "Remove $kind '$raw_path'? [y/N] " confirm
    if [[ "$confirm" =~ ^[Yy]$ ]]; then
      if [ "$kind" = "worktree" ]; then
        git worktree remove --force "$raw_path" 2>/dev/null \
          || { echo "  WARNING: git worktree remove failed, trying rm -rf"; rm -rf "$raw_path" || true; }
      else
        rm -rf "$raw_path"
      fi
      echo "  Removed."
    else
      echo "  Skipped."
    fi
  done

  return $overall_rc
}

usage() {
  cat <<'EOF'
Usage: agent.sh <subcommand> [args]

  agent.sh new <name>    Create a new task worktree (branch task/<name>)
  agent.sh list          List active task worktrees with status
  agent.sh merge <name>  Fast-forward merge task/<name> into master and clean up
  agent.sh clean         Remove orphaned or dangling worktrees (interactive)

EXIT: 0 ok, 1 preflight, 2 operation failed.
EOF
}

# ── dispatch ──────────────────────────────────────────────────────────────────

SUBCOMMAND="${1:-}"
shift || true

case "$SUBCOMMAND" in
  new)   cmd_new   "$@" ;;
  list)  cmd_list  "$@" ;;
  merge) cmd_merge "$@" ;;
  clean) cmd_clean "$@" ;;
  *)     usage; exit 1  ;;
esac
