#!/usr/bin/env bash
#
# setup_aionui_opencode_discretestack.sh
#
# One-shot interactive setup for:
#   - AionUI   (https://github.com/iofficeai/aionui)
#   - OpenCode (https://github.com/anomalyco/opencode)
# configured to use the DiscreteStack OpenAI-compatible API endpoint.
#
# The script is interactive: it prompts only for the API key. The DiscreteStack
# base URL and model names are hard-coded. It installs/updates both apps,
# writes OpenCode's opencode.json, makes sure opencode is on PATH, and finally
# launches AionUI.
#
# Can be run remotely via curl|bash:
#   curl -fsSL https://your-domain.com/setup_aionui_opencode_discretestack.sh | bash
#
# Tested primarily on macOS. Linux (brew / .deb / AppImage) is supported
# on a best-effort basis. For Windows, use the companion PowerShell script:
#   setup_aionui_opencode_discretestack.ps1

set -euo pipefail

# ---------------------------------------------------------------------------
# Utilities
# ---------------------------------------------------------------------------

# --- Output helpers ---------------------------------------------------------
# Colorful, low-noise output for non-technical users. All technical detail
# (timestamps, URLs, checksums) is hidden unless the user opts in with
# AION_VERBOSE=1. Colors are disabled when not attached to a terminal.

if [[ -t 1 ]]; then
    C_RESET=$'\033[0m'; C_BOLD=$'\033[1m'; C_DIM=$'\033[2m'
    C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m'; C_RED=$'\033[31m'
    C_ORANGE=$'\033[38;5;214m'   # 256-color orange (matches OpenCode installer)
else
    C_RESET=""; C_BOLD=""; C_DIM=""; C_GREEN=""; C_YELLOW=""; C_RED=""; C_ORANGE=""
fi

IS_VERBOSE=false
[[ "${AION_VERBOSE:-0}" == "1" ]] && IS_VERBOSE=true

# Step banner: "== Installing AionUI ... =="
step() {
    echo ""
    echo "${C_BOLD}${C_ORANGE}━━ $* ━━${C_RESET}"
}
ok()   { echo "${C_GREEN}  ✓${C_RESET} $*"; }
info() { echo "${C_DIM}    $*${C_RESET}"; }
warn() { echo "${C_YELLOW}  !${C_RESET} $*" >&2; }

# Spinner: run a command with an animated indicator so long steps don't look
# stuck. Usage: spin "message" command args...
# Falls back to a simple info line when not attached to a terminal (or in
# verbose mode, where interleaved debug output would corrupt the animation).
spin() {
    local msg="$1"; shift
    if ! [[ -t 1 ]] || $IS_VERBOSE; then
        info "$msg ..." >&2
        "$@"; return $?
    fi
    local frames="|/-\\"
    local i=0 rc
    "$@" &
    local pid=$!
    # Animation goes to stderr so the command's stdout passes through cleanly
    # when spin is used in a command substitution.
    printf "  ${C_ORANGE}%s ... %s${C_RESET}" "$msg" "${frames:0:1}" >&2
    while kill -0 "$pid" 2>/dev/null; do
        i=$(( (i + 1) % 4 ))
        printf "\r  ${C_ORANGE}%s ... %s${C_RESET}" "$msg" "${frames:$i:1}" >&2
        sleep 0.2
    done
    wait "$pid"; rc=$?
    printf "\r\033[K" >&2   # clear the spinner line
    return $rc
}

# Technical detail shown only in verbose mode.
vlog() { $IS_VERBOSE && echo "${C_DIM}    [debug] $*${C_RESET}" >&2 || true; }
log()  { vlog "$*"; }   # timestamps demoted to verbose-only

# Read a value from the user, with an optional default.
# Input is read from /dev/tty so this works when the script is piped in
# (e.g. curl ... | bash).
ask() {
    local prompt="$1"
    local default="${2:-}"
    local value=""
    if [[ -n "$default" ]]; then
        read -rp "$prompt [$default]: " value </dev/tty
        value="${value:-$default}"
    else
        read -rp "$prompt: " value </dev/tty
    fi
    echo "$value"
}

# Read a secret value (API key) without echoing to the terminal.
# Like ask(), reads from /dev/tty to survive curl|bash execution.
ask_secret() {
    local prompt="$1"
    local value=""
    while true; do
        read -rsp "$prompt (hidden): " value </dev/tty
        echo >&2
        if [[ -n "$value" ]]; then
            break
        fi
        warn "A value is required."
    done
    echo "$value"
}

# ---------------------------------------------------------------------------
# Platform detection
# ---------------------------------------------------------------------------

OS="$(uname -s)"
ARCH="$(uname -m)"

case "$OS" in
    Darwin) PLATFORM="macos" ;;
    Linux)  PLATFORM="linux" ;;
    *)      fail "Unsupported OS: $OS. This script supports macOS and Linux." ;;
