#!/usr/bin/env bash
# tools/sync — invoked by `yarn skills` in consumer repos.
#
# Pulls latest MetaMask/skills (public) and/or Consensys/skills (private),
# walks the engineer through domain selection (or uses saved/env values),
# then exec into tools/install. Does not auto-clone — prints setup help if
# no source is configured.
#
# Domain selection order of precedence:
#   1. --domain CLI flag           (one-off override)
#   2. SKILLS_DOMAINS env var      (CI-friendly)
#   3. saved selection in <target>/.skills.local
#   4. interactive prompt (TTY)
#   5. all domains (fallback)
#
# Skills with `mandatory: true` install regardless of selection.
# Skills with `scope: user` install at user-level (~/.claude, ~/.codex).
#
# Env (at least one required):
#   METAMASK_SKILLS_DIR   Path to local MetaMask/skills source (checkout, cache, or package).
#   CONSENSYS_SKILLS_DIR  Path to local Consensys/skills source (private overlay).
#                         When both set, private overrides public on name conflict.
#   SKILLS_DOMAINS        Comma-separated domain filter (overrides saved + prompt).
#   SKILLS_MATURITY       Minimum maturity: experimental, stable, deprecated.
#   SKILLS_INCLUDE        Comma-separated skill opt-ins (domain/skill or skill).
#   SKILLS_EXCLUDE        Comma-separated skill opt-outs (domain/skill or skill).
#
# Usage (via `yarn skills`):
#   tools/sync --repo metamask-extension --target /path/to/repo
#   tools/sync --repo metamask-mobile --target . --domain perps,testing --dry-run
#   tools/sync --repo core --target . --domain perps --dry-run
#   tools/sync --repo metamask-mobile --target . --reset      # forget saved selection

set -euo pipefail

REPO_NAME=""
TARGET="$(pwd)"
DOMAIN_OVERRIDE=""
MATURITY_FILTER=""
MATURITY_SOURCE=""
INCLUDE_FILTER=""
INCLUDE_SOURCE=""
EXCLUDE_FILTER=""
EXCLUDE_SOURCE=""
DRY_RUN=false
RESET=false
SAVE=true
SAVE_REQUESTED=false
SELECT=false
INCLUDE_USER=false

