#!/usr/bin/env bash
# Install MetaMask skills from one or more source repos into a consuming repo.
#
# Per ADR #57 (decisions/core/0057-centralized-agent-skills-repo.md):
#   - Skills land in .claude/skills/, .agents/skills/, .cursor/rules/
#   - Output names are prefixed mms-<skill-name>
#   - Source frontmatter stays unprefixed
#   - --domain and --maturity filters; default --maturity stable
#
# Sources:
#   - Default: the repo containing this script.
#   - --source <dir> (repeatable, ordered): walk one or more skill repos.
#     Later sources override earlier ones on `name` collision — private
#     overlays beat the public base.
#
# Usage:
#   tools/install --repo metamask-extension --target ~/dev/metamask/metamask-extension
#   tools/install --repo metamask-mobile --target ~/dev/metamask/metamask-mobile --dry-run
#   tools/install --repo core --target ~/dev/metamask/core --domain perps --dry-run
#   tools/install --repo metamask-extension --target /path --domain perps,testing
#   tools/install --repo metamask-extension --target /path --maturity experimental
#   tools/install --repo metamask-extension --target /path \
#                 --source /path/to/metamask-skills --source /path/to/consensys-skills

set -euo pipefail

REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
REPO=""
TARGET=""
DOMAIN_FILTER=""
MATURITY_FILTER="stable"
INCLUDE_FILTER=""
EXCLUDE_FILTER=""
DRY_RUN=false
INCLUDE_USER=false
SOURCES=()

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

Required:
  --target <path>     Path to the consuming repo

