#!/usr/bin/env bash
# br-find-all - Search and browse issues across repositories
# Usage: br-find-all [--all] [--current]
#   --current  Search from current directory (default)
#   --all      Search from home directory
#   If no tracker data is found in current scope, falls back to --all

set -euo pipefail

# Colors (terminal-agnostic ANSI)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
CYAN='\033[0;36m'
GRAY='\033[0;90m'
BOLD='\033[1m'
RESET='\033[0m'

# Check dependencies
for cmd in fd jq fzf gum br; do
  if ! command -v "$cmd" &>/dev/null; then
    echo -e "${RED}Error: $cmd not found${RESET}" >&2
    exit 1
  fi
done

# Clipboard command (macOS vs Linux)
if command -v pbcopy &>/dev/null; then
  CLIP_CMD="pbcopy"
elif command -v xclip &>/dev/null; then
  CLIP_CMD="xclip -selection clipboard"
elif command -v wl-copy &>/dev/null; then
  CLIP_CMD="wl-copy"
else
  CLIP_CMD=""
fi

# Get script path for reload bindings
SCRIPT_PATH="${BASH_SOURCE[0]}"

# Handle --format-only mode FIRST (for reload bindings)
# This must be before any other processing
if [[ "${1:-}" == "--format-only" ]]; then
  # Read JSON from stdin and format for fzf display
  jq -r '
    def status_color:
      if . == "open" then "32"
      elif . == "in_progress" then "33"
      elif . == "blocked" then "31"
      elif . == "closed" then "90"
      else "0"
      end;
    
    def extract_hash:
      split("-") | last;

    def clean_title:
      (.title // "untitled")
      | gsub("[\\r\\n\\t]+"; " ")
      | gsub("  +"; " ");
    
    .[] |
    "\(.repo_name)\t\(.id)\t\(clean_title)\t\(.status)\t\(.priority)\t\(.issue_type // "task")\t\(.repo_path)\t\u001b[36m\(.repo_name)\u001b[0m \u001b[90m│\u001b[0m \(clean_title) \u001b[90m│\u001b[0m \u001b[\(.status | status_color)m\(.id | extract_hash)\u001b[0m"
  '
  exit 0
fi

# Parse arguments
SCOPE="current"
while [[ $# -gt 0 ]]; do
  case "$1" in
    --all|-a)
      SCOPE="all"
      shift
      ;;
    --current|-c)
      SCOPE="current"
      shift
      ;;
    -h|--help)
      echo "Usage: br-find-all [--all] [--current]"
      echo ""
      echo "Search and browse issues across repositories."
      echo ""
      echo "Options:"
      echo "  --current, -c  Search from current directory (default)"
      echo "  --all, -a      Search from home directory"
      echo ""
      echo "Keybindings in fzf:"
      echo "  Enter     Copy issue ID to clipboard"
      echo "  Ctrl-O    Filter: open issues"
      echo "  Ctrl-P    Filter: in_progress issues"
      echo "  Ctrl-B    Filter: blocked issues"
      echo "  Ctrl-A    Clear all filters"
      echo "  Ctrl-R    Filter: ready issues (no blockers)"
      echo "  Alt-1     Filter: high priority (P0-P1)"
      echo "  Alt-2     Filter: medium priority (P2)"
      echo "  Alt-3     Filter: low priority (P3-P4)"
      echo "  Alt-F     Filter: features"
      echo "  Alt-T     Filter: tasks"
      echo "  Alt-G     Filter: bugs"
      echo "  Esc       Cancel"
      exit 0
      ;;
    *)
      echo -e "${RED}Unknown option: $1${RESET}" >&2
      exit 1
      ;;
  esac
done

# Set search root based on scope
if [[ "$SCOPE" == "current" ]]; then
  SEARCH_ROOT="$PWD"
else
  SEARCH_ROOT="$HOME"
fi

