#!/usr/bin/env bash
# opc-skills — opencode skill enable/disable manager
# Parked: ~/Documents/opencode-skills-parked
# Active: ~/.config/opencode/skills

set -euo pipefail

PARKED="${OPC_PARKED:-$HOME/Documents/opencode-skills-parked}"
ACTIVE="${OPC_ACTIVE:-$HOME/.config/opencode/skills}"
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"
}

skill_dirs_in() {
  # list skill folder names in a given dir (exclude bin/, files, hidden)
  local base="$1"
  [ -d "$base" ] || return 0
  find "$base" -mindepth 1 -maxdepth 1 -type d \
    ! -name bin ! -name '.*' -printf '%f\n' | sort
}

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

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

cmd_categories() {
  require_dirs
  skill_dirs_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 skill by folder name
cmd_enable() {
  require_dirs
  local name="${1:-}"
  [ -n "$name" ] || { echo "usage: opc-skills enable <folder>" >&2; exit 2; }
  local src="$PARKED/$name" dst="$ACTIVE/$name"
  [ -d "$src" ] || { echo "not parked: $name" >&2; exit 1; }
  [ -d "$dst" ] && { echo "already active: $name"; exit 0; }
  cp -r "$src" "$dst"
  echo "enabled: $name"
}

# disable one active skill (move back to parked)
cmd_disable() {
  require_dirs
  local name="${1:-}"
  [ -n "$name" ] || { echo "usage: opc-skills disable <folder>" >&2; exit 2; }
  local src="$ACTIVE/$name" dst="$PARKED/$name"
  [ -d "$src" ] || { echo "not active: $name" >&2; exit 1; }
  if [ -d "$dst" ]; then
    # parked copy exists; just remove active
    rm -rf "$src"
    echo "removed active copy (parked version kept): $name"
  else
    mv "$src" "$dst"
    echo "disabled (moved to parked): $name"
  fi
}

# batch enable by prefix/category (first word before '-')
cmd_enable_category() {
  require_dirs
  ensure_fzf
  local prefix="${1:-}"
  [ -n "$prefix" ] || { echo "usage: opc-skills enable-category <prefix>" >&2; exit 2; }
  local -a matches
  mapfile -t matches < <(skill_dirs_in "$PARKED" | awk -v p="$prefix" '$0 ~ "^" p "(-|$)"')
  [ "${#matches[@]}" -gt 0 ] || { echo "no parked skills 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")
  [ -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: category → multi-select skills
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; }
  # fzf preview: description from SKILL.md frontmatter
  local selection
  selection=$(skill_dirs_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/{}/SKILL.md\" 2>/dev/null || echo '(no SKILL.md)'" \
        --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"
}

# search by name+description via 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-skills reindex" >&2; exit 1; }
  local query="${1:-}"
  # Build "folder\tdescription" lines
  local tmp
  tmp=$(mktemp)
  jq -r '.[] | "\(.folder)\t\(.description)"' "$INDEX_JSON" > "$tmp"
  local selection
  if [ -n "$query" ]; then
    selection=$(fzf --query="$query" --multi --height=80% \
      --delimiter='\t' --with-nth=1,2 \
      --prompt="search > " \
      --header="TAB: toggle | ENTER: enable selected" \
      --preview="sed -n '1,40p' \"$PARKED/{1}/SKILL.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 \
      --prompt="search > " \
      --header="TAB: toggle | ENTER: enable selected" \
      --preview="sed -n '1,40p' \"$PARKED/{1}/SKILL.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 folder
  while IFS=$'\t' read -r folder _; do
    [ -z "$folder" ] && continue
    cmd_enable "$folder" || true
  done <<< "$selection"
}

# reindex: scan parked + active, regenerate INDEX.json / INDEX.md
cmd_reindex() {
  require_dirs
  python3 - "$PARKED" "$ACTIVE" "$INDEX_JSON" "$INDEX_MD" <<'PY'
import sys, os, 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_skill(d: Path):
    sm = d / "SKILL.md"
    name, desc = d.name, ""
    if sm.exists():
        try:
            txt = sm.read_text(errors="replace")
            m = re.match(r"^---\s*\n(.*?)\n---\s*\n", txt, re.DOTALL)
            if m:
                fm = m.group(1)
                n = re.search(r'^name:\s*"?([^"\n]+?)"?\s*$', fm, re.MULTILINE)
                dsc = re.search(r'^description:\s*"?(.+?)"?\s*$', fm, re.MULTILINE | re.DOTALL)
                if n: name = n.group(1).strip()
                if dsc: desc = dsc.group(1).strip()
        except Exception as e:
            desc = f"(read error: {e})"
    else:
        desc = "(no SKILL.md)"
    return name, desc

seen = {}
def scan(base: Path, status: str):
    if not base.exists(): return
    for d in sorted(p for p in base.iterdir() if p.is_dir() and p.name not in ("bin",) and not p.name.startswith(".")):
        name, desc = read_skill(d)
        key = d.name
        if key in seen:
            seen[key]["status"] = "both"
        else:
            seen[key] = {"folder": d.name, "name": name, "description": desc, "status": status}

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

items = sorted(seen.values(), key=lambda x: x["folder"])
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"))

lines = [
    "# Opencode Skills — Index",
    "",
    f"**{len(items)} unique skills** — active: {active_n}, parked: {parked_n}.",
    "",
    f"Active: `{active}`  |  Parked: `{parked}`",
    "",
    "See `README.md` for the `opc-skills` CLI.",
    "",
    "| # | Status | Folder | 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['folder']}` | {d} |")
out_md.write_text("\n".join(lines) + "\n")
print(f"reindexed: {len(items)} skills  (active={active_n}, parked={parked_n})")
PY
}

usage() {
  cat <<'USG'
opc-skills — opencode skill manager

  status                      counts of active vs parked
  list {active|parked|all}    list skill folder names
  categories                  prefix-based category counts
  enable <folder>             enable single skill (copy parked → active)
  disable <folder>            disable single skill (remove from active; keep parked)
  enable-category <prefix>    fzf multi-pick within a prefix, then enable
  pick                        fzf: choose category → multi-select → enable
  search [query]              fzf fuzzy search across name+description
  reindex                     rebuild INDEX.json / INDEX.md

Environment:
  OPC_PARKED   override parked dir  (default: ~/Documents/opencode-skills-parked)
  OPC_ACTIVE   override active dir  (default: ~/.config/opencode/skills)
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 "$@" ;;
    enable-category|enable-cat) cmd_enable_category "$@" ;;
    pick)             cmd_pick "$@" ;;
    search)           cmd_search "$@" ;;
    reindex)          cmd_reindex "$@" ;;
    -h|--help|help)   usage ;;
    *) echo "unknown command: $cmd" >&2; usage; exit 2 ;;
  esac
}

main "$@"
