#!/usr/bin/env bash
# ---------------------------------------------------------------------------
# Seligo for OpenCode — one-line installer (mirrors the opencode.ai/install
# pattern: curl | bash).
#
#   curl -fsSL https://docs.seligo.cloud/downloads/seligo-opencode-install.sh | bash
#
# What it does (all verified against OpenCode 1.18.27 — see WP26 notes):
#   1. Checks prerequisites: Node.js 24.16+, OpenCode on PATH.
#   2. Downloads the first-party Seligo provider plugin
#      (https://docs.seligo.cloud/downloads/seligo-opencode-provider-0.1.0.tgz)
#      and verifies its SHA-256 (reference copy; not unpacked into the global
#      plugins dir — on 1.18 the plugin files' mere presence breaks provider
#      discovery, see below).
#   3. Fetches the live catalogue for YOUR key and pins it into
#      ~/.config/opencode/opencode.json as the `seligo` provider
#      (baseURL + model list + a default model — the API key is NEVER written
#      to config).
#   4. Stores your key in OpenCode's auth store
#      (~/.local/share/opencode/auth.json, chmod 600) — the exact shape
#      `opencode auth login` writes, and the mechanism that works on 1.18.
#   5. Verifies the wiring: `opencode models seligo` lists your models, and a
#      default `opencode run` round-trips.
#
# Why config + auth store, not the plugin: on OpenCode 1.18 the provider is
# registered by the config. The plugin is NOT installed into
# ~/.config/opencode/plugins/ because its mere presence breaks provider
# discovery on 1.18 ("Provider not found: seligo" — verified). It ships for
# newer OpenCode versions / npm once published.
#
# Options:
#   --key rk-...          provide the key on the command line (no prompt)
#   --no-store-key        use SELIGO_API_KEY from env only; write nothing
#   --no-modify-config    do not touch opencode.json (plugin reference only)
#   --uninstall           remove plugin, provider block, auth entry
#   --yes / -y            non-interactive (no prompts)
#   --help / -h           show this help
#
# Exit codes: 0 ok; 1 prerequisite missing; 2 bad usage; 3 install/verify failed.
# ---------------------------------------------------------------------------
set -euo pipefail

APP="seligo-opencode"
VERSION="0.1.0"
PLUGIN_ARCHIVE_URL="https://docs.seligo.cloud/downloads/seligo-opencode-provider-${VERSION}.tgz"
PLUGIN_SHA256="fd2447891015112c793e2174ea082b15053b35970978f4cb4a919a78db891f3a"
CONFIG_DIR="$HOME/.config/opencode"
PLUGIN_DIR="$CONFIG_DIR/plugins"
CONFIG_FILE="$CONFIG_DIR/opencode.json"
AUTH_FILE="$HOME/.local/share/opencode/auth.json"
BASE_URL="https://api.seligo.cloud/v1"
MUTED='\033[0;2m'; RED='\033[0;31m'; GREEN='\033[0;32m'; ORANGE='\033[38;5;214m'; NC='\033[0m'

usage() {
    cat <<EOF
Seligo for OpenCode installer ${VERSION}

Usage:
  curl -fsSL https://docs.seligo.cloud/downloads/seligo-opencode-install.sh | bash
  curl -fsSL .../seligo-opencode-install.sh | bash -s -- --key rk-...

Options:
  --key rk-...          Seligo API key (skips the prompt)
  --no-store-key        use SELIGO_API_KEY from env only; write nothing
  --no-modify-config    verify only; leave opencode.json alone
  --uninstall           remove plugin + provider block + auth entry
  --yes, -y             non-interactive (no prompts)
  -h, --help            show this help

The Seligo key is read from: SELIGO_API_KEY (env), then OpenCode's auth store.
It is never written to opencode.json.
EOF
}

say()  { printf "${GREEN}==>${NC} %b\n" "$*"; }
info() { printf "${MUTED}    %b${NC}\n" "$*"; }
warn() { printf "${ORANGE}  !${NC} %b\n" "$*" >&2; }
die()  { printf "${RED}Error:${NC} %b\n" "$*" >&2; exit "${2:-1}"; }

ASSUME_YES=0
STORE_KEY=1
MODIFY_CONFIG=1
UNINSTALL=0
KEY=""

while [[ $# -gt 0 ]]; do
    case "$1" in
        --key) KEY="${2:-}"; shift 2 ;;
        --no-store-key) STORE_KEY=0; shift ;;
        --no-modify-config) MODIFY_CONFIG=0; shift ;;
        --uninstall) UNINSTALL=1; shift ;;
        --yes|-y) ASSUME_YES=1; shift ;;
        -h|--help) usage; exit 0 ;;
        *) die "unknown option: $1" 2 ;;
    esac
done

