#!/usr/bin/env bash
# opc-agents — multi-target agent enable/disable manager (opencode + claude + feynman)
#
# Parked sources (per-target — formats differ):
#   opencode  → ~/Documents/personas/agents-opencode-archive   (permission: { ... })
#   claude    → ~/Documents/personas/agents-claude-archive     (tools: Read, Glob, ...)
#   feynman   → ~/Documents/personas/agents-feynman-archive    (tools: lowercase, thinking, output)
#
# Active targets:
#   opencode  → ~/.config/opencode/agents
#   claude    → ~/.claude/agents
#   feynman   → ~/.feynman/agent/agents
#
# Default targets: opencode,claude,feynman  (override via OPC_AGENTS_TARGETS or --target)

set -euo pipefail

# Backward compat: OPC_AGENTS_PARKED still works for opencode (legacy)
PARKED_OPENCODE="${OPC_AGENTS_PARKED:-$HOME/Documents/personas/agents-opencode-archive}"
PARKED_CLAUDE="${OPC_AGENTS_CLAUDE_PARKED:-$HOME/Documents/personas/agents-claude-archive}"
PARKED_FEYNMAN="${OPC_AGENTS_FEYNMAN_PARKED:-$HOME/Documents/personas/agents-feynman-archive}"
ACTIVE_OPENCODE="${OPC_AGENTS_ACTIVE:-$HOME/.config/opencode/agents}"
ACTIVE_CLAUDE="${OPC_AGENTS_CLAUDE_ACTIVE:-$HOME/.claude/agents}"
ACTIVE_FEYNMAN="${OPC_AGENTS_FEYNMAN_ACTIVE:-$HOME/.feynman/agent/agents}"

# Index lives under opencode-archive (canonical) but tracks ALL targets
INDEX_JSON="$PARKED_OPENCODE/INDEX.json"
INDEX_MD="$PARKED_OPENCODE/INDEX.md"

DEFAULT_TARGETS="${OPC_AGENTS_TARGETS:-opencode,claude,feynman}"

# resolve target → parked dir / active dir
target_parked() {
  case "$1" in
    opencode) printf '%s\n' "$PARKED_OPENCODE" ;;
    claude)   printf '%s\n' "$PARKED_CLAUDE" ;;
    feynman)  printf '%s\n' "$PARKED_FEYNMAN" ;;
    *) echo "unknown target: $1" >&2; return 1 ;;
  esac
}
target_active() {
  case "$1" in
    opencode) printf '%s\n' "$ACTIVE_OPENCODE" ;;
    claude)   printf '%s\n' "$ACTIVE_CLAUDE" ;;
    feynman)  printf '%s\n' "$ACTIVE_FEYNMAN" ;;
    *) echo "unknown target: $1" >&2; return 1 ;;
  esac
}

resolve_targets() {
  local raw="${1:-$DEFAULT_TARGETS}"
  case "$raw" in
    all)  raw="opencode,claude,feynman" ;;
    both) raw="opencode,claude" ;;
  esac
  printf '%s\n' "$raw" | tr ',' '\n' | awk 'NF' | awk '!seen[$0]++'
}

parse_target_flag() {
  REMAINING_ARGS=()
  PARSED_TARGETS=""
  local seen_target=""
  while [ "$#" -gt 0 ]; do
    case "$1" in
      --target=*)  seen_target="${1#--target=}"; shift ;;
      --target|-t) seen_target="${2:-}"; shift 2 ;;
      *)           REMAINING_ARGS+=("$1"); shift ;;
    esac
  done
  if [ -n "$seen_target" ]; then
    PARSED_TARGETS=$(resolve_targets "$seen_target" | paste -sd ',')
  else
    PARSED_TARGETS=$(resolve_targets | paste -sd ',')
  fi
}

