#!/usr/bin/env bash
# Rehydrate code-server extensions and user settings.
# Run from the Docker host as the abc user (the default code-server user):
#
#   docker exec -u abc <container-name> bash /config/workspace/codeserver-setup/extensions.sh
#
# Idempotent: `code-server --install-extension` is a no-op for already-installed
# extensions; settings.json is merged with jq so manual tweaks are preserved.
#
# Prereqs: run install.sh first (this script uses `jq` from that install).

set -euo pipefail

# ---------------------------------------------------------------------------
# Preflight
# ---------------------------------------------------------------------------

# Code-server extensions must be installed as the 'abc' user. If we were
# launched as root (e.g. via `docker exec -u root`, matching install.sh's
# invocation), re-exec ourselves as abc.
if [ "$(id -u)" -eq 0 ]; then
    exec runuser -u abc -- bash "$0" "$@"
fi

if [ "$(id -un)" != "abc" ]; then
    echo "ERROR: must run as root or abc; current user: $(id -un)" >&2
    exit 1
fi

if ! command -v jq >/dev/null 2>&1; then
    echo "ERROR: jq not found — run install.sh first." >&2
    exit 1
fi

CODE_SERVER="/app/code-server/bin/code-server"
if [ ! -x "$CODE_SERVER" ]; then
    # Fall back to PATH lookup if the linuxserver path moves in a future image.
    CODE_SERVER="$(command -v code-server || true)"
fi
if [ -z "$CODE_SERVER" ] || [ ! -x "$CODE_SERVER" ]; then
    echo "ERROR: code-server binary not found." >&2
    exit 1
fi

log() { printf '\n\033[1;36m[ext]\033[0m %s\n' "$*"; }

# ---------------------------------------------------------------------------
# Extensions
# ---------------------------------------------------------------------------

# Existing extensions (codified so a fresh container reproduces today's setup).
EXTENSIONS_EXISTING=(
    anthropic.claude-code
    eamodio.gitlens
    mhutchie.git-graph
    oderwat.indent-rainbow
    waderyan.gitblame
)

# New extensions enabled by install.sh tooling.
EXTENSIONS_NEW=(
    bmewburn.vscode-intelephense-client     # PHP IntelliSense
    SanderRonde.phpstan-vscode              # Live PHPStan squiggles
    timonwong.shellcheck                    # Uses the shellcheck binary
    mtxr.sqltools                           # GUI SQL client framework
    mtxr.sqltools-driver-mysql              # MariaDB/MySQL driver for sqltools
    mikestead.dotenv                        # .env syntax highlighting
)

# code-server's CLI tries to talk over IPC to a parent process when it
# inherits VSCODE_* env vars (which happens inside the running code-server
# shell). `env -i` strips the environment so the CLI runs standalone.
code_server_cli() {
    env -i \
        HOME="${HOME:-/config}" \
        PATH="${PATH:-/usr/local/bin:/usr/bin:/bin}" \
        USER="$(id -un)" \
        "$CODE_SERVER" \
        --user-data-dir /config/data \
        --extensions-dir /config/extensions \
        "$@"
}

install_extension() {
    local ext="$1"
    log "Installing $ext"
    # --force lets us upgrade if a newer version is published; safe on re-runs.
    code_server_cli --install-extension "$ext" --force
}

log "Installing existing extensions"
for ext in "${EXTENSIONS_EXISTING[@]}"; do
    install_extension "$ext"
done

log "Installing new tooling extensions"
for ext in "${EXTENSIONS_NEW[@]}"; do
    install_extension "$ext"
done

# ---------------------------------------------------------------------------
# User settings — merge with existing settings.json
# ---------------------------------------------------------------------------

SETTINGS_FILE="/config/data/User/settings.json"
mkdir -p "$(dirname "$SETTINGS_FILE")"
[ -f "$SETTINGS_FILE" ] || echo '{}' > "$SETTINGS_FILE"