esac


# ---------------------------------------------------------------------------
# 1. Install AionUI
# ---------------------------------------------------------------------------

install_aionui() {
    # Already installed? (macOS: app bundle; Linux: binary on PATH or common
    # install locations — brew/deb may have put it there.)
    if [[ "$PLATFORM" == "macos" && -d "/Applications/AionUi.app" ]]; then
        ok "AionUI is already installed."
        return 0
    fi
    if [[ "$PLATFORM" == "linux" ]] && { command -v aionui >/dev/null 2>&1 || [[ -x "/usr/bin/aionui" ]] || [[ -x "/usr/local/bin/aionui" ]]; }; then
        ok "AionUI is already installed."
        return 0
    fi

    # Resolve the latest version + expected sha512 from AionUI's official
    # electron-updater manifests.
    local manifest_url
    if [[ "$PLATFORM" == "macos" ]]; then
        manifest_url="https://static.aionui.com/releases/latest-mac.yml"
    else
        manifest_url="https://static.aionui.com/releases/latest-linux.yml"
    fi

    local manifest version
    manifest="$(curl -fsSL "$manifest_url" 2>/dev/null)" || true
    version="$(echo "$manifest" | sed -n 's/^version:[[:space:]]*//p' | head -1)"

    if [[ -z "$version" ]]; then
        warn "Could not check the latest AionUI version."
        install_aionui_fallback
        return
    fi
    # Map uname arch to AionUI asset naming.
    local arch_asset
    case "$ARCH" in
        arm64|aarch64) arch_asset="arm64" ;;
        x86_64)        arch_asset="amd64" ;;
        *)             warn "Unsupported architecture: $ARCH"; install_aionui_fallback; return ;;
    esac

    local filename url expected_sha512
    if [[ "$PLATFORM" == "macos" ]]; then
        filename="AionUi-${version}-mac-${arch_asset}.dmg"
    else
        filename="AionUi-${version}-linux-${arch_asset}.deb"
    fi
    url="https://static.aionui.com/releases/${version}/${filename}"

    expected_sha512="$(echo "$manifest" \
        | awk -v f="$filename" '$0 ~ "- url: "f { getline; if ($1 == "sha512:") print $2 }')"
    [[ -z "$expected_sha512" ]] && vlog "No checksum published for $filename; installing without verification."

    local tmp_file
    tmp_file="$(mktemp -t aionui_download.XXXXXX)"
    if ! spin "Downloading AionUI ${version}" curl -fsSL -o "$tmp_file" "$url"; then
        rm -f "$tmp_file"
        warn "Download failed."
        install_aionui_fallback
        return
    fi
    ok "Downloaded AionUI ${version}."

    if [[ -n "$expected_sha512" ]] && command -v openssl >/dev/null 2>&1; then
        # Manifests store the digest as base64, not hex.
        local actual_sha512
        actual_sha512="$(spin "Verifying checksum" bash -c 'openssl dgst -sha512 -binary "$0" | openssl base64 -A' "$tmp_file")"
        if [[ "$actual_sha512" != "$expected_sha512" ]]; then
            rm -f "$tmp_file"
            fail "Checksum verification failed for $filename."
        fi
        vlog "Checksum verified."
    elif [[ -n "$expected_sha512" ]]; then
        vlog "openssl not available; skipping checksum verification."
    fi
    if [[ "$PLATFORM" == "macos" ]]; then
        vlog "Mounting $filename ..."
        local mount_dir
        mount_dir="$(mktemp -d)"
        if ! hdiutil attach -quiet -nobrowse -readonly -mountpoint "$mount_dir" "$tmp_file"; then
            rm -rf "$mount_dir" "$tmp_file"
            fail "Could not mount $filename."
        fi
        if [[ -d "$mount_dir/AionUi.app" ]]; then
            if ! rm -rf "/Applications/AionUi.app" || ! spin "Installing AionUI" ditto "$mount_dir/AionUi.app" "/Applications/AionUi.app"; then
                # Almost always a permissions wall; brew would hit it too.
                # Treat like the other install failures: non-fatal.
                hdiutil detach -quiet "$mount_dir" || true
                rm -f "$tmp_file"
                warn "Could not install AionUI (Applications folder is protected)."
                install_aionui_fallback
                return
            fi
            ok "AionUI installed."
        else
            warn "Installation package looks broken."
            hdiutil detach -quiet "$mount_dir" || true
            rm -f "$tmp_file"
            install_aionui_fallback
            return
        fi
        hdiutil detach -quiet "$mount_dir" || true
    else
        info "Installing AionUI (may ask for your password) ..."
        local deb_ok=false
        if command -v sudo >/dev/null 2>&1; then
            if sudo dpkg -i "$tmp_file"; then deb_ok=true
            else sudo apt-get install -f -y && deb_ok=true || true; fi
        else
            if dpkg -i "$tmp_file"; then deb_ok=true
            else apt-get install -f -y && deb_ok=true || true; fi
        fi
        rm -f "$tmp_file"
        if [[ "$deb_ok" == true ]]; then
            ok "AionUI installed."
        else
            warn "Installation failed."
            install_aionui_fallback
            return
        fi
    fi
}