# Discover .beads directories
discover_beads() {
  local root="$1"
  fd -t d -H "^\.beads$" "$root" -d 5 \
    --exclude .cache \
    --exclude .Trash \
    --exclude Library \
    --exclude node_modules \
    --exclude .git \
    --exclude venv \
    --exclude __pycache__ \
    --exclude target \
    --exclude build \
    --exclude dist \
    --exclude .vscode \
    --exclude .idea \
    --exclude .bun \
    --exclude .claude/plugins \
    --exclude .config/claude/plugins \
    --exclude .config/opencode/plugin \
    --exclude "*/plugins/*" \
    --exclude "*/plugin/*" \
    2>/dev/null
}

resolve_repo_dir() {
  local repo_dir="$1"
  local repo_base
  repo_base=$(basename "$repo_dir")

  if [[ "$repo_base" != *".bd-"* && "$repo_base" != *".wt-"* ]]; then
    echo "$repo_dir"
    return
  fi

  if ! git -C "$repo_dir" rev-parse --git-dir >/dev/null 2>&1; then
    echo "$repo_dir"
    return
  fi

  local entry_path=""
  local entry_branch=""
  local first_path=""
  local main_path=""

  while IFS= read -r line; do
    if [[ -z "$line" ]]; then
      if [[ -n "$entry_path" ]]; then
        if [[ "$entry_branch" =~ refs/heads/(main|master)$ ]]; then
          main_path="$entry_path"
          break
        fi
        if [[ -z "$first_path" ]]; then
          first_path="$entry_path"
        fi
      fi
      entry_path=""
      entry_branch=""
      continue
    fi

    case "$line" in
      worktree\ *) entry_path="${line#worktree }" ;;
      branch\ *) entry_branch="${line#branch }" ;;
    esac
  done < <(git -C "$repo_dir" worktree list --porcelain)

  if [[ -z "$main_path" && -n "$entry_path" ]]; then
    if [[ "$entry_branch" =~ refs/heads/(main|master)$ ]]; then
      main_path="$entry_path"
    elif [[ -z "$first_path" ]]; then
      first_path="$entry_path"
    fi
  fi

  if [[ -z "$main_path" ]]; then
    main_path="$first_path"
  fi

  if [[ -n "$main_path" ]]; then
    echo "$main_path"
    return
  fi

  echo "$repo_dir"
}

# Find beads directories
mapfile -t BEADS_DIRS < <(discover_beads "$SEARCH_ROOT")

