#!/usr/bin/env bash
# install — Primary entry point for agent-config installation.
#
# Orchestrates the two stages:
#   1. scripts/install.sh  — payload sync (copy rules, symlink skills/commands)
#   2. scripts/install.py  — bridge files (.agent-settings, VSCode/Augment JSONs)
#
# This is the only installer a consumer needs to invoke. The underlying
# scripts remain callable directly for advanced use; see their --help.
#
# Usage:
#   bash scripts/install [OPTIONS]
#
# Options:
#   --source <dir>    Package source directory (default: auto-detect)
#   --target <dir>    Target project root (default: cwd)
#   --profile <name>  Cost profile for bridges (minimal|balanced|full)
#   --user-type <id>  Primary user-type for skill filtering (step-9 axis).
#                     Valid ids: consultant | creator | developer | finance
#                     | founder | gtm | ops. Default: empty (no filter).
#                     Written to personal.user_type in .agent-settings.yml.
#   --tools <list>    Comma-separated tool IDs to install (default: all).
#                     Valid: claude-code,claude-desktop,cursor,windsurf,
#                            cline,gemini-cli,copilot,augment,aider,codex,
#                            roocode,continue,kilocode,zed,jetbrains,kiro,all
#   --ai <list>       Alias for --tools (same IDs). When both are passed
#                     the comma-separated values are unioned.
#   --list-tools      Print supported tool IDs with descriptions, then exit
#   --yes, -y         Non-interactive mode: do not prompt (default for non-TTY)
#   --force           Overwrite existing bridge files
#   --dry-run         Show what payload sync would do (does not run bridges)
#   --verbose         Detailed payload sync output
#   --quiet           Suppress non-error output
#   --skip-sync       Skip payload sync (install.sh)
#   --skip-bridges    Skip bridge files (install.py)
#   --global          Install to user-scope paths (~/.claude/, ~/.cursor/, …)
#                     instead of project-locally. Implies --skip-sync (the
#                     payload sync stage is project-only). See ADR-007.
#   --scope <mode>    Override scope detection: auto | project | global | prompt.
#                     auto   = honor multi-signal detection (Phase 1.3)
#                     project = force project-local install
#                     global = force user-scope install (same as --global)
#                     prompt = always show the 3-option chooser
#   --custom-path <d> Use <d> as the project root when prompted; rejected with
#                     --scope=global / --global.
#   --offline         Skip every network call (post-install update banner +
#                     downstream registry fetchers). Sets AGENT_CONFIG_OFFLINE=1
#                     for child subprocesses. All bridge content is bundled
#                     in the package, so install itself is already offline-safe;
#                     this flag is the explicit air-gap / CI guarantee.
#   --minimal         Bootstrap only `.agent-settings.yml`, `agents/.gitkeep`,
#                     and the `./agent-config` wrapper. No tool payload, no
#                     AGENTS.md, no symlinks. Refuses to install inside an
#                     existing agent-config project (nested-install guard).
#                     See docs/installation.md → "Minimal init".
#   --settings-only   Alias for --minimal.
#   --help, -h        Show this help
#
# Examples:
#   bash scripts/install                                # everything (default)
#   bash scripts/install --tools=claude-code,cursor     # only those two
#   bash scripts/install --ai=cursor --yes              # alias form (CI-friendly)
#   bash scripts/install --list-tools                   # show catalog

set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
INSTALL_SH="$SCRIPT_DIR/install.sh"
INSTALL_PY="$SCRIPT_DIR/install.py"

SOURCE_DIR=""
TARGET_DIR=""
PROFILE=""
TOOLS=""
TOOLS_EXPLICIT=false
YES=false
FORCE=false
DRY_RUN=false
VERBOSE=false
QUIET=false
SKIP_SYNC=false
SKIP_BRIDGES=false
LIST_TOOLS=false
GLOBAL=false
SCOPE=""
CUSTOM_PATH=""
OFFLINE=false
MINIMAL=false
USER_TYPE=""