# Last-resort path when the direct download does not work out.
install_aionui_fallback() {
    if command -v brew >/dev/null 2>&1; then
        info "Falling back to Homebrew ..."
        if brew list --cask aionui >/dev/null 2>&1; then
            brew upgrade --cask aionui 2>/dev/null || brew reinstall --cask aionui
        else
            brew install --cask aionui 2>/dev/null || brew install aionui
        fi
        return 0
    fi

    warn "Could not install AionUI automatically."
    warn "Please download the installer for your platform from https://www.aionui.com/"
    warn "then re-run this script."
    return 1
}

# ---------------------------------------------------------------------------
# 2. Install OpenCode
# ---------------------------------------------------------------------------


install_opencode() {
    # Already installed? (binary on PATH, or in the known install locations
    # from previous runs / the official installer.)
    local oc=""
    oc="$(command -v opencode 2>/dev/null || true)"
    if [[ -z "$oc" ]]; then
        for d in "$HOME/.opencode/bin" "/usr/local/bin" "/opt/homebrew/bin"; do
            [[ -x "$d/opencode" ]] && { oc="$d/opencode"; break; }
        done
    fi
    if [[ -n "$oc" ]]; then
        vlog "opencode found at: $oc ($( "$oc" --version 2>/dev/null || echo 'unknown version'))"
        ok "OpenCode is already installed."
        return 0
    fi

    # Use the official installer: it downloads a prebuilt binary (no Node.js
    # required) and is idempotent (re-running updates to the latest release).
    # Its own output (banner, version details) is hidden unless verbose.
    local installer_out
    if installer_out="$(spin "Installing OpenCode" bash -c 'curl -fsSL https://opencode.ai/install | bash -s --' 2>&1)"; then
        vlog "$installer_out"
        ok "OpenCode installed."
        return 0
    fi
    installer_out=""
    warn "Standard installer failed; trying alternative method..."
    if ! command -v npm >/dev/null 2>&1; then
        fail "Could not install OpenCode automatically. Please install Node.js (https://nodejs.org) and re-run."
    fi
    npm install -g opencode-ai@latest
    ok "OpenCode installed."
}

# ---------------------------------------------------------------------------
# 3b. Install OpenCode skills (productivity packs from trusted repos)
# ---------------------------------------------------------------------------