# Fallback to --all if no tracker data is found in current scope
if [[ ${#BEADS_DIRS[@]} -eq 0 && "$SCOPE" == "current" ]]; then
  SEARCH_ROOT="$HOME"
  mapfile -t BEADS_DIRS < <(discover_beads "$SEARCH_ROOT")
  if [[ ${#BEADS_DIRS[@]} -gt 0 ]]; then
    gum style --foreground 208 "No tracker data in current directory, searching home..."
  fi
fi

# Exit if still no beads found
if [[ ${#BEADS_DIRS[@]} -eq 0 ]]; then
  gum style --foreground 208 "No .beads directories found"
  exit 0
fi

# Collect all issues from all repos (parallel)
collect_issues() {
  local tmpdir
  tmpdir=$(mktemp -d)
  trap 'rm -rf "$tmpdir"' RETURN

  # Deduplicate repos first
  local seen_repos=()
  local unique_repos=()
  for beads_dir in "${BEADS_DIRS[@]}"; do
    local repo_dir
    repo_dir=$(dirname "$beads_dir")
    repo_dir=$(resolve_repo_dir "$repo_dir")

    local already_seen=0
    for seen_repo in "${seen_repos[@]}"; do
      if [[ "$seen_repo" == "$repo_dir" ]]; then
        already_seen=1
        break
      fi
    done
    if [[ "$already_seen" -eq 1 ]]; then continue; fi
    seen_repos+=("$repo_dir")
    unique_repos+=("$repo_dir")
  done

  # Fetch repos in parallel (capped at 8 concurrent to avoid overwhelming dolt)
  local i=0 max_jobs=8
  local pids=()
  for repo_dir in "${unique_repos[@]}"; do
    local repo_name
    repo_name=$(basename "$repo_dir")
    (
      issues=$(cd "$repo_dir" && timeout 10 br list --json 2>/dev/null | jq -c '.issues // []' 2>/dev/null || echo "[]")
      echo "$issues" | jq --arg repo "$repo_name" --arg path "$repo_dir" \
        'map(. + {repo_name: $repo, repo_path: $path})' > "$tmpdir/$i.json" 2>/dev/null
    ) &
    pids+=($!)
    ((i++))
    # Throttle: wait when we hit max_jobs
    if (( i % max_jobs == 0 )); then
      for pid in "${pids[@]}"; do wait "$pid" 2>/dev/null || true; done
      pids=()
    fi
  done
  for pid in "${pids[@]}"; do wait "$pid" 2>/dev/null || true; done

  # Single merge of all results
  jq -s 'add // []' "$tmpdir"/*.json 2>/dev/null || echo "[]"
}

# Format issues for fzf display
format_for_fzf() {
  local issues="$1"
  
  # Format: repo_name \t id \t title \t status \t priority \t type \t repo_path \t display_line
  echo "$issues" | jq -r '
    def status_color:
      if . == "open" then "32"        # green
      elif . == "in_progress" then "33"  # yellow
      elif . == "blocked" then "31"   # red
      elif . == "closed" then "90"    # gray
      else "0"
      end;
    
    def extract_hash:
      split("-") | last;

    def clean_title:
      (.title // "untitled")
      | gsub("[\\r\\n\\t]+"; " ")
      | gsub("  +"; " ");
    
    .[] |
    "\(.repo_name)\t\(.id)\t\(clean_title)\t\(.status)\t\(.priority)\t\(.issue_type // "task")\t\(.repo_path)\t\u001b[36m\(.repo_name)\u001b[0m \u001b[90m│\u001b[0m \(clean_title) \u001b[90m│\u001b[0m \u001b[\(.status | status_color)m\(.id | extract_hash)\u001b[0m"
  '
}

# Preview function for selected issue
generate_preview_script() {
  cat << 'PREVIEW_EOF'
#!/usr/bin/env bash
ID="$1"
REPO_PATH="$2"

if [[ -z "$ID" || -z "$REPO_PATH" ]]; then
  echo "No issue selected"
  exit 0
fi

cd "$REPO_PATH" 2>/dev/null || exit 1

# Get issue details and render preview in a single jq call
br show "$ID" --json 2>/dev/null | jq -r --arg id "$ID" --arg repo "$REPO_PATH" '
  .[0] // empty |
  def sep: "\u001b[1;36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m";
  def g: "\u001b[90m";
  def b: "\u001b[1m";
  def r: "\u001b[0m";

  sep, (b + (.title // "untitled") + r), sep, "",

  (g + "ID:" + r + "       " + $id),
  (g + "Status:" + r + "   " + (.status // "unknown")),
  (g + "Priority:" + r + " P" + ((.priority // 2) | tostring)),
  (g + "Type:" + r + "     " + (.issue_type // "task")),
  (g + "Assignee:" + r + " " + (.owner // "unassigned")),
  (g + "Repo:" + r + "     " + $repo),
  "",

  (if (.description // "") != "" then
    b + "Description:" + r + "\n" + (.description | split("\n")[0:20] | join("\n")) + "\n"
  else empty end),

  (if ((.dependencies // []) | length) > 0 then
    b + "Blocked by:" + r + " (" + ((.dependencies // []) | length | tostring) + ")\n" +
    ([.dependencies[]? | "  - \(.id): \(.title // "untitled")"][0:5] | join("\n")) + "\n"
  else empty end),

  (if ((.labels // []) | length) > 0 then
    g + "Labels:" + r + " " + ((.labels // []) | join(", "))
  else empty end),

  (if (.created_at // "") != "" then
    g + "Created:" + r + "  " + (.created_at | split("T")[0])
  else empty end),

  (if (.updated_at // "") != "" then
    g + "Updated:" + r + "  " + (.updated_at | split("T")[0])
  else empty end)
' 2>/dev/null || echo "Issue not found: $ID"
PREVIEW_EOF
}

# Create temp preview script
PREVIEW_SCRIPT=$(mktemp)
generate_preview_script > "$PREVIEW_SCRIPT"
chmod +x "$PREVIEW_SCRIPT"
trap 'rm -f "$PREVIEW_SCRIPT"' EXIT

# Collect and format issues
gum spin --spinner dot --title "Searching for issues..." -- sleep 0.1 &
ALL_ISSUES=$(collect_issues)
wait

ISSUE_COUNT=$(echo "$ALL_ISSUES" | jq 'length')

if [[ "$ISSUE_COUNT" -eq 0 ]]; then
  gum style --foreground 208 "No issues found in ${#BEADS_DIRS[@]} repositories"
  exit 0
fi

# Store formatted issues in temp file for reload
ISSUES_FILE=$(mktemp)
echo "$ALL_ISSUES" > "$ISSUES_FILE"
trap 'rm -f "$PREVIEW_SCRIPT" "$ISSUES_FILE"' EXIT

# Format for initial display
FORMATTED=$(format_for_fzf "$ALL_ISSUES")

# Build header
HEADER="Enter=copy | ^O=open ^P=prog ^B=blocked ^A=all ^R=ready | M-1/2/3=prio | M-f/t/g=type"

# Run fzf
SELECTED=$(echo "$FORMATTED" | fzf \
  --ansi \
  --height 100% \
  --layout reverse \
  --border rounded \
  --prompt "Issues ($ISSUE_COUNT): " \
  --header "$HEADER" \
  --delimiter '\t' \
  --with-nth 8 \
  --preview "$PREVIEW_SCRIPT {2} {7}" \
  --preview-window 'right:55%:wrap' \
  --bind "ctrl-o:reload(cat '$ISSUES_FILE' | jq 'map(select(.status==\"open\"))' | '$SCRIPT_PATH' --format-only)" \
  --bind "ctrl-p:reload(cat '$ISSUES_FILE' | jq 'map(select(.status==\"in_progress\"))' | '$SCRIPT_PATH' --format-only)" \
  --bind "ctrl-b:reload(cat '$ISSUES_FILE' | jq 'map(select(.status==\"blocked\"))' | '$SCRIPT_PATH' --format-only)" \
  --bind "ctrl-r:reload(cat '$ISSUES_FILE' | jq 'map(select(.dependency_count==0 and .status!=\"closed\"))' | '$SCRIPT_PATH' --format-only)" \
  --bind "ctrl-a:reload(cat '$ISSUES_FILE' | jq '.' | '$SCRIPT_PATH' --format-only)" \
  --bind "alt-1:reload(cat '$ISSUES_FILE' | jq 'map(select(.priority <= 1))' | '$SCRIPT_PATH' --format-only)" \
  --bind "alt-2:reload(cat '$ISSUES_FILE' | jq 'map(select(.priority == 2))' | '$SCRIPT_PATH' --format-only)" \
  --bind "alt-3:reload(cat '$ISSUES_FILE' | jq 'map(select(.priority >= 3))' | '$SCRIPT_PATH' --format-only)" \
  --bind "alt-f:reload(cat '$ISSUES_FILE' | jq 'map(select(.issue_type==\"feature\"))' | '$SCRIPT_PATH' --format-only)" \
  --bind "alt-t:reload(cat '$ISSUES_FILE' | jq 'map(select(.issue_type==\"task\"))' | '$SCRIPT_PATH' --format-only)" \
  --bind "alt-g:reload(cat '$ISSUES_FILE' | jq 'map(select(.issue_type==\"bug\"))' | '$SCRIPT_PATH' --format-only)" \
  || true)

# Handle selection
if [[ -n "$SELECTED" ]]; then
  ISSUE_ID=$(echo "$SELECTED" | cut -f2)
  
  if [[ -n "$CLIP_CMD" ]]; then
    echo -n "$ISSUE_ID" | $CLIP_CMD
    gum style --foreground 212 "Copied $ISSUE_ID to clipboard"
  else
    echo "$ISSUE_ID"
    gum style --foreground 208 "No clipboard available, ID printed above"
  fi
fi