# --- uninstall ------------------------------------------------------------
if [[ "$UNINSTALL" = 1 ]]; then
    say "Uninstalling Seligo for OpenCode"
    rm -f "$PLUGIN_DIR/seligo.js"
    rm -rf "$PLUGIN_DIR/seligo"
    if [[ -f "$CONFIG_FILE" ]] && command -v node >/dev/null 2>&1; then
        node -e '
            const fs=require("fs"); const p=process.argv[1];
            try {
                const c=JSON.parse(fs.readFileSync(p,"utf8"));
                if(c.provider?.seligo){ delete c.provider.seligo; if(!Object.keys(c.provider).length) delete c.provider; }
                if(Array.isArray(c.plugin))
                    c.plugin=c.plugin.filter(x=>!(typeof x==="string" && x.includes("seligo")));
                // Clear a dangling default that pointed at a seligo model.
                if(typeof c.model==="string" && c.model.startsWith("seligo/")) delete c.model;
                if(typeof c.small_model==="string" && c.small_model.startsWith("seligo/")) delete c.small_model;
                fs.writeFileSync(p, JSON.stringify(c,null,2)+"\n");
            } catch(e){}' "$CONFIG_FILE" || true
    fi
    if [[ -f "$AUTH_FILE" ]] && command -v node >/dev/null 2>&1; then
        node -e '
            const fs=require("fs"); const p=process.argv[1];
            try { const a=JSON.parse(fs.readFileSync(p,"utf8"));
            if(a.seligo){ delete a.seligo; fs.writeFileSync(p, JSON.stringify(a,null,2)+"\n"); } }
            catch(e){}' "$AUTH_FILE" || true
    fi
    info "Removed plugin, provider block and auth entry."
    info "Your other providers/config were left untouched."
    exit 0
fi

# --- prerequisites --------------------------------------------------------
say "Checking prerequisites"
missing=0
if ! command -v node >/dev/null 2>&1; then
    warn "Node.js not found (required for the plugin loader)."; missing=1
elif ! node --version >/dev/null 2>&1; then
    warn "Node.js present but not runnable."; missing=1
else
    info "node $(node --version | sed 's/^v//')"
fi
if ! command -v opencode >/dev/null 2>&1; then
    warn "OpenCode not found on PATH."; missing=1
fi
if [[ "$missing" = 1 ]]; then
    cat >&2 <<'EOF'

Missing prerequisite:
  - Node.js 24.16+    https://nodejs.org
  - OpenCode          https://opencode.ai/install  (curl -fsSL https://opencode.ai/install | bash)

Install those first, then re-run this installer.
EOF
    exit 1
fi

# --- key ------------------------------------------------------------------
if [[ -n "$KEY" ]]; then
    : # provided on CLI
elif [[ -n "${SELIGO_API_KEY:-}" ]]; then
    KEY="$SELIGO_API_KEY"; info "Using SELIGO_API_KEY from the environment."
elif [[ "$STORE_KEY" = 1 ]]; then
    if [[ "$ASSUME_YES" = 1 ]]; then
        die "no key: pass --key rk-... or export SELIGO_API_KEY, or drop --yes" 2
    fi
    printf "Paste your Seligo API key (rk-...): "
    read -r KEY
    KEY="$(printf '%s' "$KEY" | tr -d '[:space:]')"
fi
if [[ -z "$KEY" && "$STORE_KEY" = 1 && -f "$AUTH_FILE" ]]; then
    # Fall back to the existing auth store if present (re-run).
    KEY="$(node -e 'try{const a=require(process.argv[1]);process.stdout.write(a.seligo?.key||"")}catch(e){}' "$AUTH_FILE")"
fi

# --- download & verify (reference copy only; NOT installed on 1.18) ---------
say "Downloading the Seligo provider plugin (reference copy)"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
archive="$tmp/seligo-opencode-provider.tgz"
curl -fsSL "$PLUGIN_ARCHIVE_URL" -o "$archive"
actual="$(shasum -a 256 "$archive" | awk '{print $1}')"
if [[ "$actual" != "$PLUGIN_SHA256" ]]; then
    die "checksum mismatch for the plugin archive (got $actual, expected $PLUGIN_SHA256). Download refused." 3
fi
info "SHA-256 verified."

# IMPORTANT — verified on OpenCode 1.18.27: the provider is registered by
# config + auth store. The plugin files are NOT installed into the global
# plugins dir, because on 1.18 their mere presence breaks provider discovery
# ("Provider not found: seligo"). The plugin ships for newer OpenCode versions
# / npm once published (docs/runbooks/opencode-provider-publish.md).