# Each entry: <skill-name>|<github-owner/repo>|<path-inside-repo>
# Path is "" when the skill lives at the repo root.
# OpenCode loads global skills from ~/.config/opencode/skills/<name>/SKILL.md.
OPCODE_SKILLS=(
    "xlsx|anthropics/skills|skills/xlsx"
    "docx|anthropics/skills|skills/docx"
    "pptx|anthropics/skills|skills/pptx"
    "pdf|anthropics/skills|skills/pdf"
    "doc-coauthoring|anthropics/skills|skills/doc-coauthoring"
    "brand-guidelines|anthropics/skills|skills/brand-guidelines"
    "theme-factory|anthropics/skills|skills/theme-factory"
    "humanizer|blader/humanizer|"
    "file-organizer|claude-office-skills/skills|file-organizer"
    "financial-analysis|d-wwei/financial-analysis|"
    "image-generation|agent-skills-hub/agent-skills-hub|skills/generate-image"
    "proposal-writer|SkillMedev/skills|skills/sales-proposal-writer"
    "executive-summary|latour-ai/skills|executive-briefing"
)

# Download all skills once into a staging dir. Returns 0 and prints the
# staging path; the caller removes it when done.
stage_skills_download() {
    local staging="$1"
    mkdir -p "$staging"

    # Group skills by repo so each repo is downloaded only once.
    # (Plain bash 3.2-compatible dedupe — no associative arrays.)
    local repos=()
    local entry name repo subpath
    for entry in "${OPCODE_SKILLS[@]}"; do
        IFS='|' read -r name repo subpath <<< "$entry"
        local seen=false
        local existing
        for existing in "${repos[@]:-}"; do
            [[ "$existing" == "$repo" ]] && seen=true && break
        done
        $seen || repos+=("$repo")
    done

    for repo in "${repos[@]}"; do
        tmp_dir="$(mktemp -d)"
        if ! spin "Downloading skills from ${repo}" curl -fsSL "https://api.github.com/repos/${repo}/tarball/HEAD" -o "$tmp_dir/repo.tar.gz"; then
            warn "Could not download skills from $repo (skipping)."
            rm -rf "$tmp_dir"
            continue
        fi
        if ! tar -xzf "$tmp_dir/repo.tar.gz" -C "$tmp_dir"; then
            warn "Could not extract skills from $repo (skipping)."
            rm -rf "$tmp_dir"
            continue
        fi

        # The tarball extracts to <tmp>/<owner>-<repo>-<sha>/.
        local repo_root
        repo_root="$(find "$tmp_dir" -mindepth 1 -maxdepth 1 -type d | head -1)"

        for entry in "${OPCODE_SKILLS[@]}"; do
            IFS='|' read -r name entry_repo subpath <<< "$entry"
            [[ "$entry_repo" != "$repo" ]] && continue
            local src
            if [[ -n "$subpath" ]]; then
                src="$repo_root/${subpath}"
            else
                src="$repo_root"   # skill lives at the repo root
            fi
            if [[ -f "$src/SKILL.md" ]]; then
                rm -rf "$staging/$name"
                cp -R "$src" "$staging/$name"
            else
                warn "Skill '$name' not found in $repo (skipping)."
            fi
        done

        rm -rf "$tmp_dir"
    done

    # A skill counts as staged when it has a SKILL.md.
    local n=0
    for d in "$staging"/*/; do
        [[ -f "$d/SKILL.md" ]] && n=$((n + 1))
    done
    [[ "$n" -gt 0 ]]
}

# Copy staged skills into ONE skills root; skips identical copies.
# Echoes 1 if anything was copied, 0 otherwise.
install_skills_into_root() {
    local staging="$1" root="$2"
    mkdir -p "$root"
    local copied=false
    local d name src_hash dest_hash
    for d in "$staging"/*/; do
        name="$(basename "$d")"
        [[ -f "$d/SKILL.md" ]] || continue
        if [[ -d "$root/$name" ]]; then
            src_hash="$(cd "$d" && find . -type f -exec shasum -a 256 {} + | sort | shasum -a 256 | awk '{print $1}')"
            dest_hash="$(cd "$root/$name" && find . -type f -exec shasum -a 256 {} + | sort | shasum -a 256 | awk '{print $1}')"
            [[ "$src_hash" == "$dest_hash" ]] && continue
        fi
        rm -rf "$root/$name"
        cp -R "$d" "$root/$name"
        copied=true
        vlog "Installed skill: $name"
    done
    $copied && echo 1 || echo 0
}