usage() {
  cat <<EOF
Usage: $(basename "$0") --target <path> [options]

Required:
  --target <path>      Path to consumer repo (default: cwd)

Options:
  --repo <name>        Consumer repo (auto-detected from <target>/package.json)
  --domain <list>      Comma-separated domain opt-in filter (overrides default-all)
  --maturity <level>    Minimum maturity: experimental, stable, deprecated
  --include <list>      Comma-separated skill opt-ins (domain/skill or skill).
                       Repeatable. Alias: --skill.
  --exclude <list>      Comma-separated skill opt-outs (domain/skill or skill).
                       Repeatable.
  --save               Persist CLI domain/maturity/include/exclude to .skills.local
  --select             Interactively pick which domains to install (requires
                       a TTY). Default behavior is install all — use this to
                       opt out of some.
  --include-user       Also install skills with \`scope: user\` (writes to
                       \$HOME — outside this repo). Off by default.
  --dry-run            Preview without writing
  --reset              Forget saved selection in <target>/.skills.local
  --no-save            Don't persist selection to .skills.local
  -h, --help           Show this help

Env:
  METAMASK_SKILLS_DIR  Path to local MetaMask/skills checkout (public, no auth)
  CONSENSYS_SKILLS_DIR Path to local Consensys/skills checkout (private overlay)
  SKILLS_DOMAINS       Domain filter (overrides saved selection; blank = all)
  SKILLS_MATURITY      Minimum maturity filter (default: stable)
  SKILLS_INCLUDE       Skill opt-ins; explicit includes bypass domain/maturity
  SKILLS_EXCLUDE       Skill opt-outs; explicit excludes win
EOF
  exit "${1:-1}"
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --repo)          REPO_NAME="$2"; shift 2 ;;
    --target)        TARGET="$2"; shift 2 ;;
    --domain)        DOMAIN_OVERRIDE="$2"; shift 2 ;;
    --maturity)      MATURITY_FILTER="$2"; shift 2 ;;
    --include|--skill) INCLUDE_FILTER="${INCLUDE_FILTER:+$INCLUDE_FILTER,}$2"; INCLUDE_SOURCE="--include flag"; shift 2 ;;
    --exclude)       EXCLUDE_FILTER="${EXCLUDE_FILTER:+$EXCLUDE_FILTER,}$2"; EXCLUDE_SOURCE="--exclude flag"; shift 2 ;;
    --save)          SAVE_REQUESTED=true; shift ;;
    --select)        SELECT=true; shift ;;
    --include-user)  INCLUDE_USER=true; shift ;;
    --dry-run)       DRY_RUN=true; shift ;;
    --reset)         RESET=true; shift ;;
    --no-save)       SAVE=false; shift ;;
    -h|--help)       usage 0 ;;
    *)               echo "Unknown arg: $1" >&2; usage ;;
  esac
done

[[ ! -d "$TARGET" ]] && { echo "Error: target not found: $TARGET" >&2; exit 1; }
SOURCES=()
PRIMARY_SKILLS_DIR=""

validate_source() {
  local name="$1" dir="$2" clone_hint="$3"
  if [[ ! -d "$dir/domains" || ! -d "$dir/tools" ]]; then
    cat >&2 <<EOF
$name points to "$dir", but that's not a MetaMask skills source.

Either fix the path or clone the source repo there:
  git clone $clone_hint "$dir"
EOF
    exit 1
  fi
}

if [[ -n "${METAMASK_SKILLS_DIR:-}" ]]; then
  validate_source "METAMASK_SKILLS_DIR" "$METAMASK_SKILLS_DIR" "https://github.com/MetaMask/skills.git"
  SOURCES+=("$METAMASK_SKILLS_DIR")
  PRIMARY_SKILLS_DIR="$METAMASK_SKILLS_DIR"
fi

if [[ -n "${CONSENSYS_SKILLS_DIR:-}" ]]; then
  validate_source "CONSENSYS_SKILLS_DIR" "$CONSENSYS_SKILLS_DIR" "git@github.com:Consensys/skills.git"
  SOURCES+=("$CONSENSYS_SKILLS_DIR")
  [[ -z "$PRIMARY_SKILLS_DIR" ]] && PRIMARY_SKILLS_DIR="$CONSENSYS_SKILLS_DIR"
fi

if [[ ${#SOURCES[@]} -eq 0 ]]; then
  cat >&2 <<EOF
No skill source configured. Set at least one of:

  METAMASK_SKILLS_DIR   public MetaMask/skills checkout (no auth needed)
  CONSENSYS_SKILLS_DIR  private Consensys/skills checkout (internal overlay)

To set up (one time, public — recommended starting point):
  1. Clone the public source repo:
       git clone https://github.com/MetaMask/skills.git ~/dev/metamask/skills
  2. Export the env var (add to your shell rc):
       export METAMASK_SKILLS_DIR=~/dev/metamask/skills

For private overlays (Consensys internal):
       git clone git@github.com:Consensys/skills.git ~/dev/Consensys/skills
       export CONSENSYS_SKILLS_DIR=~/dev/Consensys/skills

Once configured, re-run \`yarn skills\`.
EOF
  exit 1
fi

CONFIG_FILE="$TARGET/.skills.local"

if $RESET; then
  if [[ -f "$CONFIG_FILE" ]]; then
    rm -f "$CONFIG_FILE"
    echo "Removed saved selection: $CONFIG_FILE"
  else
    echo "No saved selection at $CONFIG_FILE"
  fi
  exit 0
fi

if $SELECT && [[ ! -t 0 ]]; then
  cat >&2 <<EOF
Error: --select requires an interactive TTY.
Use --domain <list> or SKILLS_DOMAINS=<list> for non-interactive sync.
EOF
  exit 1
fi

for src in "${SOURCES[@]}"; do
  if [[ ! -d "$src/.git" ]]; then
    echo "Using packaged skills source: $src"
    continue
  fi

  echo "Updating $src"
  if ! pull_output=$(git -C "$src" pull --ff-only 2>&1); then
    printf '%s\n' "$pull_output" | sed 's/^/  /' >&2
    cat >&2 <<EOF
Error: failed to update configured skill source: $src
Refusing to install potentially stale skills. Fix the checkout (commit/stash/rebase, restore connectivity, or point the env var at an up-to-date source) and rerun.
EOF
    exit 1
  fi
  printf '%s\n' "$pull_output" | sed 's/^/  /'
done

# ---- domain resolution ----

load_saved() {
  [[ -f "$CONFIG_FILE" ]] || return 0
  awk -F= '/^SKILLS_DOMAINS=/{sub(/^SKILLS_DOMAINS=/,""); print; exit}' "$CONFIG_FILE"
}

load_saved_maturity() {
  [[ -f "$CONFIG_FILE" ]] || return 0
  awk -F= '/^SKILLS_MATURITY=/{sub(/^SKILLS_MATURITY=/,""); print; exit}' "$CONFIG_FILE"
}

load_saved_include() {
  [[ -f "$CONFIG_FILE" ]] || return 0
  awk -F= '/^SKILLS_INCLUDE=/{sub(/^SKILLS_INCLUDE=/,""); print; exit}' "$CONFIG_FILE"
}

load_saved_exclude() {
  [[ -f "$CONFIG_FILE" ]] || return 0
  awk -F= '/^SKILLS_EXCLUDE=/{sub(/^SKILLS_EXCLUDE=/,""); print; exit}' "$CONFIG_FILE"
}

if [[ -n "$MATURITY_FILTER" ]]; then
  MATURITY_SOURCE="--maturity flag"
elif [[ -n "${SKILLS_MATURITY:-}" ]]; then
  MATURITY_FILTER="$SKILLS_MATURITY"
  MATURITY_SOURCE="SKILLS_MATURITY env"
else
  saved_maturity=$(load_saved_maturity || true)
  if [[ -n "$saved_maturity" ]]; then
    MATURITY_FILTER="$saved_maturity"
    MATURITY_SOURCE="saved (.skills.local)"
  else
    MATURITY_FILTER="stable"
    MATURITY_SOURCE="default"
  fi
fi
case "$MATURITY_FILTER" in
  experimental|stable|deprecated) ;;
  *) echo "Error: --maturity must be experimental|stable|deprecated" >&2; exit 1 ;;
esac

if [[ -n "$INCLUDE_FILTER" ]]; then
  : "${INCLUDE_SOURCE:=--include flag}"
elif [[ -n "${SKILLS_INCLUDE:-}" ]]; then
  INCLUDE_FILTER="$SKILLS_INCLUDE"
  INCLUDE_SOURCE="SKILLS_INCLUDE env"
else
  saved_include=$(load_saved_include || true)
  if [[ -n "$saved_include" ]]; then
    INCLUDE_FILTER="$saved_include"
    INCLUDE_SOURCE="saved (.skills.local)"
  fi
fi

if [[ -n "$EXCLUDE_FILTER" ]]; then
  : "${EXCLUDE_SOURCE:=--exclude flag}"
elif [[ -n "${SKILLS_EXCLUDE:-}" ]]; then
  EXCLUDE_FILTER="$SKILLS_EXCLUDE"
  EXCLUDE_SOURCE="SKILLS_EXCLUDE env"
else
  saved_exclude=$(load_saved_exclude || true)
  if [[ -n "$saved_exclude" ]]; then
    EXCLUDE_FILTER="$saved_exclude"
    EXCLUDE_SOURCE="saved (.skills.local)"
  fi
fi


domain_has_mandatory() {
  local domain_dir="$1"
  grep -lE '^mandatory:[[:space:]]*(true|yes|1)' "$domain_dir"/skills/*/skill.md >/dev/null 2>&1
}

list_domains() {
  # Union of domain names across all configured sources, dedup, sorted.
  for src in "${SOURCES[@]}"; do
    for d in "$src"/domains/*/; do
      [[ -d "$d" ]] || continue
      basename "$d"
    done
  done | sort -u
}

domain_dirs_for() {
  local domain="$1"
  for src in "${SOURCES[@]}"; do
    [[ -d "$src/domains/$domain" ]] && printf '%s\n' "$src/domains/$domain"
  done
}

interactive_select() {
  local domains=()
  while IFS= read -r d; do domains+=("$d"); done < <(list_domains)

  echo >&2
  echo "Available domains:" >&2
  local i=0
  for d in "${domains[@]}"; do
    i=$((i+1))
    local marker=""
    while IFS= read -r dd; do
      domain_has_mandatory "$dd" && { marker=" ★"; break; }
    done < <(domain_dirs_for "$d")
    printf "  %2d) %s%s\n" "$i" "$d" "$marker" >&2
  done
  echo >&2
  echo "  ★ = contains mandatory skills (always installed regardless)" >&2
  echo >&2

  local choice
  echo "Default is install ALL domains. Pick a subset to opt out of the rest." >&2
  read -rp "Pick (numbers comma-separated, blank/'a' = all [recommended]): " choice
  if [[ -z "$choice" || "$choice" == "a" || "$choice" == "all" ]]; then
    return 0
  fi

  local out="" n
  IFS=',' read -ra sel <<< "$choice"
  for n in "${sel[@]}"; do
    n="${n//[[:space:]]/}"
    if [[ "$n" =~ ^[0-9]+$ ]] && (( n >= 1 && n <= ${#domains[@]} )); then
      out="${out:+$out,}${domains[$((n-1))]}"
    else
      echo "  (skipping invalid: $n)" >&2
    fi
  done
  echo "$out"
}

# Resolution order: --domain > env > saved > --select prompt > all (default)
DOMAINS=""
SOURCE=""
if [[ -n "$DOMAIN_OVERRIDE" ]]; then
  DOMAINS="$DOMAIN_OVERRIDE"; SOURCE="--domain flag"
elif [[ -n "${SKILLS_DOMAINS:-}" ]]; then
  DOMAINS="$SKILLS_DOMAINS"; SOURCE="SKILLS_DOMAINS env"
elif $SELECT && [[ -t 0 ]]; then
  DOMAINS=$(interactive_select); SOURCE="interactive (--select)"
else
  saved=$(load_saved || true)
  if [[ -n "$saved" ]]; then
    DOMAINS="$saved"; SOURCE="saved (.skills.local)"
  else
    SOURCE="all (default — pass --select to choose)"
  fi
fi

echo
echo "Maturity: $MATURITY_FILTER  (source: $MATURITY_SOURCE)"
if [[ -n "$DOMAINS" ]]; then
  echo "Domains: $DOMAINS  (source: $SOURCE)"
else
  echo "Domains: <all>  (source: $SOURCE)"
fi
if [[ -n "$INCLUDE_FILTER" ]]; then
  echo "Include skills: $INCLUDE_FILTER  (source: $INCLUDE_SOURCE)"
fi
if [[ -n "$EXCLUDE_FILTER" ]]; then
  echo "Exclude skills: $EXCLUDE_FILTER  (source: $EXCLUDE_SOURCE)"
fi

# Persist if interactive selection produced something new, or if CLI config was
# explicitly marked --save. Plain CLI flags remain one-off by default.
if $SAVE && { [[ "$SOURCE" == "interactive (--select)" ]] || $SAVE_REQUESTED; }; then
  cat > "$CONFIG_FILE" <<EOF
# Auto-generated by MetaMask/skills tools/sync.
# Edit to change what \`yarn skills\` installs by default.
# Use \`--reset\` to wipe; CLI flags are one-off unless passed with \`--save\`.
# SKILLS_INCLUDE and SKILLS_EXCLUDE accept comma-separated domain/skill or skill names.
SKILLS_DOMAINS=$DOMAINS
SKILLS_MATURITY=$MATURITY_FILTER
SKILLS_INCLUDE=$INCLUDE_FILTER
SKILLS_EXCLUDE=$EXCLUDE_FILTER
EOF
  echo "Saved to $CONFIG_FILE"
fi

# ---- delegate to install ----
echo
INSTALL_ARGS=(--target "$TARGET")
[[ -n "$REPO_NAME" ]] && INSTALL_ARGS+=(--repo "$REPO_NAME")
for src in "${SOURCES[@]}"; do
  INSTALL_ARGS+=(--source "$src")
done
[[ -n "$DOMAINS" ]] && INSTALL_ARGS+=(--domain "$DOMAINS")
INSTALL_ARGS+=(--maturity "$MATURITY_FILTER")
[[ -n "$INCLUDE_FILTER" ]] && INSTALL_ARGS+=(--include "$INCLUDE_FILTER")
[[ -n "$EXCLUDE_FILTER" ]] && INSTALL_ARGS+=(--exclude "$EXCLUDE_FILTER")
$INCLUDE_USER && INSTALL_ARGS+=(--include-user)
$DRY_RUN && INSTALL_ARGS+=(--dry-run)

INSTALL_BIN=""
# Prefer METAMASK_SKILLS_DIR's tools/install (canonical, kept in sync with latest CLI).
if [[ -n "${METAMASK_SKILLS_DIR:-}" && -x "$METAMASK_SKILLS_DIR/tools/install" ]]; then
  INSTALL_BIN="$METAMASK_SKILLS_DIR/tools/install"
else
  for src in "${SOURCES[@]}"; do
    if [[ -x "$src/tools/install" ]]; then
      INSTALL_BIN="$src/tools/install"
    fi
  done
fi
if [[ -z "$INSTALL_BIN" ]]; then
  echo "Error: no tools/install found in any configured source." >&2
  echo "  Sources: ${SOURCES[*]}" >&2
  exit 1
fi
exec "$INSTALL_BIN" "${INSTALL_ARGS[@]}"