# Single source of truth for valid tool IDs (also referenced by install.sh / install.py).
VALID_TOOLS="claude-code claude-desktop cursor windsurf cline gemini-cli copilot augment aider codex roocode continue kilocode zed jetbrains kiro all"

show_help() {
    sed -n '3,58p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
}

list_tools() {
    cat <<'EOF'
Supported --tools IDs (default: all):

  claude-code      .claude/rules, .claude/skills, .claude/commands, .claude/settings.json
  claude-desktop   .claude-desktop/agent-config.md marker (global-scope tool — see ADR-007)
  cursor           .cursor/rules, .cursor/commands (legacy .cursorrules also written)
  windsurf         .windsurf/rules, .windsurf/workflows (legacy .windsurfrules also written)
  cline            .clinerules/ symlinks
  gemini-cli       GEMINI.md, .gemini/settings.json
  copilot          .github/copilot-instructions.md, .vscode/settings.json
  augment          .augment/ payload + settings (substrate — recommended for every install)
  aider            .aider/agent-config.md marker (wire via `read:` in .aider.conf.yml)
  codex            .codex/agent-config.md marker (Codex reads AGENTS.md directly)
  roocode          .roo/rules/agent-config.md marker (Roo Code auto-discovery)
  continue         .continue/rules/agent-config.md marker (Continue.dev auto-discovery)
  kilocode         .kilocode/rules/agent-config.md marker (Kilo Code auto-discovery)
  zed              .zed/agent-config.md marker (Zed reads .rules at project root)
  jetbrains        .jetbrains/agent-config.md marker (JetBrains AI Assistant)
  kiro             .kiro/steering/agent-config.md marker (Kiro auto-discovery)
  all              every ID above (the default; backward-compatible)

Examples:
  --tools=claude-code,cursor       project-local install for those two surfaces
  --ai=cursor                      alias for --tools=cursor
  --tools=all                      equivalent to omitting the flag
EOF
}

err() { echo "  ❌  $*" >&2; }

# Validate a comma-separated tool list against $VALID_TOOLS. Empty input is
# rejected so a stray --tools= does not silently behave like --tools=all.
validate_tools() {
    local raw="$1"
    [[ -z "$raw" ]] && { err "--tools requires a non-empty value (use --list-tools to see options)"; return 1; }
    local IFS=','
    local item
    for item in $raw; do
        [[ -z "$item" ]] && { err "--tools contains an empty entry"; return 1; }
        if [[ " $VALID_TOOLS " != *" $item "* ]]; then
            err "Unknown tool ID: $item (run --list-tools for the catalog)"
            return 1
        fi
    done
    return 0
}

while [[ $# -gt 0 ]]; do
    case "$1" in
        --source)        SOURCE_DIR="$2"; shift 2 ;;
        --source=*)      SOURCE_DIR="${1#*=}"; shift ;;
        --target)        TARGET_DIR="$2"; shift 2 ;;
        --target=*)      TARGET_DIR="${1#*=}"; shift ;;
        --profile)       PROFILE="$2"; shift 2 ;;
        --profile=*)     PROFILE="${1#*=}"; shift ;;
        --user-type)     USER_TYPE="$2"; shift 2 ;;
        --user-type=*)   USER_TYPE="${1#*=}"; shift ;;
        --tools)         TOOLS="${TOOLS:+$TOOLS,}$2"; TOOLS_EXPLICIT=true; shift 2 ;;
        --tools=*)       TOOLS="${TOOLS:+$TOOLS,}${1#*=}"; TOOLS_EXPLICIT=true; shift ;;
        --ai)            TOOLS="${TOOLS:+$TOOLS,}$2"; TOOLS_EXPLICIT=true; shift 2 ;;
        --ai=*)          TOOLS="${TOOLS:+$TOOLS,}${1#*=}"; TOOLS_EXPLICIT=true; shift ;;
        --list-tools)    LIST_TOOLS=true; shift ;;
        --yes|-y)        YES=true; shift ;;
        --force)         FORCE=true; shift ;;
        --dry-run)       DRY_RUN=true; shift ;;
        --verbose)       VERBOSE=true; shift ;;
        --quiet)         QUIET=true; shift ;;
        --skip-sync)     SKIP_SYNC=true; shift ;;
        --skip-bridges)  SKIP_BRIDGES=true; shift ;;
        --global)        GLOBAL=true; SKIP_SYNC=true; shift ;;
        --scope)         SCOPE="$2"; shift 2 ;;
        --scope=*)       SCOPE="${1#*=}"; shift ;;
        --custom-path)   CUSTOM_PATH="$2"; shift 2 ;;
        --custom-path=*) CUSTOM_PATH="${1#*=}"; shift ;;
        --offline)       OFFLINE=true; shift ;;
        --minimal|--settings-only)
                         MINIMAL=true; shift ;;
        --help|-h)       show_help; exit 0 ;;
        *)               err "Unknown argument: $1"; show_help >&2; exit 1 ;;
    esac