require_dirs() {
  [ -d "$PARKED_OPENCODE" ] || mkdir -p "$PARKED_OPENCODE"
  [ -d "$PARKED_CLAUDE" ]   || mkdir -p "$PARKED_CLAUDE"
  [ -d "$PARKED_FEYNMAN" ]  || mkdir -p "$PARKED_FEYNMAN"
  mkdir -p "$ACTIVE_OPENCODE" "$ACTIVE_CLAUDE" "$ACTIVE_FEYNMAN"
}

# legacy aliases for backward compat
PARKED="$PARKED_OPENCODE"
ACTIVE="$ACTIVE_OPENCODE"

# 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
  echo "Parked sources (per-target — different formats):"
  printf "  %-10s %5d  (%s)\n" "opencode" "$(agent_names_in "$PARKED_OPENCODE" | wc -l)" "$PARKED_OPENCODE"
  printf "  %-10s %5d  (%s)\n" "claude"   "$(agent_names_in "$PARKED_CLAUDE"   | wc -l)" "$PARKED_CLAUDE"
  printf "  %-10s %5d  (%s)\n" "feynman"  "$(agent_names_in "$PARKED_FEYNMAN"  | wc -l)" "$PARKED_FEYNMAN"

  echo ""
  echo "Active targets:"
  local t base n
  for t in opencode claude feynman; do
    base=$(target_active "$t")
    n=$(agent_names_in "$base" | wc -l)
    printf "  %-10s %5d  (%s)\n" "$t" "$n" "$base"
  done

  # mode breakdown (opencode-style frontmatter; claude format has no `mode:`)
  local active_oc; active_oc=$(agent_names_in "$ACTIVE_OPENCODE" | wc -l)
  if [ "$active_oc" -gt 0 ]; then
    local primary subagent other
    primary=$(grep -l '^mode: primary'  "$ACTIVE_OPENCODE"/*.md 2>/dev/null | wc -l)
    subagent=$(grep -l '^mode: subagent' "$ACTIVE_OPENCODE"/*.md 2>/dev/null | wc -l)
    other=$((active_oc - primary - subagent))
    echo
    echo "opencode mode breakdown:"
    printf "  primary  : %5d\n" "$primary"
    printf "  subagent : %5d\n" "$subagent"
    [ "$other" -gt 0 ] && printf "  (other)  : %5d\n" "$other" || true
  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; }
}

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

# suffix_of <name>  — everything after the first '-' (the variant); empty for bare base
suffix_of() {
  local n="$1"
  case "$n" in
    *-*) printf '%s\n' "${n#*-}" ;;
    *)   printf '%s\n' "" ;;
  esac
}

# variant suffix distribution across PARKED
cmd_variants() {
  require_dirs
  agent_names_in "$PARKED" | while read -r n; do
    local s; s=$(suffix_of "$n")
    [ -n "$s" ] && printf '%s\n' "$s" || printf '%s\n' "<base>"
  done | sort | uniq -c | sort -rn
}

# bulk enable agents matching variant suffix (within PARKED)
cmd_enable_variant() {
  require_dirs
  local v="${1:-}"
  [ -n "$v" ] || { echo "usage: opc-agents enable-variant <suffix>" >&2; exit 2; }
  local -a matches=()
  while IFS= read -r n; do
    [ -z "$n" ] && continue
    [ "$(suffix_of "$n")" = "$v" ] && matches+=("$n")
  done < <(agent_names_in "$PARKED")
  [ "${#matches[@]}" -gt 0 ] || { echo "no parked agents with variant: $v" >&2; exit 1; }
  echo "Will enable ${#matches[@]} agent(s) with variant '$v':"
  printf '  %s\n' "${matches[@]}"
  local n
  for n in "${matches[@]}"; do cmd_enable "$n" || true; done
}

# bulk disable from ACTIVE matching variant suffix
cmd_disable_variant() {
  require_dirs
  local v="${1:-}"
  [ -n "$v" ] || { echo "usage: opc-agents disable-variant <suffix>" >&2; exit 2; }
  local -a matches=()
  while IFS= read -r n; do
    [ -z "$n" ] && continue
    [ "$(suffix_of "$n")" = "$v" ] && matches+=("$n")
  done < <(agent_names_in "$ACTIVE")
  [ "${#matches[@]}" -gt 0 ] || { echo "no active agents with variant: $v" >&2; exit 1; }
  echo "Will disable ${#matches[@]} agent(s) with variant '$v':"
  printf '  %s\n' "${matches[@]}"
  local n
  for n in "${matches[@]}"; do cmd_disable "$n" || true; done
}

# live scan: emit agent base-names whose description matches Domain: <wanted>
# (INDEX-free, so it stays correct between reindexes)
domain_scan_in() {
  local base="$1" wanted="$2"
  [ -d "$base" ] || return 0
  grep -liE "^description:.*[Dd]omain:[[:space:]]*${wanted}([^A-Za-z]|$)" \
    "$base"/*.md 2>/dev/null \
    | while read -r f; do
        local b; b=$(basename "$f" .md)
        case "$b" in INDEX|README) ;; *) printf '%s\n' "$b" ;; esac
      done | sort -u
}

# bulk enable agents whose 'domain' matches (live scan, no INDEX dependency)
cmd_enable_domain() {
  require_dirs
  local d="${1:-}"
  [ -n "$d" ] || { echo "usage: opc-agents enable-domain <domain>" >&2; exit 2; }
  local -a matches=()
  mapfile -t matches < <(domain_scan_in "$PARKED" "$d")
  [ "${#matches[@]}" -gt 0 ] || { echo "no parked agents in domain: $d" >&2; exit 1; }
  echo "Will enable ${#matches[@]} agent(s) in domain '$d':"
  printf '  %s\n' "${matches[@]}"
  local n
  for n in "${matches[@]}"; do cmd_enable "$n" || true; done
}

# bulk disable from ACTIVE matching domain (live scan)
cmd_disable_domain() {
  require_dirs
  local d="${1:-}"
  [ -n "$d" ] || { echo "usage: opc-agents disable-domain <domain>" >&2; exit 2; }
  local -a matches=()
  mapfile -t matches < <(domain_scan_in "$ACTIVE" "$d")
  [ "${#matches[@]}" -gt 0 ] || { echo "no active agents in domain: $d" >&2; exit 1; }
  echo "Will disable ${#matches[@]} agent(s) in domain '$d':"
  printf '  %s\n' "${matches[@]}"
  local n
  for n in "${matches[@]}"; do cmd_disable "$n" || true; done
}

# enable one agent in selected targets
# usage: cmd_enable [--target T] <name>
cmd_enable() {
  parse_target_flag "$@"
  set -- "${REMAINING_ARGS[@]}"
  require_dirs
  local name="${1:-}"
  [ -n "$name" ] || { echo "usage: opc-agents enable [--target opencode|claude|feynman|all] <name>" >&2; exit 2; }
  name="${name%.md}"
  local t pdir adir src dst
  IFS=',' read -ra tgts <<< "$PARSED_TARGETS"
  for t in "${tgts[@]}"; do
    pdir=$(target_parked "$t") || continue
    adir=$(target_active "$t") || continue
    src="$pdir/$name.md"
    dst="$adir/$name.md"
    if [ ! -f "$src" ]; then
      echo "[$t] not parked (no source for this target): $name"
      continue
    fi
    if [ -f "$dst" ]; then
      echo "[$t] already active: $name"
    else
      cp "$src" "$dst"
      echo "[$t] enabled: $name"
    fi
  done
}

# disable one agent in selected targets (keeps parked source untouched)
cmd_disable() {
  parse_target_flag "$@"
  set -- "${REMAINING_ARGS[@]}"
  require_dirs
  local name="${1:-}"
  [ -n "$name" ] || { echo "usage: opc-agents disable [--target opencode|claude|feynman|all] <name>" >&2; exit 2; }
  name="${name%.md}"
  local t adir src
  IFS=',' read -ra tgts <<< "$PARSED_TARGETS"
  for t in "${tgts[@]}"; do
    adir=$(target_active "$t") || continue
    src="$adir/$name.md"
    if [ ! -f "$src" ]; then
      echo "[$t] not active: $name"
      continue
    fi
    rm -f "$src"
    echo "[$t] disabled: $name"
  done
}

# disable all active agents across selected targets
# usage: cmd_disable_all [--target T] [-y|--yes] [--keep-primary]
cmd_disable_all() {
  parse_target_flag "$@"
  set -- "${REMAINING_ARGS[@]}"
  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
  IFS=',' read -ra tgts <<< "$PARSED_TARGETS"
  # Collect (target, name) pairs that should be disabled
  local -a pairs=()
  local t adir n
  for t in "${tgts[@]}"; do
    adir=$(target_active "$t") || continue
    while IFS= read -r n; do
      [ -z "$n" ] && continue
      if [ "$keep_primary" -eq 1 ]; then
        # only opencode has 'mode: primary' frontmatter; claude format has no mode field → never primary
        if grep -q '^mode: primary' "$adir/$n.md" 2>/dev/null; then continue; fi
      fi
      pairs+=("$t:$n")
    done < <(agent_names_in "$adir")
  done
  [ "${#pairs[@]}" -gt 0 ] || { echo "no active agents to disable in [${tgts[*]}]"; exit 0; }
  echo "Will disable ${#pairs[@]} (target:agent) entries:"
  printf '  %s\n' "${pairs[@]}"
  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
  for p in "${pairs[@]}"; do
    t="${p%%:*}"; n="${p#*:}"
    adir=$(target_active "$t")
    [ -f "$adir/$n.md" ] && rm -f "$adir/$n.md" && echo "[$t] disabled: $n"
  done
}

# enable every parked agent into selected target(s) — symmetric to disable-all
# usage: cmd_enable_all [--target T] [-y|--yes]
# Per-target: only reads from THAT target's parked source (format-aware).
# Idempotent: skips agents already active.
cmd_enable_all() {
  parse_target_flag "$@"
  set -- "${REMAINING_ARGS[@]}"
  require_dirs
  local force=0
  if [ "${1:-}" = "-y" ] || [ "${1:-}" = "--yes" ]; then
    force=1
  fi
  IFS=',' read -ra tgts <<< "$PARSED_TARGETS"
  local -a pairs=()
  local t pdir adir n
  for t in "${tgts[@]}"; do
    pdir=$(target_parked "$t") || continue
    adir=$(target_active "$t") || continue
    while IFS= read -r n; do
      [ -z "$n" ] && continue
      [ -f "$adir/$n.md" ] && continue   # already active
      pairs+=("$t:$n")
    done < <(agent_names_in "$pdir")
  done
  [ "${#pairs[@]}" -gt 0 ] || { echo "all parked agents already active in [${tgts[*]}]"; exit 0; }
  echo "Will enable ${#pairs[@]} (target:agent) entries:"
  printf '  %s\n' "${pairs[@]:0:20}"
  [ "${#pairs[@]}" -gt 20 ] && echo "  ... and $((${#pairs[@]} - 20)) more"
  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 p enabled=0
  for p in "${pairs[@]}"; do
    t="${p%%:*}"; n="${p#*:}"
    pdir=$(target_parked "$t"); adir=$(target_active "$t")
    [ -f "$pdir/$n.md" ] || continue
    [ -f "$adir/$n.md" ] && continue
    cp "$pdir/$n.md" "$adir/$n.md"
    enabled=$((enabled+1))
  done
  echo "enabled $enabled agent(s)."
}

# 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 target picker — returns comma-separated targets on stdout, exit 1 on cancel.
pick_targets() {
  local action="${1:-enable}"
  ensure_fzf
  local oc cl fy
  oc=$(agent_names_in "$ACTIVE_OPENCODE" | wc -l)
  cl=$(agent_names_in "$ACTIVE_CLAUDE"   | wc -l)
  fy=$(agent_names_in "$ACTIVE_FEYNMAN"  | wc -l)
  local sel
  sel=$(printf '%s\n' \
        "all       → opencode + claude + feynman" \
        "opencode  ($oc active)" \
        "claude    ($cl active)" \
        "feynman   ($fy active)" \
    | fzf --multi --height=30% \
        --prompt="$action target > " \
        --header="TAB: multi-select | ENTER: confirm  (default highlighted = all)" \
    | awk '{print $1}')
  [ -n "$sel" ] || return 1
  if grep -qx all <<< "$sel"; then
    echo "opencode,claude,feynman"
  else
    echo "$sel" | paste -sd ','
  fi
}

# Helper: union of parked agent names across all 3 targets (deduped)
parked_union() {
  { agent_names_in "$PARKED_OPENCODE"
    agent_names_in "$PARKED_CLAUDE"
    agent_names_in "$PARKED_FEYNMAN"; } | sort -u
}

# Helper: union of active agent names with target indicators "name\t[targets]"
active_union_with_targets() {
  {
    for t in opencode claude feynman; do
      while IFS= read -r n; do
        [ -z "$n" ] && continue
        printf '%s\t%s\n' "$n" "$t"
      done < <(agent_names_in "$(target_active "$t")")
    done
  } | awk -F'\t' '
      { agents[$1] = (agents[$1] ? agents[$1]"," : "") $2 }
      END { for (a in agents) printf "%s\t[%s]\n", a, agents[a] }
    ' | sort
}

# Resolve a parked source path for an agent name (first target where parked file exists)
parked_path_for() {
  local name="$1" t pdir
  for t in opencode claude feynman; do
    pdir=$(target_parked "$t")
    [ -f "$pdir/$name.md" ] && { echo "$pdir/$name.md"; return 0; }
  done
  return 1
}

# interactive pick: union-parked category → multi-select → pick target(s)
cmd_pick() {
  require_dirs
  ensure_fzf
  # Categories from union of parked across all 3 targets
  local cats category
  cats=$(parked_union | awk -F'-' '{print $1}' | sort | uniq -c | sort -rn)
  category=$(printf '%s\n' "$cats" | fzf --prompt="category > " --height=50% \
    --header="Pick a category (first column = count)  •  parked = union of all 3 targets" \
    | awk '{print $2}')
  [ -n "$category" ] || { echo "cancelled"; exit 0; }

  # Build {name}\t{parked-in:[oc,cl,fy]} lines
  local tmp; tmp=$(mktemp)
  {
    for t in opencode claude feynman; do
      while IFS= read -r n; do
        [ -z "$n" ] && continue
        printf '%s\t%s\n' "$n" "$t"
      done < <(agent_names_in "$(target_parked "$t")")
    done
  } | awk -F'\t' '
      { p[$1] = (p[$1] ? p[$1]"," : "") $2 }
      END { for (a in p) printf "%s\t[%s]\n", a, p[a] }
    ' | awk -F'\t' -v p="$category" '$1 ~ "^"p"(-|$)"' | sort > "$tmp"

  local selection
  selection=$(fzf --multi --height=80% \
      --prompt="$category > " \
      --header="TAB: toggle | ENTER: confirm → pick target" \
      --delimiter='\t' --with-nth=1,2 \
      --preview="for d in \"$PARKED_OPENCODE\" \"$PARKED_CLAUDE\" \"$PARKED_FEYNMAN\"; do [ -f \"\$d\"/{1}.md ] && { echo \"=== \$d ===\"; sed -n '1,40p' \"\$d\"/{1}.md; break; }; done 2>/dev/null" \
      --preview-window=right:60%:wrap < "$tmp")
  rm -f "$tmp"
  [ -n "$selection" ] || { echo "cancelled"; exit 0; }

  local targets
  targets=$(pick_targets "enable") || { echo "cancelled (no target)"; exit 0; }

  local n
  while IFS=$'\t' read -r n _; do
    [ -z "$n" ] && continue
    cmd_enable --target "$targets" "$n" || true
  done <<< "$selection"
}

# interactive disable-pick: union of actives across all 3 targets → pick target(s) to remove from
cmd_disable_pick() {
  require_dirs
  ensure_fzf
  local tmp; tmp=$(mktemp)
  active_union_with_targets > "$tmp"
  [ -s "$tmp" ] || { rm -f "$tmp"; echo "no active agents across any target"; exit 0; }

  local cats category
  cats=$(awk -F'\t' '{print $1}' "$tmp" | awk -F'-' '{print $1}' | sort | uniq -c | sort -rn)
  category=$(printf '%s\n' "$cats" | fzf --prompt="active-category > " --height=50% \
    --header="Pick category from ACTIVE agents (any target)" \
    | awk '{print $2}')
  [ -n "$category" ] || { rm -f "$tmp"; echo "cancelled"; exit 0; }

  local selection
  selection=$(awk -F'\t' -v p="$category" '$1 ~ "^"p"(-|$)"' "$tmp" \
    | fzf --multi --height=80% \
        --prompt="disable $category > " \
        --header="TAB: toggle | ENTER: pick target(s) to disable from" \
        --delimiter='\t' --with-nth=1,2 \
        --preview="for d in \"$ACTIVE_OPENCODE\" \"$ACTIVE_CLAUDE\" \"$ACTIVE_FEYNMAN\"; do [ -f \"\$d\"/{1}.md ] && { echo \"=== \$d ===\"; sed -n '1,40p' \"\$d\"/{1}.md; break; }; done 2>/dev/null" \
        --preview-window=right:60%:wrap)
  rm -f "$tmp"
  [ -n "$selection" ] || { echo "cancelled"; exit 0; }

  local targets
  targets=$(pick_targets "disable") || { echo "cancelled (no target)"; exit 0; }

  local n
  while IFS=$'\t' read -r n _; do
    [ -z "$n" ] && continue
    cmd_disable --target "$targets" "$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\(.domain // "-")\t\(.variant // "-")\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,4,5 \
      --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,4,5 \
      --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\(.domain // "-")\t\(.variant // "-")\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,4,5 \
      --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,4,5 \
      --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_OPENCODE" "$PARKED_CLAUDE" "$PARKED_FEYNMAN" "$ACTIVE_OPENCODE" "$ACTIVE_CLAUDE" "$ACTIVE_FEYNMAN" "$INDEX_JSON" "$INDEX_MD" <<'PY'
import sys, re, json
from pathlib import Path

parked_oc = Path(sys.argv[1])
parked_cl = Path(sys.argv[2])
parked_fy = Path(sys.argv[3])
active_oc = Path(sys.argv[4])
active_cl = Path(sys.argv[5])
active_fy = Path(sys.argv[6])
out_json  = Path(sys.argv[7])
out_md    = Path(sys.argv[8])

def read_agent(p: Path):
    name = p.stem
    desc, mode, domain, variant = "", "", "", ""
    persona = name.split("-", 1)[0]
    suffix  = name[len(persona)+1:] if "-" in name else ""
    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})"
    dm = re.search(r"Domain:\s*([A-Za-z]+)", desc)
    if dm: domain = dm.group(1).lower()
    vm = re.search(r"Variant:\s*([A-Za-z0-9][A-Za-z0-9-]*)", desc)
    if vm:    variant = vm.group(1).lower()
    elif suffix: variant = suffix
    return {
        "name": name, "persona": persona, "variant": variant,
        "mode": mode or "?", "domain": domain,
        "description": desc,
    }

agents = {}
def upsert(rec):
    n = rec["name"]
    cur = agents.get(n)
    if cur is None:
        rec.setdefault("parked", {"opencode": False, "claude": False, "feynman": False})
        rec.setdefault("active", [])
        agents[n] = rec
    else:
        # prefer opencode-side description (richer with permissions/mode); fill any blanks
        for k in ("description","domain","variant"):
            if not cur.get(k) and rec.get(k): cur[k] = rec[k]
        if (cur.get("mode") in ("","?")) and rec.get("mode") not in ("","?"):
            cur["mode"] = rec["mode"]

def scan_parked(base: Path, target: str):
    if not base.exists(): return
    for p in sorted(base.glob("*.md")):
        if p.name in ("INDEX.md","README.md"): continue
        rec = read_agent(p)
        upsert(rec)
        agents[rec["name"]]["parked"][target] = True

def scan_active(base: Path, target: str):
    if not base.exists(): return
    for p in sorted(base.glob("*.md")):
        if p.name in ("INDEX.md","README.md"): continue
        rec = read_agent(p)
        upsert(rec)
        if target not in agents[rec["name"]]["active"]:
            agents[rec["name"]]["active"].append(target)

scan_parked(parked_oc, "opencode")
scan_parked(parked_cl, "claude")
scan_parked(parked_fy, "feynman")
scan_active(active_oc, "opencode")
scan_active(active_cl, "claude")
scan_active(active_fy, "feynman")

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

n_oc_park = sum(1 for i in items if i["parked"]["opencode"])
n_cl_park = sum(1 for i in items if i["parked"]["claude"])
n_fy_park = sum(1 for i in items if i["parked"]["feynman"])
in_oc     = sum(1 for i in items if "opencode" in i["active"])
in_cl     = sum(1 for i in items if "claude"   in i["active"])
in_fy     = sum(1 for i in items if "feynman"  in i["active"])
all_park  = sum(1 for i in items if all(i["parked"].get(t) for t in ("opencode","claude","feynman")))
prim      = sum(1 for i in items if i["mode"] == "primary")
sub       = sum(1 for i in items if i["mode"] == "subagent")

from collections import Counter
dom_c = Counter(i["domain"] for i in items if i["domain"])
var_c = Counter(i["variant"] for i in items if i["variant"])
top_doms = ", ".join(f"{k}:{v}" for k,v in dom_c.most_common(8)) or "-"
top_vars = ", ".join(f"{k}:{v}" for k,v in var_c.most_common(8)) or "-"

lines = [
    "# Opencode + Claude + Feynman Agents — Index",
    "",
    f"**{len(items)} unique agents.**",
    f"Parked sources — opencode: {n_oc_park}, claude: {n_cl_park}, feynman: {n_fy_park}, all-three: {all_park}.",
    f"Active — opencode: {in_oc}, claude: {in_cl}, feynman: {in_fy}.",
    f"Modes — primary: {prim}, subagent: {sub}.",
    f"Top domains — {top_doms}.",
    f"Top variants — {top_vars}.",
    "",
    f"Parked opencode: `{parked_oc}`",
    f"Parked claude  : `{parked_cl}`",
    f"Parked feynman : `{parked_fy}`",
    f"Active opencode: `{active_oc}`",
    f"Active claude  : `{active_cl}`",
    f"Active feynman : `{active_fy}`",
    "",
    "| # | Parked | Active | Mode | Persona | Variant | Domain | Name | Description |",
    "|---|--------|--------|------|---------|---------|--------|------|-------------|",
]
for i, it in enumerate(items, 1):
    d = it["description"].replace("\n", " ").replace("|", "\\|")
    if len(d) > 140: d = d[:137] + "..."
    pk = ",".join(t for t in ("opencode","claude","feynman") if it["parked"][t]) or "-"
    ac = ",".join(it.get("active", [])) or "-"
    lines.append(
        f"| {i} | {pk} | {ac} | {it['mode']} | {it['persona']} | "
        f"{it['variant'] or '-'} | {it['domain'] or '-'} | `{it['name']}` | {d} |"
    )
out_md.write_text("\n".join(lines) + "\n")
print(f"reindexed: {len(items)} agents  "
      f"(parked oc={n_oc_park} cl={n_cl_park} fy={n_fy_park} all3={all_park}, "
      f"active oc={in_oc} cl={in_cl} fy={in_fy}, primary={prim}, subagent={sub})")
PY
}

usage() {
  cat <<'USG'
opc-agents — multi-target agent manager (opencode + claude + feynman)

Targets: opencode, claude, feynman ("all"=all three, "both"=opencode+claude legacy)
Default = opencode,claude,feynman. Use --target / -t to scope ops.

  status                      per-target counts (parked + active) + mode breakdown
  list {active|parked|all}    list agent names (active = opencode set)
  categories                  prefix-based base-persona counts (PARKED opencode)
  variants                    suffix-based variant counts (PARKED opencode)
  enable  [--target T] <name>             copy parked → active in selected targets
  disable [--target T] <name>             remove from active in selected targets
  enable-all  [--target T] [-y|--yes]     enable every parked agent into selected targets
                                          (idempotent — restore-from-archive helper)
  disable-all [--target T] [-y|--yes] [--keep-primary]
                                          disable every active agent across targets
  enable-category  <prefix>   fzf multi-pick within a base-persona prefix, then enable
  disable-category <prefix>   fzf multi-pick of ACTIVE agents with prefix, then disable
  enable-variant   <suffix>   bulk enable all *-<suffix>.md (eg. salva)
  disable-variant  <suffix>   bulk disable all *-<suffix>.md
  enable-domain    <domain>   bulk enable agents whose `Domain: <x>` matches (live scan)
  disable-domain   <domain>   bulk disable from ACTIVE matching domain
  pick                        fzf: choose category → multi-select → enable
  disable-pick                fzf: choose ACTIVE category → multi-select → disable
  search [query]              fzf fuzzy search across all index fields
  disable-search [query]      fzf fuzzy search ACTIVE agents only (disable)
  reindex                     rebuild INDEX.json / INDEX.md (per-target parked + active)

Notes:
  - Agent format differs between targets:
      opencode → `permission: {...}` + `mode:` field
      claude   → `tools: Read, Glob, ...` (PascalCase)
      feynman  → `tools: read, write, ...` (lowercase) + `thinking`/`output` fields
    Each target reads from its own parked dir; cross-target enable copies as-is.
  - disable* never deletes parked sources.
  - --keep-primary skips agents with `mode: primary` (opencode-format only).
  - Single source-of-truth: ~/Documents/personas/agents-{opencode,claude,feynman}-archive

Environment:
  OPC_AGENTS_PARKED          opencode parked  (default: personas/agents-opencode-archive)
  OPC_AGENTS_CLAUDE_PARKED   claude   parked  (default: personas/agents-claude-archive)
  OPC_AGENTS_FEYNMAN_PARKED  feynman  parked  (default: personas/agents-feynman-archive)
  OPC_AGENTS_ACTIVE          opencode active  (default: ~/.config/opencode/agents)
  OPC_AGENTS_CLAUDE_ACTIVE   claude   active  (default: ~/.claude/agents)
  OPC_AGENTS_FEYNMAN_ACTIVE  feynman  active  (default: ~/.feynman/agent/agents)
  OPC_AGENTS_TARGETS         default targets when --target omitted
                             (default: opencode,claude,feynman)
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 "$@" ;;
    variants|vars)    cmd_variants "$@" ;;
    enable)           cmd_enable "$@" ;;
    disable)          cmd_disable "$@" ;;
    enable-all)       cmd_enable_all "$@" ;;
    disable-all)      cmd_disable_all "$@" ;;
    enable-category|enable-cat)   cmd_enable_category "$@" ;;
    disable-category|disable-cat) cmd_disable_category "$@" ;;
    enable-variant|enable-var)    cmd_enable_variant "$@" ;;
    disable-variant|disable-var)  cmd_disable_variant "$@" ;;
    enable-domain)    cmd_enable_domain "$@" ;;
    disable-domain)   cmd_disable_domain "$@" ;;
    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 "$@"
