#!/usr/bin/env bash
# uai-init — prepare a standard-image task container (ADR-022).
#
# Runs INSIDE the container (Linux, bash 5 — may use anything). Invoked by
# the host via `docker exec <app> /usr/local/bin/uai-init` after compose up.
# Spec: ../../../docs/runtime.md §uai-init.
#
# Workspace layout (ADR-022): /workspace/<project-slug>/ — one git worktree
# per selected project. A 0-project (scratchpad) task has an empty /workspace.
#
# Responsibilities:
#   1. For each /workspace/<slug>/ that carries a .tool-versions: `asdf
#      install` (downloads cached on the host-wide /opt/asdf-data volume),
#      then a dependency-install heuristic for the folder's stack.
#   2. Launch code-server on 0.0.0.0:8080 (auth disabled — reached only via
#      the authenticated tunnel), opening /workspace.
#
# Best-effort + idempotent: a failing folder logs and is skipped; it never
# aborts the whole init. Re-running when things are already up is a no-op.
# uai-init itself always exits 0 so task-up's `docker exec ... uai-init` step
# succeeds as long as the container is reachable.

set -uo pipefail

WORKSPACE="${UAI_WORKSPACE:-/workspace}"
ASDF_SH="${ASDF_DIR:-/opt/asdf}/asdf.sh"

log() { echo "uai-init: $*"; }

# Make asdf + its shims available to this non-interactive shell. A bare
# `docker exec` sources no profile, so source asdf explicitly here.
if [ -f "$ASDF_SH" ]; then
  # shellcheck disable=SC1090
  . "$ASDF_SH"
else
  log "warning: asdf not found at $ASDF_SH; runtime install will be skipped"
fi

# ---------------------------------------------------------------------------
# 0. GitHub auth for in-container git (ADR-027). git rides the host's uai SSH
#    identity (docker-cp'd to ~/.ssh, registered on GitHub for auth + signing),
#    NOT an HTTPS token. Route every GitHub remote — including https:// ones —
#    over SSH so a project's clone scheme doesn't matter, and trust github's
#    host key non-interactively so a fresh container's first push doesn't stall
#    on a prompt. `gh` is separate: it authenticates from its own config file,
#    which the host writes via `gh auth login --with-token` with the user's
#    short-lived token. We must NOT set GH_TOKEN in the env — `gh` would prefer
#    it over that stored credential (and re-attribute every PR to it).
# ---------------------------------------------------------------------------

if [ -f "$HOME/.ssh/id_ed25519" ]; then
  log "routing git GitHub remotes over the uai SSH identity"
  git config --global url."git@github.com:".insteadOf "https://github.com/"
  git config --global core.sshCommand "ssh -o StrictHostKeyChecking=accept-new"
fi

# Signed commits (policy): when the uai SSH identity is present, configure git
# to SSH-sign every commit + tag with it. The pubkey is registered on GitHub
# (via `setup-identity`) so commits show as Verified. The same key also carries
# git push over SSH (configured above).
if [ -f "$HOME/.ssh/id_ed25519.pub" ]; then
  log "enabling SSH commit signing with the uai identity"
  git config --global gpg.format ssh
  git config --global user.signingkey "$HOME/.ssh/id_ed25519.pub"
  git config --global commit.gpgsign true
  git config --global tag.gpgsign true
fi

# Attribution policy: never co-author with the agents. Claude Code honors
# `includeCoAuthoredBy: false` (drops the "Co-Authored-By: Claude" commit
# trailer and the "Generated with Claude Code" PR footer). Seed it into the
# per-task Claude user settings if absent. Codex is covered by the
# orchestrator's system preamble.
cc_settings="$HOME/.claude/settings.json"
if [ ! -f "$cc_settings" ]; then
  mkdir -p "$HOME/.claude"
  printf '{\n  "includeCoAuthoredBy": false\n}\n' > "$cc_settings"
  log "seeded ~/.claude/settings.json (includeCoAuthoredBy: false)"
fi

# ---------------------------------------------------------------------------
# 1. Per-folder runtime + dependency install.
# ---------------------------------------------------------------------------

install_node_deps() {
  # Pick the package manager from the packageManager field, else a lockfile,
  # else npm. corepack provisions pnpm/yarn from the packageManager field.
  local pm=""
  if [ -f package.json ]; then
    pm=$(jq -r '.packageManager // ""' package.json 2>/dev/null | sed 's/@.*//')
  fi
  if [ -z "$pm" ]; then
    if [ -f pnpm-lock.yaml ]; then pm="pnpm"
    elif [ -f yarn.lock ]; then pm="yarn"
    elif [ -f package-lock.json ] || [ -f npm-shrinkwrap.json ]; then pm="npm"
    elif [ -f bun.lockb ] || [ -f bun.lock ]; then pm="bun"
    else pm="npm"
    fi
  fi

  # corepack ships with Node and resolves pnpm/yarn per the packageManager
  # field; harmless no-op when already enabled.
  corepack enable >/dev/null 2>&1 || true

  case "$pm" in
    pnpm) log "pnpm install"; pnpm install ;;
    yarn) log "yarn install"; yarn install ;;
    bun)  log "bun install";  bun install ;;
    *)    log "npm install";  npm install --no-audit --no-fund ;;
  esac
}