# OpenCode side: native skills dir.
install_opencode_skills() {
    local staging
    staging="$(mktemp -d)"
    if ! stage_skills_download "$staging"; then
        warn "No skills could be downloaded."
        rm -rf "$staging"
        return 0
    fi
    local copied
    copied="$(install_skills_into_root "$staging" "$HOME/.config/opencode/skills")"
    if [[ "$copied" == 1 ]]; then
        ok "OpenCode skills installed."
    else
        ok "OpenCode skills are already up to date."
    fi
    STAGED_SKILLS_DIR="$staging"   # reused later for the AionUI side
}
# ---------------------------------------------------------------------------
# 3c. AionUI skills (phase 2: runs after the user logged into AionUI)
# ---------------------------------------------------------------------------

# Find the AionUI backend the app is actually using.
# Prints "data_dir<TAB>port" or returns 1. Prefers the app-spawned backend
# (has --parent-pid) over a stale manual one.
detect_aionui_backend() {
    local pids pid args data_dir port
    local best_data="" best_port=""
    pids="$(pgrep -x aioncore 2>/dev/null || true)"
    [[ -z "$pids" ]] && return 1
    for pid in $pids; do
        args="$(ps -p "$pid" -o command= 2>/dev/null)" || continue
        case "$args" in
            *--data-dir*)
                # Path may contain spaces: take everything after --data-dir
                # up to the next " --" flag (or end of string).
                data_dir="$(echo "$args" | sed 's/^.*--data-dir[ =]//; s/ --.*//')"
                ;;
            *) data_dir="" ;;
        esac
        [[ -z "$data_dir" ]] && continue
        # Prefer the app-managed backend (spawned by AionUi, has --parent-pid).
        case "$args" in
            *--parent-pid*)
                best_data="$data_dir"
                port="$(echo "$args" | sed -n 's/.*--port[ =]\([0-9]*\).*/\1/p')"
                best_port="$port"
                break
                ;;
            *)
                [[ -z "$best_data" ]] && { best_data="$data_dir"; best_port="$(echo "$args" | sed -n 's/.*--port[ =]\([0-9]*\).*/\1/p')"; }
                ;;
        esac
    done

    [[ -n "$best_data" ]] || return 1
    printf '%s\t%s\n' "$best_data" "${best_port:-0}"
}