# Detect binary paths at runtime so settings stay correct regardless of where
# apt/composer placed each tool (e.g. shellcheck is /usr/bin, PHARs are /usr/local/bin).
PHP_BIN="$(command -v php || echo /usr/bin/php)"
PHPSTAN_BIN="$(command -v phpstan || echo /usr/local/bin/phpstan)"
SHELLCHECK_BIN="$(command -v shellcheck || echo /usr/bin/shellcheck)"

# Canonical settings: anything here is applied; anything not here is preserved
# if already present in settings.json. To force-overwrite a setting, list it here.
CANONICAL_SETTINGS=$(jq -n \
    --arg php "$PHP_BIN" \
    --arg phpstan "$PHPSTAN_BIN" \
    --arg shellcheck "$SHELLCHECK_BIN" \
    '{
        "workbench.colorTheme": "Default Dark Modern",
        "claudeCode.preferredLocation": "panel",
        "files.associations": {
            "*.inc": "php"
        },
        "remote.autoForwardPortsSource": "hybrid",
        "php.validate.executablePath": $php,
        "phpstan.binPath": $phpstan,
        "phpstan.configFile": "evolution/.dev-tools/phpstan.neon",
        "phpstan.enabled": true,
        "shellcheck.executablePath": $shellcheck,
        "intelephense.environment.phpVersion": "8.3.0"
    }')

log "Merging settings.json (existing values win over canonical for keys not listed above)"
TMP="$(mktemp)"
# Merge order: canonical first, then existing on top — so the user's manual
# tweaks beat the canonical defaults for any key not explicitly in CANONICAL_SETTINGS.
# For keys IN CANONICAL_SETTINGS, we want canonical to win (it's the source of truth
# for the rehydrate). Achieved by listing existing first, then canonical: `*` is
# right-biased in jq.
echo "$CANONICAL_SETTINGS" | jq -s --slurpfile existing "$SETTINGS_FILE" \
    '$existing[0] * .[0]' > "$TMP"
mv "$TMP" "$SETTINGS_FILE"

# ---------------------------------------------------------------------------
# Pin the `claude` CLI to the newest installed extension
# ---------------------------------------------------------------------------
#
# The Claude Code VS Code extension shells out to whatever `claude` is on PATH.
# If /usr/local/bin/claude points at an older extension's bundled binary than
# the one the editor loads, the UICLI handshake drifts and things like session
# renames silently fail to persist. The base image created that symlink once and
# never updates it, while this script keeps pulling newer extension versions —
# so we repoint it here, on every rehydrate, to whatever version is now newest.
log "Pinning /usr/local/bin/claude to the newest installed extension binary"
NEWEST_EXT_DIR="$(find /config/extensions -maxdepth 1 -type d \
    -name 'anthropic.claude-code-*-linux-x64' 2>/dev/null | sort -V | tail -1)"
NEWEST_CLAUDE="${NEWEST_EXT_DIR:+$NEWEST_EXT_DIR/resources/native-binary/claude}"
if [ -n "$NEWEST_CLAUDE" ] && [ -x "$NEWEST_CLAUDE" ]; then
    # /usr/local/bin is root-owned and this script runs as abc, hence sudo.
    if sudo ln -sfn "$NEWEST_CLAUDE" /usr/local/bin/claude; then
        log "  claude -> $NEWEST_CLAUDE ($("$NEWEST_CLAUDE" --version 2>/dev/null || echo '?'))"
    else
        log "  WARNING: could not update symlink (sudo failed?); run manually:"
        log "    sudo ln -sfn '$NEWEST_CLAUDE' /usr/local/bin/claude"
    fi
else
    log "  WARNING: no native-binary/claude found under /config/extensions; skipping"
fi

# ---------------------------------------------------------------------------
# Verification
# ---------------------------------------------------------------------------

log "Installed extensions:"
code_server_cli --list-extensions | sed 's/^/  /'

log "Final settings.json:"
jq . "$SETTINGS_FILE" | sed 's/^/  /'

log "Done. Reload the code-server window (Cmd/Ctrl+Shift+P → 'Reload Window') to activate new extensions and settings."