install_python_deps() {
  # uv handles both requirements.txt and pyproject.toml; create a project .venv
  # so the install is isolated to the folder. Pin uv to ASDF's Python (the
  # version asdf just resolved from the in-scope .tool-versions) so the venv
  # matches the project's declared runtime. uv otherwise selects via
  # .python-version / requires-python, which can disagree with .tool-versions
  # (or be junk, e.g. a stray pyenv-virtualenv name) — asdf is the authority.
  local py
  py="$(asdf which python 2>/dev/null || true)"
  if [ -f pyproject.toml ]; then
    log "uv sync (pyproject.toml)${py:+ — asdf python $py}"
    if [ -n "$py" ]; then uv sync --python "$py"; else uv sync; fi
  elif [ -f requirements.txt ]; then
    log "uv venv + pip install -r requirements.txt${py:+ — asdf python $py}"
    if [ -n "$py" ]; then uv venv --python "$py"; else uv venv; fi \
      && uv pip install -r requirements.txt
  fi
}

install_folder() {
  local dir="$1" name
  name=$(basename "$dir")

  # Enter the folder; asdf auto-resolves the nearest .tool-versions here.
  cd "$dir" || { log "cannot cd into $name — skipping"; return; }

  # Runtime install. asdf reads the in-scope .tool-versions and downloads any
  # missing versions (cached on the /opt/asdf-data volume).
  if command -v asdf >/dev/null 2>&1; then
    log "asdf install ($name)"
    asdf install || log "asdf install failed in $name — continuing"
  fi

  # Dependency-install heuristic. Run every matcher that applies (a polyglot
  # folder may have more than one); each is independently best-effort.
  if [ -f package.json ]; then
    install_node_deps || log "node deps failed in $name — continuing"
  fi
  if [ -f pyproject.toml ] || [ -f requirements.txt ]; then
    install_python_deps || log "python deps failed in $name — continuing"
  fi
  if [ -f Cargo.toml ]; then
    log "cargo build ($name)"
    cargo build || log "cargo build failed in $name — continuing"
  fi
  if [ -f go.mod ]; then
    log "go mod download ($name)"
    go mod download || log "go mod download failed in $name — continuing"
  fi
}

if [ -d "$WORKSPACE" ]; then
  shopt -s nullglob
  for folder in "$WORKSPACE"/*/; do
    [ -d "$folder" ] || continue
    if [ -f "${folder}.tool-versions" ]; then
      # Run in a subshell so a `cd` (or a failing command under the relaxed
      # error mode) in one folder never leaks into the next.
      ( install_folder "$folder" )
    else
      log "no .tool-versions in $(basename "$folder") — skipping per-folder install"
    fi
  done
  shopt -u nullglob
else
  log "workspace $WORKSPACE missing — scratchpad/no-project task, skipping installs"
fi

# ---------------------------------------------------------------------------
# 2. code-server — the Editor pane. Bound to 0.0.0.0:8080, auth disabled
#    (reached only through the authenticated tunnel / editor proxy). Opens
#    /workspace so all worktrees are visible at the top level. Idempotent:
#    skips launch if already running.
# ---------------------------------------------------------------------------

editor_root="$WORKSPACE"
[ -d "$editor_root" ] || editor_root="/home/node"

# Seed the curated code-server defaults (slim chrome, telemetry off, trust
# off) into the User settings dir when the task hasn't written its own yet.
cs_user_dir="$HOME/.local/share/code-server/User"
cs_seed="/usr/local/share/uai/code-server-settings.json"
if [ -f "$cs_seed" ] && [ ! -f "$cs_user_dir/settings.json" ]; then
  mkdir -p "$cs_user_dir"
  cp "$cs_seed" "$cs_user_dir/settings.json"
  log "seeded code-server settings"
fi

if pgrep -f 'code-server.*--bind-addr' >/dev/null 2>&1; then
  log "code-server already running"
else
  log "launching code-server on 0.0.0.0:8080"
  nohup code-server \
    --auth none \
    --disable-telemetry \
    --bind-addr 0.0.0.0:8080 \
    "$editor_root" \
    >/tmp/code-server.log 2>&1 &
  disown
fi

# Always succeed: the container is up and code-server has been (re)launched.
exit 0