done

if $LIST_TOOLS; then
    list_tools
    exit 0
fi

# road-to-global-only-install § Phase 3.3 — consumer-floor gate.
#
# Consumer installs are global-only (ADR-020). Explicit --scope=project
# (and its --scope project / --scope=project / SCOPE shorthand) is the
# maintainer-only escape hatch behind AGENT_CONFIG_DEV_MODE=1. Without
# the env flag we refuse here so the bash orchestrator fails fast with
# a directive error pointing at the maintainer doc — install.py's
# Python-side _enforce_consumer_global_only still backstops the same
# check for direct python3 scripts/install.py invocations.
if [[ "${SCOPE:-}" == "project" && "${AGENT_CONFIG_DEV_MODE:-}" != "1" ]]; then
    err "--scope=project is reserved for maintainers (ADR-020 — consumer installs are global-only). Set AGENT_CONFIG_DEV_MODE=1 to opt in. See docs/maintainers/dev-mode.md."
    exit 1
fi

# Interactive --tools picker (S9). Fires only when:
#   - --tools was not explicitly passed
#   - --yes / -y was not passed (CI / non-interactive opt-out)
#   - stdin AND stdout are both TTYs (so we're not in a pipe / curl|bash flow)
#   - --dry-run, --quiet, --skip-sync, --skip-bridges did not opt out of UX
# Otherwise we fall through to the backward-compatible "all" default.
prompt_tools() {
    local choice picked tool i=0
    local -a menu=(claude-code claude-desktop cursor windsurf cline gemini-cli copilot augment aider codex roocode continue kilocode zed jetbrains kiro)
    echo ""
    echo "  📦 Pick the tools to install (comma-separated numbers, blank = all):"
    for tool in "${menu[@]}"; do
        i=$((i+1))
        printf "    %2d) %s\n" "$i" "$tool"
    done
    echo "    a)  all (default)"
    echo ""
    printf "  Selection: "
    IFS= read -r choice || choice=""
    choice="${choice// /}"
    if [[ -z "$choice" || "$choice" == "a" || "$choice" == "all" ]]; then
        TOOLS="all"; return
    fi
    picked=""
    local IFS=','
    for n in $choice; do
        if ! [[ "$n" =~ ^[0-9]+$ ]] || (( n < 1 || n > ${#menu[@]} )); then
            err "Invalid selection: $n (expected 1-${#menu[@]} or 'a')"; exit 1
        fi
        picked+="${menu[$((n-1))]},"
    done
    TOOLS="${picked%,}"
    echo "  ✅  Selected: $TOOLS"
}

# When an interactive global install will hand off to the browser wizard,
# the wizard is the single tool-selection surface — skip the terminal
# picker so it does not pre-empt (and, via TOOLS_EXPLICIT, suppress) the
# GUI. Mirrors install.py::_wizard_should_launch minus the --no-ui flag,
# which the bash orchestrator never receives (it errors on unknown args).
# Headless paths (no TTY / CI / AGENT_CONFIG_NO_UI) still get the picker.
wizard_will_handle_tools=false
if $GLOBAL && [[ -t 0 && -t 1 && -z "${CI:-}" ]] \
   && { [[ -z "${AGENT_CONFIG_NO_UI:-}" ]] || [[ "${AGENT_CONFIG_NO_UI:-}" == "0" ]]; }; then
    wizard_will_handle_tools=true
fi

if ! $MINIMAL && ! $TOOLS_EXPLICIT && ! $YES && ! $QUIET && ! $LIST_TOOLS \
   && [[ -t 0 && -t 1 ]] && ! $wizard_will_handle_tools; then
    prompt_tools
    TOOLS_EXPLICIT=true
fi

# Default = "all": backward compatible with pre-Phase-1 invocations. An
# explicit --tools= (empty value) is rejected by validate_tools — only an
# absent flag falls through to "all".
if ! $TOOLS_EXPLICIT && [[ -z "$TOOLS" ]]; then
    TOOLS="all"
fi

if ! validate_tools "$TOOLS"; then
    exit 1
fi

# Auto-detect source: this orchestrator lives at src/scripts/install, so the
# package root (where dist/agent-src/ lives) is two levels up (../.. ).
if [[ -z "$SOURCE_DIR" ]]; then
    SOURCE_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
fi

# Auto-detect target: PROJECT_ROOT env, or derive from node_modules path, else cwd
if [[ -z "$TARGET_DIR" ]]; then
    if [[ -n "${PROJECT_ROOT:-}" ]]; then
        TARGET_DIR="$PROJECT_ROOT"
    elif [[ "$SOURCE_DIR" == */node_modules/@event4u/agent-config ]]; then
        TARGET_DIR="$(cd "$SOURCE_DIR/../../.." && pwd)"
    elif [[ "$SOURCE_DIR" == */node_modules/*/agent-config ]]; then
        TARGET_DIR="$(cd "$SOURCE_DIR/../../.." && pwd)"
    else
        TARGET_DIR="$(pwd)"
    fi
fi

# Source-repo guard: refuse to install into the agent-config dev tree itself.
# Defense-in-depth so a direct `bash scripts/install` from inside the source
# checkout cannot corrupt .augment/ symlinks. Skipped for --global because
# global installs only write to user-scope paths (~/.config/agent-config/,
# ~/.claude/, …) and never touch the source tree. Override for self-tests:
# AGENT_CONFIG_ALLOW_SELF_INSTALL=1.
if ! $GLOBAL && [[ "${AGENT_CONFIG_ALLOW_SELF_INSTALL:-0}" != "1" ]]; then
    self_marker=""
    if [[ -d "$TARGET_DIR/.agent-src.uncondensed" ]]; then
        self_marker=".agent-src.uncondensed/"
    elif [[ -f "$TARGET_DIR/package.json" ]] && \
         grep -qE '"name"[[:space:]]*:[[:space:]]*"@event4u/agent-config"' "$TARGET_DIR/package.json" 2>/dev/null; then
        self_marker='package.json::name === "@event4u/agent-config"'
    fi
    if [[ -n "$self_marker" ]]; then
        err "Refusing to install agent-config into its own source checkout."
        echo "      Target:   $TARGET_DIR" >&2
        echo "      Detected: $self_marker" >&2
        echo "      Run \`task sync\` to regenerate dist/agent-src/ + .augment/ from" >&2
        echo "      .agent-src.uncondensed/ instead. To force this anyway, set" >&2
        echo "      AGENT_CONFIG_ALLOW_SELF_INSTALL=1." >&2
        exit 2
    fi
fi

# Find python3 for the bridges stage (optional until SKIP_BRIDGES=false)
find_python() {
    local candidate path
    for candidate in python3 python; do
        path="$(command -v "$candidate" 2>/dev/null || true)"
        [[ -z "$path" ]] && continue
        if "$path" -c 'import sys; exit(0 if sys.version_info[0] >= 3 else 1)' 2>/dev/null; then
            echo "$path"
            return 0
        fi
    done
    return 1
}

run_sync() {
    local args=(--source "$SOURCE_DIR" --target "$TARGET_DIR")
    $DRY_RUN && args+=(--dry-run)
    $VERBOSE && args+=(--verbose)
    $QUIET   && args+=(--quiet)
    $MINIMAL && args+=(--minimal)
    args+=(--tools="$TOOLS")
    # Suppress the install.sh deprecation banner when called through the
    # orchestrator (this script). Direct `bash install.sh` invocations
    # still see it. See ADR-016 § Distribution / Phase 6.
    AGENT_CONFIG_FROM_ORCHESTRATOR=1 bash "$INSTALL_SH" "${args[@]}"
}

run_bridges() {
    local python_bin
    if ! python_bin="$(find_python)"; then
        $QUIET || echo "  ⚠️  Python 3 not found — skipping .agent-settings and bridge files." >&2
        $QUIET || echo "      Install python3 and re-run: bash scripts/install --skip-sync" >&2
        return 0
    fi

    if $DRY_RUN; then
        $QUIET || echo "  ⏭️  Skipping bridges (--dry-run)"
        return 0
    fi

    local args=(--project "$TARGET_DIR" --package "$SOURCE_DIR")
    [[ -n "$PROFILE" ]] && args+=(--profile="$PROFILE")
    [[ -n "$USER_TYPE" ]] && args+=(--user-type="$USER_TYPE")
    $FORCE && args+=(--force)
    $QUIET && args+=(--quiet)
    $GLOBAL && args+=(--global)
    [[ -n "$SCOPE" ]] && args+=(--scope="$SCOPE")
    [[ -n "$CUSTOM_PATH" ]] && args+=(--custom-path="$CUSTOM_PATH")
    $OFFLINE && args+=(--offline)
    $MINIMAL && args+=(--minimal)
    args+=(--tools="$TOOLS")
    "$python_bin" "$INSTALL_PY" "${args[@]}"
}

RC=0

# Minimal init runs the bridge stage *first* so its nested-install guard
# (Step 7 Phase 2) fires before any wrapper / file is written. The
# payload sync stage is then a no-op in minimal mode (install.sh
# short-circuits) but is still invoked so it can install the
# `./agent-config` wrapper on a confirmed-clean target.
if $MINIMAL; then
    if ! $SKIP_BRIDGES; then
        if [[ ! -f "$INSTALL_PY" ]]; then
            err "Missing $INSTALL_PY"
            exit 1
        fi
        run_bridges || RC=$?
    fi
    if [[ $RC -ne 0 ]]; then
        exit $RC
    fi
    if ! $SKIP_SYNC; then
        if [[ ! -f "$INSTALL_SH" ]]; then
            err "Missing $INSTALL_SH"
            exit 1
        fi
        run_sync || RC=$?
    fi
    exit $RC
fi

if ! $SKIP_SYNC; then
    if [[ ! -f "$INSTALL_SH" ]]; then
        err "Missing $INSTALL_SH"
        exit 1
    fi
    run_sync || RC=$?
fi

if [[ $RC -ne 0 ]]; then
    err "Payload sync failed (exit $RC); skipping bridges."
    exit $RC
fi

if ! $SKIP_BRIDGES; then
    if [[ ! -f "$INSTALL_PY" ]]; then
        err "Missing $INSTALL_PY"
        exit 1
    fi
    run_bridges || RC=$?
fi

# Post-install PATH advisory. The Claude plugin hook resolves `agent-config`
# from PATH on a global-only consumer (ADR-020); if it is unreachable every
# PostToolUse hook silently no-ops — the documented "dashboard never updates"
# failure. Advisory only: a bad PATH must not fail an otherwise-clean install.
if [[ $RC -eq 0 ]] && ! command -v agent-config >/dev/null 2>&1; then
    $QUIET || {
        echo "  ⚠️   \`agent-config\` is not on your PATH." >&2
        echo "      Claude plugin hooks resolve it from PATH; without it the" >&2
        echo "      roadmap-progress dashboard (and other hooks) will not fire." >&2
        echo "      Add your npm global bin dir (\`npm prefix -g\`/bin) to PATH," >&2
        echo "      then verify with \`agent-config doctor\`." >&2
    }
fi

exit $RC