# --- config (baseURL + pinned live catalogue; never the key) --------------
if [[ "$MODIFY_CONFIG" = 1 ]]; then
    say "Registering the Seligo provider in OpenCode config"
    mkdir -p "$CONFIG_DIR"
    if [[ -f "$CONFIG_FILE" ]]; then
        cp "$CONFIG_FILE" "$CONFIG_FILE.bak-seligo-$(date +%Y%m%d-%H%M%S)" 2>/dev/null || true
    fi
    if [[ -n "$KEY" ]]; then
        # Fetch the live catalogue and pin every chat-capable model, write the
        # config, and pick a sensible default (prefer tensorx/glm-5.3-flash if
        # present) so the provider actually appears in OpenCode 1.18 — a
        # provider with no default model is hidden (verified in WP26 notes).
        # A single node call does all of it, so there is no shell
        # word-splitting of the catalogue (JSON is one word; `read -r A B`
        # would discard the default line).
        KEY="$KEY" SELIGO_BASE_URL="$BASE_URL" node -e '
            const fs=require("fs");
            const file=process.argv[1];
            const base=(process.env.SELIGO_BASE_URL||"").replace(/\/+$/,"");
            (async()=>{
                let out={}; let def="";
                try {
                    const r=await fetch(base+"/models",{headers:{Authorization:"Bearer "+process.env.KEY}});
                    const b=await r.json();
                    const rows=(b.data||[]).filter(m=>m.modality==="llm"||m.modality===undefined);
                    for(const row of rows) if(row.id) out[row.id]={ name: row.name||row.id };
                    const ids=Object.keys(out);
                    const defaults=["tensorx/glm-5.3-flash","tensorx/glm-5.3","scaleway/glm-5.3","scaleway/glm-5.2"];
                    def=(defaults.find(d=>ids.includes(d)))||ids[0]||"";
                } catch(e){}
                let c={};
                if (fs.existsSync(file)) { try { c=JSON.parse(fs.readFileSync(file,"utf8")); } catch(e){ c={}; } }
                c.$schema = c.$schema || "https://opencode.ai/config.json";
                c.provider = c.provider || {};
                c.provider.seligo = {
                    npm: "@ai-sdk/openai-compatible",
                    name: "Seligo Cloud",
                    options: { baseURL: "https://api.seligo.cloud/v1" },
                    models: out
                };
                // A pinned default makes the provider visible in OpenCode 1.18.
                if (def && Object.keys(out).length) { c.model = "seligo/"+def; c.small_model = "seligo/"+def; }
                // Do NOT add a seligo plugin entry: OpenCode 1.18 does not
                // resolve a bare file name and a bad entry breaks provider
                // discovery. The plugin files are still installed for newer
                // versions that auto-load them.
                fs.writeFileSync(file, JSON.stringify(c,null,2)+"\n");
                console.error(`models=${Object.keys(out).length} default=${def}`);
            })();
        ' "$CONFIG_FILE" 2>&1 | sed 's/^/    /'
    else
        node -e '
            const fs=require("fs");
            const file=process.argv[1];
            let c={};
            if (fs.existsSync(file)) { try { c=JSON.parse(fs.readFileSync(file,"utf8")); } catch(e){ c={}; } }
            c.$schema = c.$schema || "https://opencode.ai/config.json";
            c.provider = c.provider || {};
            c.provider.seligo = {
                npm: "@ai-sdk/openai-compatible",
                name: "Seligo Cloud",
                options: { baseURL: "https://api.seligo.cloud/v1" },
                models: {}
            };
            fs.writeFileSync(file, JSON.stringify(c,null,2)+"\n");
        ' "$CONFIG_FILE"
    fi
    info "Wrote $CONFIG_FILE — provider registered (live catalogue pinned; no key stored here)."
fi

# --- auth store -------------------------------------------------------------
if [[ "$STORE_KEY" = 1 && -n "$KEY" ]]; then
    say "Storing your Seligo key in OpenCode auth"
    mkdir -p "$(dirname "$AUTH_FILE")"
    node -e '
        const fs=require("fs"); const p=process.argv[1]; const k=process.argv[2];
        let a={}; if (fs.existsSync(p)) { try { a=JSON.parse(fs.readFileSync(p,"utf8")); } catch(e){ a={}; } }
        a.seligo = { type:"api", key:k };
        fs.writeFileSync(p, JSON.stringify(a,null,2)+"\n");
        fs.chmodSync(p, 0o600);
    ' "$AUTH_FILE" "$KEY"
    info "Stored in $AUTH_FILE (chmod 600). SELIGO_API_KEY env still overrides it."
elif [[ -n "${SELIGO_API_KEY:-}" ]]; then
    info "Key stays in the environment (SELIGO_API_KEY)."
fi

# --- verify ------------------------------------------------------------------
if command -v opencode >/dev/null 2>&1; then
    say "Verifying the live Seligo catalogue"
    if out="$(opencode models seligo 2>&1)"; then
        count="$(printf '%s\n' "$out" | grep -c 'seligo/' || true)"
        info "$(printf '%s\n' "$out" | head -6)"
        printf "${GREEN}==>${NC} Seligo is live in OpenCode (%s models). Done.\n" "$count"
        cat <<'EOF'

Next: run `opencode` and pick a model under "Seligo Cloud", or set it as the
default model in ~/.config/opencode/opencode.json:
  "model": "seligo/<model-id>"

To remove everything later:
  curl -fsSL https://docs.seligo.cloud/downloads/seligo-opencode-install.sh | bash -s -- --uninstall
EOF
    else
        warn "opencode models seligo failed — the provider is registered but the list did not load."
        info "Check that the auth store (or SELIGO_API_KEY) holds a funded rk- key."
        exit 3
    fi
else
    warn "opencode not on PATH — plugin installed; run the verify step after installing OpenCode."
fi