Optional:
  --repo <name>       GitHub repo name (auto-detected from <target>/package.json
                      name field, falling back to basename of --target).
                      Override e.g., --repo metamask-extension or --repo core.
  --source <dir>      Skill source repo (repeatable, ordered). Later sources
                      override earlier ones on name conflict. Defaults to the
                      repo containing this script.
  --domain <list>     Comma-separated domain filter (default: all)
                      e.g., --domain perps,testing
  --maturity <level>  Minimum maturity: experimental, stable, deprecated
                      (default: stable; matches stable+deprecated when set to stable)
  --include <list>    Comma-separated skill opt-ins. Each item may be
                      domain/skill or skill; explicit includes bypass domain
                      and maturity filters. Repeatable. Alias: --skill.
  --exclude <list>    Comma-separated skill opt-outs. Each item may be
                      domain/skill or skill; explicit excludes win. Repeatable.
  --include-user      Also install skills with \`scope: user\` (writes to
                      \$HOME/.claude/skills and \$HOME/.codex/skills, outside
                      the target repo). Off by default — warns + lists them.
  --dry-run           Preview without writing files
  -h, --help          Show this help

Examples:
  $(basename "$0") --repo metamask-extension --target ~/dev/metamask/metamask-extension
  $(basename "$0") --repo metamask-mobile --target ~/dev/metamask/metamask-mobile --domain perps --dry-run
  $(basename "$0") --repo core --target ~/dev/metamask/core --domain perps --dry-run
  $(basename "$0") --repo metamask-extension --target . \\
    --source ~/dev/metamask/skills --source ~/dev/Consensys/skills
EOF
  exit "${1:-1}"
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --repo)          REPO="$2"; shift 2 ;;
    --target)        TARGET="$2"; shift 2 ;;
    --source)        SOURCES+=("$2"); shift 2 ;;
    --domain)        DOMAIN_FILTER="$2"; shift 2 ;;
    --maturity)      MATURITY_FILTER="$2"; shift 2 ;;
    --include|--skill) INCLUDE_FILTER="${INCLUDE_FILTER:+$INCLUDE_FILTER,}$2"; shift 2 ;;
    --exclude)       EXCLUDE_FILTER="${EXCLUDE_FILTER:+$EXCLUDE_FILTER,}$2"; shift 2 ;;
    --include-user)  INCLUDE_USER=true; shift ;;
    --dry-run)       DRY_RUN=true; shift ;;
    -h|--help)       usage 0 ;;
    *)               echo "Unknown arg: $1"; usage ;;
  esac
done

[[ -z "$TARGET" ]] && usage
[[ ! -d "$TARGET" ]] && { echo "Error: target directory does not exist: $TARGET" >&2; exit 1; }

# Auto-detect --repo from <target>/package.json name, fallback to dirname.
if [[ -z "$REPO" ]]; then
  if [[ -f "$TARGET/package.json" ]]; then
    REPO=$(awk -F'"' '/"name"[[:space:]]*:[[:space:]]*"/ {print $4; exit}' "$TARGET/package.json")
  fi
  [[ -z "$REPO" ]] && REPO=$(basename "$(cd "$TARGET" && pwd)")
fi

if [[ ${#SOURCES[@]} -eq 0 ]]; then
  SOURCES=("$REPO_ROOT")
fi

for src in "${SOURCES[@]}"; do
  [[ -d "$src/domains" ]] || { echo "Error: --source $src has no domains/ tree" >&2; exit 1; }
done

case "$MATURITY_FILTER" in
  experimental|stable|deprecated) ;;
  *) echo "Error: --maturity must be experimental|stable|deprecated" >&2; exit 1 ;;
esac

CLAUDE_DIR="$TARGET/.claude/skills"
CURSOR_DIR="$TARGET/.cursor/rules"
AGENTS_DIR="$TARGET/.agents/skills"

# User-scope (global) target dirs, used when a skill declares `scope: user`.
# Only written if the parent (~/.claude, ~/.codex) already exists.
USER_CLAUDE_DIR="$HOME/.claude/skills"
USER_CODEX_DIR="$HOME/.codex/skills"

PREFIX="mms-"
MANAGED_BANNER="<!-- DO NOT EDIT — Generated by MetaMask skills tools/install. Changes will be overwritten on next sync. Edit the source skill at https://github.com/MetaMask/skills (public) or https://github.com/Consensys/skills (private overlay) instead. -->"

log()    { echo "  $1"; }
action() { if $DRY_RUN; then echo "  [dry-run] $1"; else echo "  $1"; fi; }

frontmatter_value() {
  local file="$1" key="$2"
  awk -v k="$key" '
    /^---$/ {
      n++
      if (n >= 2 && in_block) { print out; printed=1; exit }
      next
    }
    n==1 {
      if (in_block) {
        if ($0 ~ /^[[:space:]]+/) {
          line=$0
          sub(/^[[:space:]]+/, "", line)
          if (line != "") out = out (out == "" ? "" : " ") line
          next
        }
        print out
        printed=1
        exit
      }

      prefix="^" k ":[[:space:]]*"
      if ($0 ~ prefix) {
        sub(prefix, "")
        if ($0 ~ /^[>|][+-]?$/) {
          in_block=1
          out=""
          next
        }
        print
        exit
      }
    }
    n>=2 { exit }
    END { if (in_block && !printed) print out }
  ' "$file"
}

body_after_frontmatter() {
  awk 'BEGIN{n=0} /^---$/{n++; next} n>=2{print}' "$1"
}

domain_allowed() {
  local domain="$1"
  [[ -z "$DOMAIN_FILTER" ]] && return 0
  local d
  IFS=',' read -ra ds <<< "$DOMAIN_FILTER"
  for d in "${ds[@]}"; do [[ "$d" == "$domain" ]] && return 0; done
  return 1
}

skill_list_matches() {
  local list="$1" domain="$2" skill="$3" item normalized item_domain item_skill
  [[ -z "$list" ]] && return 1
  IFS=',' read -ra items <<< "$list"
  for item in "${items[@]}"; do
    normalized="${item//[[:space:]]/}"
    [[ -z "$normalized" ]] && continue
    normalized="${normalized%%@*}"
    if [[ "$normalized" == */* ]]; then
      item_domain="${normalized%%/*}"
      item_skill="${normalized#*/}"
      [[ "$item_domain" == "$domain" && "$item_skill" == "$skill" ]] && return 0
    else
      [[ "$normalized" == "$skill" ]] && return 0
    fi
  done
  return 1
}

skill_included() { skill_list_matches "$INCLUDE_FILTER" "$1" "$2"; }
skill_excluded() { skill_list_matches "$EXCLUDE_FILTER" "$1" "$2"; }

is_truthy() {
  case "${1:-}" in
    true|yes|1|TRUE|YES|True|Yes) return 0 ;;
    *) return 1 ;;
  esac
}

yaml_field() {
  local key="$1" value="$2" indent=""
  if [[ "$key" =~ ^([[:space:]]*) ]]; then
    indent="${BASH_REMATCH[1]}"
  fi
  printf '%s: >-\n' "$key"
  while IFS= read -r line || [[ -n "$line" ]]; do
    printf '%s  %s\n' "$indent" "$line"
  done <<< "$value"
}

yaml_quoted() {
  local value="$1"
  value="${value//\\/\\\\}"
  value="${value//"/\\"}"
  printf '"%s"' "$value"
}

maturity_allowed() {
  local skill_maturity="${1:-stable}"
  case "$MATURITY_FILTER" in
    experimental) return 0 ;;
    stable)
      [[ "$skill_maturity" == "stable" || "$skill_maturity" == "deprecated" ]]
      ;;
    deprecated)
      [[ "$skill_maturity" == "deprecated" ]]
      ;;
  esac
}

write_claude() {
  local out_name="$1" name="$2" description="$3" content="$4"
  local dir="$CLAUDE_DIR/$out_name"
  local file="$dir/SKILL.md"
  action "claude: .claude/skills/$out_name/SKILL.md"
  $DRY_RUN && return
  mkdir -p "$dir"
  {
    echo '---'
    echo "name: ${name}"
    yaml_field description "$description"
    echo '---'
    echo "${MANAGED_BANNER}"
    echo "${content}"
  } > "$file"
}

write_cursor() {
  local out_name="$1" description="$2" content="$3"
  local dir="$CURSOR_DIR/$out_name"
  local file="$dir/RULE.md"
  action "cursor: .cursor/rules/$out_name/RULE.md"
  $DRY_RUN && return
  mkdir -p "$dir"
  {
    echo '---'
    yaml_field description "$description"
    echo 'alwaysApply: false'
    echo '---'
    echo "${MANAGED_BANNER}"
    echo "${content}"
  } > "$file"
}

write_agents() {
  local out_name="$1" name="$2" description="$3" content="$4"
  local dir="$AGENTS_DIR/$out_name"
  local file="$dir/SKILL.md"
  local yaml_dir="$dir/agents"
  local yaml="$yaml_dir/openai.yaml"
  action "agents: .agents/skills/$out_name/SKILL.md"
  action "agents: .agents/skills/$out_name/agents/openai.yaml"
  $DRY_RUN && return
  mkdir -p "$yaml_dir"
  {
    echo '---'
    echo "name: ${name}"
    yaml_field description "$description"
    echo '---'
    echo "${MANAGED_BANNER}"
    echo "${content}"
  } > "$file"
  {
    echo 'interface:'
    printf '  display_name: '; yaml_quoted "$name"; echo
    yaml_field '  short_description' "$description"
    printf '  default_prompt: '; yaml_quoted "Use \$${out_name} for this task."; echo
  } > "$yaml"
}

# User-scope writers — write to the engineer's home dirs instead of the
# consuming repo. Only target operator dirs that already exist (don't
# bootstrap an operator install). Skip overlays; user-scope is repo-agnostic.
write_user_claude() {
  local out_name="$1" name="$2" description="$3" content="$4"
  [[ -d "$HOME/.claude" ]] || return 0
  local dir="$USER_CLAUDE_DIR/$out_name"
  local file="$dir/SKILL.md"
  action "user-claude: ~/.claude/skills/$out_name/SKILL.md"
  $DRY_RUN && return
  mkdir -p "$dir"
  {
    echo '---'
    echo "name: ${name}"
    yaml_field description "$description"
    echo '---'
    echo "${MANAGED_BANNER}"
    echo "${content}"
  } > "$file"
}

write_user_codex() {
  local out_name="$1" name="$2" description="$3" content="$4"
  [[ -d "$HOME/.codex" ]] || return 0
  local dir="$USER_CODEX_DIR/$out_name"
  local file="$dir/SKILL.md"
  action "user-codex:  ~/.codex/skills/$out_name/SKILL.md"
  $DRY_RUN && return
  mkdir -p "$dir"
  {
    echo '---'
    echo "name: ${name}"
    yaml_field description "$description"
    echo '---'
    echo "${MANAGED_BANNER}"
    echo "${content}"
  } > "$file"
}

copy_bundle_dirs() {
  local skill_dir="$1" dest_dir="$2" label="$3"
  local bundle
  for bundle in references scripts assets adapters; do
    if [[ -d "$skill_dir/$bundle" ]]; then
      action "$label/$bundle/"
      $DRY_RUN && continue
      mkdir -p "$dest_dir"
      rm -rf "$dest_dir/$bundle"
      cp -R "$skill_dir/$bundle" "$dest_dir/$bundle"
    else
      if $DRY_RUN; then
        [[ -e "$dest_dir/$bundle" ]] && action "$label/$bundle/ (remove stale)"
        continue
      fi
      # Remove stale bundles left by older versions of this skill.
      rm -rf "$dest_dir/$bundle"
    fi
  done
}

copy_project_bundles() {
  local skill_dir="$1" out_name="$2"
  copy_bundle_dirs "$skill_dir" "$CLAUDE_DIR/$out_name" ".claude/skills/$out_name"
  copy_bundle_dirs "$skill_dir" "$CURSOR_DIR/$out_name" ".cursor/rules/$out_name"
  copy_bundle_dirs "$skill_dir" "$AGENTS_DIR/$out_name" ".agents/skills/$out_name"
}

copy_user_bundles() {
  local skill_dir="$1" out_name="$2"
  [[ -d "$HOME/.claude" ]] && copy_bundle_dirs "$skill_dir" "$USER_CLAUDE_DIR/$out_name" "~/.claude/skills/$out_name"
  [[ -d "$HOME/.codex" ]] && copy_bundle_dirs "$skill_dir" "$USER_CODEX_DIR/$out_name" "~/.codex/skills/$out_name"
}

process_skill() {
  local skill_dir="$1" domain_name="$2"
  local skill_name; skill_name=$(basename "$skill_dir")
  local base="$skill_dir/skill.md"
  local overlay="$skill_dir/repos/${REPO}.md"

  [[ -f "$base" ]] || { echo "Warning: no skill.md in $skill_dir, skipping" >&2; return; }

  local maturity;  maturity=$(frontmatter_value "$base" "maturity")
  local mandatory; mandatory=$(frontmatter_value "$base" "mandatory")
  local scope;     scope=$(frontmatter_value "$base" "scope")

  if skill_excluded "$domain_name" "$skill_name"; then
    log "skipped (explicitly excluded by --exclude/SKILLS_EXCLUDE)"
    return
  fi

  local explicit_include=false
  skill_included "$domain_name" "$skill_name" && explicit_include=true

  if ! $explicit_include && ! maturity_allowed "$maturity"; then
    log "skipped (maturity=${maturity:-stable} excluded by --maturity=$MATURITY_FILTER)"
    return
  fi

  # Domain filter — but mandatory skills and explicit skill includes bypass.
  if ! $explicit_include && ! domain_allowed "$domain_name"; then
    if is_truthy "$mandatory"; then
      log "(mandatory — included despite domain filter)"
    else
      log "skipped (domain=$domain_name excluded by --domain)"
      return
    fi
  fi

  if $explicit_include; then
    log "included (explicit skill opt-in)"
  fi

  local name;        name=$(frontmatter_value "$base" "name")
  local description; description=$(frontmatter_value "$base" "description")

  if [[ -z "$name" ]]; then
    echo "Error: $base missing 'name' frontmatter" >&2
    FAILED_SKILLS+=("$base missing name")
    return
  fi
  if [[ -z "$description" ]]; then
    echo "Error: $base missing 'description' frontmatter" >&2
    FAILED_SKILLS+=("$base missing description")
    return
  fi

  local out_name="${PREFIX}${name}"

  # User-scope skills: write outside the target repo (\$HOME/.claude, \$HOME/.codex).
  # Off by default — collected for a summary warning so engineers can opt in
  # manually via --include-user. Never auto-sync to home dir.
  if [[ "$scope" == "user" ]]; then
    if ! $INCLUDE_USER; then
      SKIPPED_USER_SKILLS+=("$name")
      log "skipped (scope=user — pass --include-user to install to \$HOME)"
      return
    fi
    local body; body=$(body_after_frontmatter "$base")
    write_user_claude "$out_name" "$out_name" "$description" "$body"
    write_user_codex  "$out_name" "$out_name" "$description" "$body"
    copy_user_bundles "$skill_dir" "$out_name"
    return
  fi

  # Project-scope below — needs --repo overlay match.
  if [[ -d "$skill_dir/repos" && ! -f "$overlay" ]]; then
    log "skipped (no overlay for $REPO)"
    return
  fi

  local base_content; base_content=$(body_after_frontmatter "$base")
  local merged="$base_content"
  if [[ -f "$overlay" ]]; then
    local overlay_content; overlay_content=$(body_after_frontmatter "$overlay")
    merged="${merged}

${overlay_content}"
  fi

  write_claude "$out_name" "$out_name" "$description" "$merged"
  write_cursor "$out_name" "$description" "$merged"
  write_agents "$out_name" "$out_name" "$description" "$merged"
  copy_project_bundles "$skill_dir" "$out_name"
}

preflight_skill() {
  local skill_dir="$1" domain_name="$2"
  local base="$skill_dir/skill.md"

  [[ -f "$base" ]] || return 0

  local maturity;  maturity=$(frontmatter_value "$base" "maturity")
  local mandatory; mandatory=$(frontmatter_value "$base" "mandatory")

  local skill_name; skill_name=$(basename "$skill_dir")
  skill_excluded "$domain_name" "$skill_name" && return 0

  local explicit_include=false
  skill_included "$domain_name" "$skill_name" && explicit_include=true

  if ! $explicit_include && ! maturity_allowed "$maturity"; then
    return 0
  fi

  if ! $explicit_include && ! domain_allowed "$domain_name"; then
    is_truthy "$mandatory" || return 0
  fi

  local name;        name=$(frontmatter_value "$base" "name")
  local description; description=$(frontmatter_value "$base" "description")

  if [[ -z "$name" ]]; then
    FAILED_SKILLS+=("$base missing name")
  fi
  if [[ -z "$description" ]]; then
    FAILED_SKILLS+=("$base missing description")
  fi
}

echo "MetaMask skills install"
echo "  repo:     $REPO"
echo "  target:   $TARGET"
echo "  sources:  ${SOURCES[*]}"
echo "  domain:   ${DOMAIN_FILTER:-<all>}"
echo "  maturity: $MATURITY_FILTER"
echo "  include:  ${INCLUDE_FILTER:-<none>}"
echo "  exclude:  ${EXCLUDE_FILTER:-<none>}"
echo "  dry-run:  $DRY_RUN"

# Two-pass resolution: collect (domain, skill_dir) per name across all sources,
# letting later sources overwrite earlier ones. Then process the resolved set.
# Keep this Bash 3-compatible: macOS /bin/bash is still 3.2, so avoid
# associative arrays here.
RESOLVED_KEYS=()
RESOLVED_DIRS=()
RESOLVED_DOMAINS=()
SKIPPED_USER_SKILLS=()
FAILED_SKILLS=()

find_resolved_index() {
  local needle="$1"
  local i
  for ((i = 0; i < ${#RESOLVED_KEYS[@]}; i++)); do
    if [[ "${RESOLVED_KEYS[$i]}" == "$needle" ]]; then
      echo "$i"
      return 0
    fi
  done
  echo "-1"
}

for src in "${SOURCES[@]}"; do
  for domain_dir in "$src"/domains/*/; do
    [[ -d "$domain_dir" ]] || continue
    domain_name=$(basename "$domain_dir")
    for skill_dir in "$domain_dir"/skills/*/; do
      [[ -d "$skill_dir" ]] || continue
      skill_name=$(basename "$skill_dir")
      key="${domain_name}/${skill_name}"
      resolved_index=$(find_resolved_index "$key")
      if [[ "$resolved_index" == "-1" ]]; then
        RESOLVED_KEYS+=("$key")
        RESOLVED_DIRS+=("$skill_dir")
        RESOLVED_DOMAINS+=("$domain_name")
      else
        RESOLVED_DIRS[$resolved_index]="$skill_dir"
        RESOLVED_DOMAINS[$resolved_index]="$domain_name"
      fi
    done
  done
done

for ((i = 0; i < ${#RESOLVED_KEYS[@]}; i++)); do
  preflight_skill "${RESOLVED_DIRS[$i]}" "${RESOLVED_DOMAINS[$i]}"
done

set +u
FAILED_SKILL_COUNT=${#FAILED_SKILLS[@]}
set -u
if (( FAILED_SKILL_COUNT > 0 )); then
  echo
  echo "Error: $FAILED_SKILL_COUNT malformed skill(s) encountered before install:" >&2
  for s in "${FAILED_SKILLS[@]}"; do echo "  - $s" >&2; done
  echo "No target files were written." >&2
  exit 1
fi

current_domain=""
for ((i = 0; i < ${#RESOLVED_KEYS[@]}; i++)); do
  domain_name="${RESOLVED_DOMAINS[$i]}"
  if [[ "$domain_name" != "$current_domain" ]]; then
    echo
    echo "Domain: $domain_name"
    current_domain="$domain_name"
  fi
  log "skill: $(basename "${RESOLVED_DIRS[$i]}")"
  process_skill "${RESOLVED_DIRS[$i]}" "$domain_name"
done

echo
$DRY_RUN && echo "Dry run complete. No files written." || echo "Install complete."

set +u
SKIPPED_USER_COUNT=${#SKIPPED_USER_SKILLS[@]}
set -u
if (( SKIPPED_USER_COUNT > 0 )); then
  echo
  echo "Note: $SKIPPED_USER_COUNT user-scope skill(s) skipped (not auto-installed):"
  for s in "${SKIPPED_USER_SKILLS[@]}"; do echo "  - $s"; done
  echo
  echo "These install to \$HOME/.claude/skills and \$HOME/.codex/skills (outside this repo)."
  echo "If you want them, re-run with --include-user:"
  echo "  tools/install --target $TARGET --include-user"
fi
