#!/usr/bin/env bash
# opc-agents — opencode agent enable/disable manager
# Parked: ~/Documents/opencode-agents-parked
# Active: ~/.config/opencode/agents
#
# Mirrors opc-skills, but each unit is a single <name>.md file (not a folder).

set -euo pipefail

PARKED="${OPC_AGENTS_PARKED:-$HOME/Documents/opencode-agents-parked}"
ACTIVE="${OPC_AGENTS_ACTIVE:-$HOME/.config/opencode/agents}"
INDEX_JSON="$PARKED/INDEX.json"
INDEX_MD="$PARKED/INDEX.md"

require_dirs() {
  [ -d "$PARKED" ] || { echo "parked dir missing: $PARKED" >&2; exit 1; }
  mkdir -p "$ACTIVE"
}

# list agent base-names (without .md) in a given dir
agent_names_in() {
  local base="$1"
  [ -d "$base" ] || return 0
  find "$base" -mindepth 1 -maxdepth 1 -type f -name '*.md' \
    ! -name 'INDEX.md' ! -name 'README.md' -printf '%f\n' \
    | sed 's/\.md$//' | sort
}

cmd_status() {
  require_dirs
  local active parked
  active=$(agent_names_in "$ACTIVE" | wc -l)
  parked=$(agent_names_in "$PARKED" | wc -l)
  printf "active : %5d  (%s)\n" "$active" "$ACTIVE"
  printf "parked : %5d  (%s)\n" "$parked" "$PARKED"

  # mode breakdown for active (primary vs subagent) — primary stays in main context
  if [ "$active" -gt 0 ]; then
    local primary subagent other
    primary=$(grep -l '^mode: primary'  "$ACTIVE"/*.md 2>/dev/null | wc -l)
    subagent=$(grep -l '^mode: subagent' "$ACTIVE"/*.md 2>/dev/null | wc -l)
    other=$((active - primary - subagent))
    echo
    printf "  primary  : %5d\n" "$primary"
    printf "  subagent : %5d\n" "$subagent"
    [ "$other" -gt 0 ] && printf "  (other)  : %5d\n" "$other"
  fi
}

cmd_list() {
  require_dirs
  local which="${1:-parked}"
  case "$which" in
    active) agent_names_in "$ACTIVE" ;;
    parked) agent_names_in "$PARKED" ;;
    all)    { agent_names_in "$ACTIVE"; agent_names_in "$PARKED"; } | sort -u ;;
    *) echo "usage: opc-agents list {active|parked|all}" >&2; exit 2 ;;
  esac
}

cmd_categories() {
  require_dirs
  agent_names_in "$PARKED" \
    | awk -F'-' '{print $1}' \
    | sort | uniq -c | sort -rn
}

ensure_fzf() {
  command -v fzf >/dev/null || { echo "fzf is required for this command" >&2; exit 1; }
}

# enable one agent by base-name (no .md)
cmd_enable() {
  require_dirs
  local name="${1:-}"
  [ -n "$name" ] || { echo "usage: opc-agents enable <name>" >&2; exit 2; }
  name="${name%.md}"
  local src="$PARKED/$name.md" dst="$ACTIVE/$name.md"
  [ -f "$src" ] || { echo "not parked: $name" >&2; exit 1; }
  [ -f "$dst" ] && { echo "already active: $name"; exit 0; }
  cp "$src" "$dst"
  echo "enabled: $name"
}

# disable one active agent (keep parked copy)
cmd_disable() {
  require_dirs
  local name="${1:-}"
  [ -n "$name" ] || { echo "usage: opc-agents disable <name>" >&2; exit 2; }
  name="${name%.md}"
  local src="$ACTIVE/$name.md" dst="$PARKED/$name.md"
  [ -f "$src" ] || { echo "not active: $name" >&2; exit 1; }
  if [ -f "$dst" ]; then
    rm -f "$src"
    echo "removed active copy (parked version kept): $name"
  else
    mv "$src" "$dst"
    echo "disabled (moved to parked): $name"
  fi
}

# disable all active agents
cmd_disable_all() {
  require_dirs
  local force=0 keep_primary=0 a
  for a in "$@"; do
    case "$a" in
      -y|--yes) force=1 ;;
      --keep-primary) keep_primary=1 ;;
      *) echo "unknown flag: $a" >&2; exit 2 ;;
    esac
  done
  local -a actives
  if [ "$keep_primary" -eq 1 ]; then
    mapfile -t actives < <(
      agent_names_in "$ACTIVE" | while read -r n; do
        grep -q '^mode: primary' "$ACTIVE/$n.md" 2>/dev/null || echo "$n"
      done
    )
  else
    mapfile -t actives < <(agent_names_in "$ACTIVE")
  fi
  [ "${#actives[@]}" -gt 0 ] || { echo "no active agents to disable"; exit 0; }
  echo "Will disable ${#actives[@]} active agent(s):"
  printf '  %s\n' "${actives[@]}"
  if [ "$force" -ne 1 ]; then
    printf "Proceed? [y/N] "
    read -r ans </dev/tty || ans=""
    case "$ans" in y|Y|yes|YES) ;; *) echo "cancelled"; exit 0 ;; esac
  fi
  local n
  for n in "${actives[@]}"; do
    cmd_disable "$n" || true
  done
}

# enable category (by prefix)
cmd_enable_category() {
  require_dirs
  ensure_fzf
  local prefix="${1:-}"
  [ -n "$prefix" ] || { echo "usage: opc-agents enable-category <prefix>" >&2; exit 2; }
  local -a matches
  mapfile -t matches < <(agent_names_in "$PARKED" | awk -v p="$prefix" '$0 ~ "^" p "(-|$)"')
  [ "${#matches[@]}" -gt 0 ] || { echo "no parked agents match prefix: $prefix" >&2; exit 1; }
  echo "Matches (${#matches[@]}):"
  local selection
  selection=$(printf '%s\n' "${matches[@]}" | fzf --multi --height=70% \
    --prompt="enable-category $prefix > " \
    --header="TAB: toggle | ENTER: confirm | ESC: cancel" \
    --preview="sed -n '1,40p' \"$PARKED/{}.md\" 2>/dev/null" \
    --preview-window=right:60%:wrap)
  [ -n "$selection" ] || { echo "cancelled"; exit 0; }
  local n
  while IFS= read -r n; do
    [ -z "$n" ] && continue
    cmd_enable "$n" || true
  done <<< "$selection"
}

# disable category (by prefix, only ACTIVE)
cmd_disable_category() {
  require_dirs
  ensure_fzf
  local prefix="${1:-}"
  [ -n "$prefix" ] || { echo "usage: opc-agents disable-category <prefix>" >&2; exit 2; }
  local -a matches
  mapfile -t matches < <(agent_names_in "$ACTIVE" | awk -v p="$prefix" '$0 ~ "^" p "(-|$)"')
  [ "${#matches[@]}" -gt 0 ] || { echo "no active agents match prefix: $prefix" >&2; exit 1; }
  echo "Active matches (${#matches[@]}):"
  local selection
  selection=$(printf '%s\n' "${matches[@]}" | fzf --multi --height=70% \
    --prompt="disable-category $prefix > " \
    --header="TAB: toggle | ENTER: confirm | ESC: cancel" \
    --preview="sed -n '1,40p' \"$ACTIVE/{}.md\" 2>/dev/null" \
    --preview-window=right:60%:wrap)
  [ -n "$selection" ] || { echo "cancelled"; exit 0; }
  local n
  while IFS= read -r n; do
    [ -z "$n" ] && continue
    cmd_disable "$n" || true
  done <<< "$selection"
}

# interactive pick: parked category → multi-select → enable
cmd_pick() {
  require_dirs
  ensure_fzf
  local category
  category=$(cmd_categories | fzf --prompt="category > " --height=50% \
    --header="Pick a category (first column = count)" \
    | awk '{print $2}')
  [ -n "$category" ] || { echo "cancelled"; exit 0; }
  local selection
  selection=$(agent_names_in "$PARKED" \
    | awk -v p="$category" '$0 ~ "^" p "(-|$)"' \
    | fzf --multi --height=80% \
        --prompt="$category > " \
        --header="TAB: toggle | ENTER: enable selected" \
        --preview="sed -n '1,40p' \"$PARKED/{}.md\" 2>/dev/null" \
        --preview-window=right:60%:wrap)
  [ -n "$selection" ] || { echo "cancelled"; exit 0; }
  local n
  while IFS= read -r n; do
    [ -z "$n" ] && continue
    cmd_enable "$n" || true
  done <<< "$selection"
}

# interactive pick: ACTIVE category → multi-select → disable
cmd_disable_pick() {
  require_dirs
  ensure_fzf
  local active_count
  active_count=$(agent_names_in "$ACTIVE" | wc -l)
  [ "$active_count" -gt 0 ] || { echo "no active agents"; exit 0; }
  local cats category
  cats=$(agent_names_in "$ACTIVE" | awk -F'-' '{print $1}' | sort | uniq -c | sort -rn)
  category=$(printf '%s\n' "$cats" | fzf --prompt="active-category > " --height=50% \
    --header="Pick a category of ACTIVE agents to disable" \
    | awk '{print $2}')
  [ -n "$category" ] || { echo "cancelled"; exit 0; }
  local selection
  selection=$(agent_names_in "$ACTIVE" \
    | awk -v p="$category" '$0 ~ "^" p "(-|$)"' \
    | fzf --multi --height=80% \
        --prompt="disable $category > " \
        --header="TAB: toggle | ENTER: disable selected" \
        --preview="sed -n '1,40p' \"$ACTIVE/{}.md\" 2>/dev/null" \
        --preview-window=right:60%:wrap)
  [ -n "$selection" ] || { echo "cancelled"; exit 0; }
  local n
  while IFS= read -r n; do
    [ -z "$n" ] && continue
    cmd_disable "$n" || true
  done <<< "$selection"
}

# fzf search (parked) by name+description (uses INDEX.json)
cmd_search() {
  require_dirs
  ensure_fzf
  command -v jq >/dev/null || { echo "jq is required" >&2; exit 1; }
  [ -f "$INDEX_JSON" ] || { echo "INDEX.json missing — run: opc-agents reindex" >&2; exit 1; }
  local query="${1:-}"
  local tmp; tmp=$(mktemp)
  jq -r '.[] | "\(.name)\t\(.mode)\t\(.description)"' "$INDEX_JSON" > "$tmp"
  local selection
  if [ -n "$query" ]; then
    selection=$(fzf --query="$query" --multi --height=80% \
      --delimiter='\t' --with-nth=1,2,3 \
      --prompt="search > " \
      --header="TAB: toggle | ENTER: enable selected" \
      --preview="sed -n '1,40p' \"$PARKED/{1}.md\" 2>/dev/null || echo '(not parked — maybe already active)'" \
      --preview-window=right:60%:wrap < "$tmp")
  else
    selection=$(fzf --multi --height=80% \
      --delimiter='\t' --with-nth=1,2,3 \
      --prompt="search > " \
      --header="TAB: toggle | ENTER: enable selected" \
      --preview="sed -n '1,40p' \"$PARKED/{1}.md\" 2>/dev/null || echo '(not parked — maybe already active)'" \
      --preview-window=right:60%:wrap < "$tmp")
  fi
  rm -f "$tmp"
  [ -n "$selection" ] || { echo "cancelled"; exit 0; }
  local name
  while IFS=$'\t' read -r name _ _; do
    [ -z "$name" ] && continue
    cmd_enable "$name" || true
  done <<< "$selection"
}

# fzf search (ACTIVE only) by name+description
cmd_disable_search() {
  require_dirs
  ensure_fzf
  command -v jq >/dev/null || { echo "jq is required" >&2; exit 1; }
  [ -f "$INDEX_JSON" ] || { echo "INDEX.json missing — run: opc-agents reindex" >&2; exit 1; }
  local query="${1:-}"
  local tmp; tmp=$(mktemp)
  jq -r '.[] | select(.status=="active" or .status=="both") | "\(.name)\t\(.mode)\t\(.description)"' "$INDEX_JSON" > "$tmp"
  [ -s "$tmp" ] || { rm -f "$tmp"; echo "no active agents indexed (try: opc-agents reindex)"; exit 0; }
  local selection
  if [ -n "$query" ]; then
    selection=$(fzf --query="$query" --multi --height=80% \
      --delimiter='\t' --with-nth=1,2,3 \
      --prompt="disable-search > " \
      --header="TAB: toggle | ENTER: disable selected" \
      --preview="sed -n '1,40p' \"$ACTIVE/{1}.md\" 2>/dev/null" \
      --preview-window=right:60%:wrap < "$tmp")
  else
    selection=$(fzf --multi --height=80% \
      --delimiter='\t' --with-nth=1,2,3 \
      --prompt="disable-search > " \
      --header="TAB: toggle | ENTER: disable selected" \
      --preview="sed -n '1,40p' \"$ACTIVE/{1}.md\" 2>/dev/null" \
      --preview-window=right:60%:wrap < "$tmp")
  fi
  rm -f "$tmp"
  [ -n "$selection" ] || { echo "cancelled"; exit 0; }
  local name
  while IFS=$'\t' read -r name _ _; do
    [ -z "$name" ] && continue
    cmd_disable "$name" || true
  done <<< "$selection"
}

# rebuild INDEX.json + INDEX.md by scanning parked + active
cmd_reindex() {
  require_dirs
  python3 - "$PARKED" "$ACTIVE" "$INDEX_JSON" "$INDEX_MD" <<'PY'
import sys, re, json
from pathlib import Path

parked = Path(sys.argv[1])
active = Path(sys.argv[2])
out_json = Path(sys.argv[3])
out_md = Path(sys.argv[4])

def read_agent(p: Path):
    name = p.stem
    desc = ""
    mode = ""
    try:
        txt = p.read_text(errors="replace")
        m = re.match(r"^---\s*\n(.*?)\n---\s*\n", txt, re.DOTALL)
        if m:
            fm = m.group(1)
            d = re.search(r'^description:\s*"?(.+?)"?\s*$', fm, re.MULTILINE | re.DOTALL)
            mo = re.search(r'^mode:\s*"?([^"\n]+?)"?\s*$', fm, re.MULTILINE)
            if d:  desc = d.group(1).strip().splitlines()[0]
            if mo: mode = mo.group(1).strip()
    except Exception as e:
        desc = f"(read error: {e})"
    return name, desc, mode

seen = {}
def scan(base: Path, status: str):
    if not base.exists(): return
    for p in sorted(base.glob("*.md")):
        if p.name in ("INDEX.md", "README.md"): continue
        n, d, m = read_agent(p)
        if n in seen:
            seen[n]["status"] = "both"
        else:
            seen[n] = {"name": n, "mode": m or "?", "description": d, "status": status}

scan(active, "active")
scan(parked, "parked")

items = sorted(seen.values(), key=lambda x: x["name"])
out_json.write_text(json.dumps(items, ensure_ascii=False, indent=2))

active_n = sum(1 for i in items if i["status"] in ("active", "both"))
parked_n = sum(1 for i in items if i["status"] in ("parked", "both"))
prim = sum(1 for i in items if i["mode"] == "primary")
sub  = sum(1 for i in items if i["mode"] == "subagent")

lines = [
    "# Opencode Agents — Index",
    "",
    f"**{len(items)} unique agents** — active: {active_n}, parked: {parked_n}.",
    f"Modes — primary: {prim}, subagent: {sub}.",
    "",
    f"Active: `{active}`  |  Parked: `{parked}`",
    "",
    "| # | Status | Mode | Name | Description |",
    "|---|--------|------|------|-------------|",
]
for i, it in enumerate(items, 1):
    d = it["description"].replace("\n", " ").replace("|", "\\|")
    if len(d) > 200: d = d[:197] + "..."
    lines.append(f"| {i} | {it['status']} | {it['mode']} | `{it['name']}` | {d} |")
out_md.write_text("\n".join(lines) + "\n")
print(f"reindexed: {len(items)} agents  (active={active_n}, parked={parked_n}, primary={prim}, subagent={sub})")
PY
}

usage() {
  cat <<'USG'
opc-agents — opencode agent manager (file-based: <name>.md)

  status                      counts of active vs parked (+ mode breakdown)
  list {active|parked|all}    list agent names
  categories                  prefix-based category counts (PARKED)
  enable <name>               enable single agent (copy parked → active)
  disable <name>              disable single agent (remove from active; keep parked)
  disable-all [-y|--yes] [--keep-primary]
                              disable every active agent (asks for confirmation)
  enable-category <prefix>    fzf multi-pick within a prefix, then enable
  disable-category <prefix>   fzf multi-pick of ACTIVE agents with prefix, then disable
  pick                        fzf: choose category → multi-select → enable
  disable-pick                fzf: choose ACTIVE category → multi-select → disable
  search [query]              fzf fuzzy search across name+description (enable)
  disable-search [query]      fzf fuzzy search ACTIVE agents only (disable)
  reindex                     rebuild INDEX.json / INDEX.md

Notes:
  - Each agent is a single <name>.md file (frontmatter + body).
  - disable* never deletes data — removes active copy and keeps (or restores) parked.
  - --keep-primary on disable-all skips agents with `mode: primary` (those stay loaded).

Environment:
  OPC_AGENTS_PARKED   override parked dir  (default: ~/Documents/opencode-agents-parked)
  OPC_AGENTS_ACTIVE   override active dir  (default: ~/.config/opencode/agents)
USG
}

main() {
  local cmd="${1:-}"
  [ -n "$cmd" ] || { usage; exit 2; }
  shift || true
  case "$cmd" in
    status)           cmd_status "$@" ;;
    list)             cmd_list "$@" ;;
    categories|cats)  cmd_categories "$@" ;;
    enable)           cmd_enable "$@" ;;
    disable)          cmd_disable "$@" ;;
    disable-all)      cmd_disable_all "$@" ;;
    enable-category|enable-cat)   cmd_enable_category "$@" ;;
    disable-category|disable-cat) cmd_disable_category "$@" ;;
    pick)             cmd_pick "$@" ;;
    disable-pick)     cmd_disable_pick "$@" ;;
    search)           cmd_search "$@" ;;
    disable-search)   cmd_disable_search "$@" ;;
    reindex)          cmd_reindex "$@" ;;
    -h|--help|help)   usage ;;
    *) echo "unknown command: $cmd" >&2; usage; exit 2 ;;
  esac
}

main "$@"