# Register one staged skill with an AionUI backend: copy to the right
# per-user dir and add the catalog DB rows the app's import would create.
register_aionui_skill() {
    local data_dir="$1" user_id="$2" user_dir_name="$3" src="$4" name="$5" desc="$6"
    local dest_dir="$data_dir/skills/users/$user_dir_name"
    mkdir -p "$dest_dir"
    local dest="$dest_dir/$name"
    rm -rf "$dest"
    cp -R "$src" "$dest"

    local db="$data_dir/aionui-backend.db"
    [[ -f "$db" ]] || return 1

    local id now
    # Unique id: epoch seconds + three bash $RANDOM values (15 bits each).
    id="skill_$(date +%s)${RANDOM}${RANDOM}${RANDOM}"
    local dbq=(sqlite3 -cmd ".timeout 10000" "$db")
    now="$(date +%s)000"
    "${dbq[@]}" \
        "INSERT OR REPLACE INTO skills (id, user_id, name, description, path, source, enabled, created_at, updated_at)
         VALUES ('$id', '$(echo "$user_id" | sed "s/'/''/g")', '$(echo "$name" | sed "s/'/''/g")', '$(echo "$desc" | sed "s/'/''/g")', '$dest', 'user', 1, $now, $now);
         INSERT INTO skill_import_records (id, operation_id, source_label, source_path, source_name, skill_id, skill_name, status, created_at)
         VALUES ('skill_import_${id}_imp', 'op_${id}', '$(basename "$src")', '$src', '$(echo "$name" | sed "s/'/''/g")', '$id', '$(echo "$name" | sed "s/'/''/g")', 'imported', $now);" 2>/dev/null
}

# Extract name + description from a SKILL.md frontmatter (YAML, minimal).
# Prints "name<TAB>description".
parse_skill_frontmatter() {
    python3 - "$1" <<'PYEOF'
import sys, re
try:
    import yaml
    def parse(p):
        with open(p, encoding="utf-8") as f: c = f.read()
        m = re.match(r"^---\s*\n(.*?)\n---", c, re.S)
        if not m: return "", ""
        try: fm = yaml.safe_load(m.group(1)) or {}
        except Exception: return "", ""
        return str(fm.get("name") or "").strip(), str(fm.get("description") or "").strip()
except ImportError:
    def parse(p):
        with open(p, encoding="utf-8") as f: c = f.read()
        m = re.match(r"^---\s*\n(.*?)\n---", c, re.S)
        if not m: return "", ""
        fm = m.group(1)
        nm = re.search(r'^name:\s*["\']?([^"\']+)["\']?\s*$', fm, re.M)
        dm = re.search(r'^description:\s*["\']?(.+?)["\']?\s*$', fm, re.M | re.S)
        return (nm.group(1).strip() if nm else ""), (dm.group(1).strip() if dm else "")
name, desc = parse(sys.argv[1])
print(name + "\t" + desc)
PYEOF
}

# Phase 2: install skills into the running AionUI backend's catalog so they
# are selectable in the UI. Runs AFTER the user logged in (so user rows exist).
install_aionui_skills() {
    local staging="${STAGED_SKILLS_DIR:-}"
    if [[ -z "$staging" || ! -d "$staging" ]]; then
        staging="$(mktemp -d)"
        stage_skills_download "$staging" || { rm -rf "$staging"; return 1; }
    fi

    local backend_line data_dir port
    if ! backend_line="$(detect_aionui_backend)"; then
        warn "AionUI backend not detected; skills not installed for AionUI."
        warn "OpenCode still has all the skills."
        rm -rf "$staging"
        return 1
    fi
    data_dir="$(echo "$backend_line" | cut -f1)"
    vlog "Detected AionUI backend data dir: $data_dir"

    local db="$data_dir/aionui-backend.db"
    [[ -f "$db" ]] || { warn "AionUI database not found at $db."; rm -rf "$staging"; return 1; }

    # Pick the user the app is logged in as: prefer a real (non-system) user,
    # fall back to system_default_user.
    local user_id user_dir_name
    user_id="$(sqlite3 "$db" "SELECT id FROM users WHERE id != 'system_default_user' ORDER BY created_at DESC LIMIT 1;" 2>/dev/null)"
    if [[ -z "$user_id" ]]; then
        user_id="system_default_user"
        user_dir_name="system_default_user"
    else
        user_dir_name="${user_id#user_}"
    fi
    vlog "Installing AionUI skills for user: $user_id"

    # Preselect OpenCode as the assistant shown on AionUI's first launch.
    # '53861a53' is OpenCode's fixed builtin agent id (AionCore schema migration);
    # the quotes in the stored value are literal (serde_json string of a string).
    if sqlite3 -cmd ".timeout 10000" "$db" \
        "INSERT OR REPLACE INTO client_preferences (user_id, key, value, updated_at)
         VALUES ('$user_id', 'guid.lastAssistantId', '\"bare:53861a53\"', $(date +%s)000);" 2>/dev/null; then
        vlog "OpenCode set as default assistant for first launch."
    else
        vlog "Could not set default assistant preference."
    fi

    local d name desc installed=0 skipped=0 failed=0
    local fm_line
    for d in "$staging"/*/; do
        [[ -f "$d/SKILL.md" ]] || continue
        fm_line="$(parse_skill_frontmatter "$d/SKILL.md")"
        name="${fm_line%%$'\t'*}"
        desc="${fm_line#*$'\t'}"
        [[ -z "$name" ]] && name="$(basename "$d")"
        if [[ -z "$desc" ]]; then
            failed=$((failed + 1))
            continue
        fi
        # Already registered for this user?
        local escaped_name
        escaped_name="${name//\'/\'\'}"
        if sqlite3 -cmd ".timeout 10000" "$db" \
            "SELECT 1 FROM skills WHERE user_id='$user_id' AND name='$escaped_name';" 2>/dev/null | grep -q 1; then
            skipped=$((skipped + 1))
            continue
        fi
        if register_aionui_skill "$data_dir" "$user_id" "$user_dir_name" "${d%/}" "$name" "$desc"; then
            installed=$((installed + 1))
        else
            failed=$((failed + 1))
        fi
    done
    rm -rf "$staging"
    STAGED_SKILLS_DIR=""

    if [[ "$installed" -gt 0 ]]; then
        ok "$installed skills installed in AionUI."
    elif [[ "$skipped" -gt 0 ]]; then
        ok "AionUI skills are already up to date."
    fi
    [[ "$failed" -gt 0 ]] && warn "$failed skill(s) could not be added to AionUI."
    [[ "$skipped" -gt 0 ]] && vlog "$skipped already registered."
    return 0
}


# ---------------------------------------------------------------------------
# 4. Make sure opencode is on the user's PATH for AionUI's ACP auto-detection
# ---------------------------------------------------------------------------

# The official installer adds ~/.opencode/bin to the shell profile; the npm
# fallback puts `opencode` in npm's global bin. In both cases `opencode` is
# normally on PATH already — this only fixes the current shell session.
ensure_opencode_on_path() {
    if command -v opencode >/dev/null 2>&1; then
        vlog "opencode found at: $(command -v opencode)"
        return 0
    fi

    # Not on PATH in this session yet. Check the two known install locations.
    local opencode_bin=""
    local candidates=(
        "$HOME/.opencode/bin"
        "$(npm config get prefix 2>/dev/null)/bin"
        "/usr/local/bin"
        "/opt/homebrew/bin"
    )
    for dir in "${candidates[@]}"; do
        if [[ -x "$dir/opencode" ]]; then
            opencode_bin="$dir/opencode"
            break
        fi
    done

    if [[ -z "$opencode_bin" ]]; then
        fail "OpenCode was installed but cannot be found. Try opening a new terminal and re-run."
    fi

    ok "OpenCode installed."

    # Make it available for the rest of this session and the AionUI launch.
    local opencode_dir
    opencode_dir="$(dirname "$opencode_bin")"
    case ":${PATH}:" in
        *":$opencode_dir:"*) ;;
        *)
            export PATH="$opencode_dir:$PATH"
            vlog "$opencode_dir was not on PATH; added it for this session."
            ;;
    esac
}

# ---------------------------------------------------------------------------
# 4. DiscreteStack endpoint constants & interactive API key
# ---------------------------------------------------------------------------

DISCRETESTACK_BASE_URL="https://api.discretestack.com/v1"
DISCRETESTACK_MODELS=(
    "discretestack-stable-fast"
    "discretestack-stable"
    "discretestack-stable-max"
)


configure_discretestack() {
    DISCRETESTACK_API_KEY="$(ask_secret "DiscreteStack API key")"
    ok "DiscreteStack configured."
}

# ---------------------------------------------------------------------------
# 5. Write OpenCode config (~/.config/opencode/opencode.json)
# ---------------------------------------------------------------------------

write_opencode_config() {

    local config_dir="$HOME/.config/opencode"
    local config_file="$config_dir/opencode.json"

    mkdir -p "$config_dir"

    if [[ -f "$config_file" ]]; then
        local backup="$config_dir/opencode.json.backup.$(date +%Y%m%d%H%M%S)"
        cp "$config_file" "$backup"
        vlog "Existing config backed up to: $backup"
    fi

    # Build the per-model JSON object from the DISCRETESTACK_MODELS array.
    local models_json=""
    local first=true
    for model in "${DISCRETESTACK_MODELS[@]}"; do
        if $first; then
            first=false
        else
            models_json+=","
        fi
        models_json+=$'\n        "'"$model"'": {
          "name": "'"$model"'",
          "modalities": {
            "input": ["text", "image"],
            "output": ["text"]
          },
          "limit": {
            "context": 200000,
            "output": 8192
          }
        }'
    done

    cat > "$config_file" <<EOF
{
  "\$schema": "https://opencode.ai/config.json",
  "model": "discretestack/discretestack-stable",
  "provider": {
    "discretestack": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "DiscreteStack",
      "options": {
        "baseURL": "$DISCRETESTACK_BASE_URL",
        "apiKey": "$DISCRETESTACK_API_KEY"
      },
      "models": {$models_json
      }
    }
  }
}
EOF

    ok "OpenCode configured."
}

health_check_discretestack() {
    local models_url="${DISCRETESTACK_BASE_URL}/models"
    local http_code
    http_code="$(spin "Connecting to DiscreteStack" curl -s -o /dev/null -w "%{http_code}" \
        -H "Authorization: Bearer $DISCRETESTACK_API_KEY" \
        "$models_url" 2>/dev/null || true)"

    if [[ "$http_code" == "200" ]]; then
        ok "Connected to DiscreteStack."
    else
        warn "Could not reach DiscreteStack. You may need to check your internet connection."
        vlog "Endpoint $models_url returned HTTP $http_code."
    fi
}

# ---------------------------------------------------------------------------
# 7. Launch AionUI
# ---------------------------------------------------------------------------

launch_aionui() {
    if [[ "$PLATFORM" == "macos" ]]; then
        if [[ -d "/Applications/AionUi.app" ]]; then
            open -a AionUi
        elif command -v aionui >/dev/null 2>&1; then
            aionui &
        else
            warn "Could not open AionUI automatically. Please open it from Launchpad."
            return 1
        fi
    elif command -v aionui >/dev/null 2>&1; then
        aionui &
    else
        warn "Could not open AionUI automatically. Please start it manually."
        return 1
    fi

    ok "AionUI is running."
}

# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
main() {
    echo "${C_BOLD}${C_ORANGE}"
    echo "  AionUI + OpenCode setup"
    echo "  Everything you need to start coding with DiscreteStack."
    echo "${C_RESET}"

    # AionUI install failure is non-fatal (fallback exhausted, permissions
    # wall, missing app in DMG, failed .deb): the user can install it manually
    # and re-run, but OpenCode must still get configured. Only the checksum
    # mismatch aborts via fail().
    step "Installing AionUI"
    install_aionui || warn "Continuing with OpenCode setup despite AionUI install issue."

    step "Installing OpenCode"
    install_opencode
    ensure_opencode_on_path

    step "Installing productivity skills (OpenCode)"
    install_opencode_skills

    step "Connecting to DiscreteStack"
    configure_discretestack
    write_opencode_config
    health_check_discretestack

    # ---- Phase 1 complete: launch AionUI and wait for the user to log in.
    step "Starting AionUI"
    if launch_aionui; then
        echo ""
        echo "  AionUI is starting. Please complete the login in the AionUI"
        echo "  window (first run only)."
        printf "\n  %sPress Enter after you are logged in to finish setting up skills...%s " \
            "$C_BOLD" "$C_RESET" > /dev/tty
        read -r _ < /dev/tty || true
    else
        warn "AionUI could not be launched automatically."
        printf "\n  Start AionUI, log in, then press Enter to finish setting up skills... " > /dev/tty
        read -r _ < /dev/tty || true
    fi

    # ---- Phase 2: install skills into the logged-in AionUI backend.
    step "Installing productivity skills (AionUI)"
    if install_aionui_skills; then
        # Restart AionUI so it picks up the freshly installed skills cleanly.
        step "Restarting AionUI"
        if [[ "$(uname)" == "Darwin" ]]; then
            osascript -e 'tell application "AionUi" to quit' </dev/null >/dev/null 2>&1 || true
            sleep 2
        fi
        pkill -x AionUi 2>/dev/null || true
        pkill -x aioncore 2>/dev/null || true
        sleep 1
        launch_aionui && ok "AionUI restarted — your skills are now available."
    else
        warn "AionUI skills were not installed; run this script again after logging in."
    fi

    echo ""
    echo "${C_BOLD}${C_GREEN}  ✓ All done!${C_RESET}"
    cat <<'EOF'

    AionUI is now set up with your account's skills.

    Next steps inside AionUI:
      1. On the welcome screen you should see OpenCode detected as an agent.
         Select it to start coding with DiscreteStack through OpenCode.
      2. If you also want to use AionUI's built-in agent with DiscreteStack:
         Settings → Models → Add Model → Custom
           • Base URL : https://api.discretestack.com/v1
           • API Key  : (the key you just entered)
           • Models   : discretestack-stable-fast, discretestack-stable, discretestack-stable-max
         Save, then pick the Built-in Agent.

    To use OpenCode directly from a terminal:
      cd /path/to/project
      opencode

    Then run /init inside OpenCode to create an AGENTS.md for the project.
EOF
}

main "$@"
