#!/usr/bin/env bash
# sw.sh — open Claude Code with its brain served by the owner's ChatGPT web session.
# audience: AI coding agents first. Invoke by path; do NOT re-derive the env block or the claude -p line.
#
# WHY this exists: the ChatGPT chat surface has its own near-separate quota, but no agency.
# solwebd serves that session as an OpenAI-compatible provider, ccr routes Claude Code at it,
# and Claude Code brings the tool loop. This wrapper is the ONE fail-closed command that brings
# both hops up before dispatching, so a proxy that is down can never be mistaken for a bad model.
#
# CONTRACT: sw.sh --workspace <dir> --trust "<prompt>" --task-slug <slug> [--model sol-web-<effort>]
#                 [--timeout <secs>] [--profile <slug>] [--session-id <id>] [--health] [--list-models] [--stop]
#   - Ensures solwebd is up (starts it if not), then ccr via the TESTED ccr-up.sh.
#     Either down and unstartable -> exit 3, NO dispatch.
#   - Runs claude FOREGROUND under `timeout -k 5 <secs>` (default 3600); stdin </dev/null.
#     3600, not na.sh's 360: a sol-web tool step measures 10-20s, so 360 kills nearly every real task.
#   - Logs raw output to a per-run logfile; stdout carries only the final status JSON line.
#   - Exit: 0 completed; 124 timeout/killed; 2 usage error; 3 daemon/ccr down or session logged out;
#     75 usage cap (carries resume_at).
set -uo pipefail

WORKSPACE="" PROMPT="" TASK_SLUG="" PROFILE="" TIMEOUT=3600 SESSION_ID="" MODEL="sol-web-medium"
MODE="dispatch"
while [[ $# -gt 0 ]]; do
  case "$1" in
    --workspace) [[ $# -lt 2 ]] && { echo '{"ok":false,"detail":"--workspace requires a value"}' >&2; exit 2; }; WORKSPACE="${2:-}"; shift 2;;
    --trust)     [[ $# -lt 2 ]] && { echo '{"ok":false,"detail":"--trust requires a value"}' >&2; exit 2; }; PROMPT="${2:-}"; shift 2;;
    --task-slug) [[ $# -lt 2 ]] && { echo '{"ok":false,"detail":"--task-slug requires a value"}' >&2; exit 2; }; TASK_SLUG="${2:-}"; shift 2;;
    --model)     [[ $# -lt 2 ]] && { echo '{"ok":false,"detail":"--model requires a value"}' >&2; exit 2; }; MODEL="${2:-}"; shift 2;;
    --timeout)   [[ $# -lt 2 ]] && { echo '{"ok":false,"detail":"--timeout requires a value"}' >&2; exit 2; }; TIMEOUT="${2:-}"; shift 2;;
    --profile)   [[ $# -lt 2 ]] && { echo '{"ok":false,"detail":"--profile requires a value"}' >&2; exit 2; }; PROFILE="${2:-}"; shift 2;;
    --session-id) [[ $# -lt 2 ]] && { echo '{"ok":false,"detail":"--session-id requires a value"}' >&2; exit 2; }; SESSION_ID="${2:-}"; shift 2;;
    --health) MODE="health"; shift;;
    --list-models) MODE="list-models"; shift;;
    --stop) MODE="stop"; shift;;
    --interactive) MODE="interactive"; shift;;
    *) shift;;
  esac
done
[[ -n "$PROFILE" ]] && echo "notice: --profile=$PROFILE ignored — sw.sh drives one browser profile" >&2

SKILL_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)/.."
source "$SKILL_DIR/wrappers/lib/finalize.sh"

_CCR_UP="${_SW_CCR_UP_BIN:-"$HOME/.claude/workflows/lib/ccr-up.sh"}"
_SOLWEBD="${_SW_SOLWEBD_BIN:-"$SKILL_DIR/../gptbridge/bin/solwebd"}"
_SOLWEBCTL="${_SW_SOLWEBCTL_BIN:-"$SKILL_DIR/../gptbridge/bin/solwebctl"}"
_SOLWEBD_PORT="${SOLWEBD_PORT:-8791}"
_SOLWEBD_URL="http://127.0.0.1:${_SOLWEBD_PORT}"
_TOKEN_FILE="${SOLWEBD_TOKEN_FILE:-$HOME/.overdeck/gptbridge/solwebd.token}"
_CCR_CONFIG="${CCR_CONFIG_FILE:-$HOME/.claude-code-router/config.json}"
_LOG_DIR="${SW_LOG_DIR:-$HOME/.overdeck/gptbridge/logs}"

http_json() {
  # Prints the body; exit 0 on 2xx, 3 on anything else. Bounded — never hangs the run.
  local token
  if [[ -n "${_SW_SOLWEBD_BIN:-}" && -z "${_SW_SOLWEBCTL_BIN:-}" ]]; then
    token="$(cat "$_TOKEN_FILE")" || { echo '{"ok":false,"detail":"solweb token unavailable"}' >&2; return 3; }
  else
    token="$("$_SOLWEBCTL" token)" || { echo '{"ok":false,"detail":"solweb token unavailable"}' >&2; return 3; }
  fi
  [[ -n "$token" ]] || { echo '{"ok":false,"detail":"solweb token empty"}' >&2; return 3; }
  URL="$1" TOKEN="$token" METHOD="${2:-GET}" python3 - <<'PY'
import json, os, pathlib, sys, urllib.error, urllib.request

token = os.environ["TOKEN"]
request = urllib.request.Request(os.environ["URL"], method=os.environ.get("METHOD", "GET"))
request.add_header("Authorization", f"Bearer {token}")
if request.method == "POST":
    request.data = b"{}"
    request.add_header("Content-Type", "application/json")
try:
    with urllib.request.urlopen(request, timeout=10) as response:
        print(response.read().decode("utf-8") or json.dumps({"ok": True}))
except urllib.error.HTTPError as exc:
    print(json.dumps({"ok": False, "detail": f"HTTP {exc.code} from {os.environ['URL']}"}), file=sys.stderr)
    sys.exit(3)
except Exception as exc:
    print(json.dumps({"ok": False, "detail": f"unreachable: {exc}"}), file=sys.stderr)
    sys.exit(3)
PY
}

solwebd_up() {
  # Explicit daemon injection is the frozen wrapper-test seam; normal callers
  # always take the enrolled-control-plane path below.
  if [[ -n "${_SW_SOLWEBD_BIN:-}" && -z "${_SW_SOLWEBCTL_BIN:-}" ]]; then
    if ! curl -fsS --max-time 1 "$_SOLWEBD_URL/healthz" >/dev/null 2>&1; then
      "$_SOLWEBD" >/dev/null 2>&1 &
      for _ in {1..20}; do curl -fsS --max-time 1 "$_SOLWEBD_URL/healthz" >/dev/null 2>&1 && break; sleep .1; done
    fi
    curl -fsS --max-time 1 "$_SOLWEBD_URL/healthz" >/dev/null 2>&1 || { echo '{"ok":false,"detail":"solwebd unavailable"}' >&2; return 3; }
    return 0
  fi
  [[ -x "$_SOLWEBCTL" ]] || { echo '{"ok":false,"detail":"solwebctl not executable"}' >&2; return 3; }
  local health
  health="$("$_SOLWEBCTL" ensure --legacy --json --seats 1)" || { printf '%s\n' "$health" >&2; return 3; }
  [[ "$health" == *'"ok":true'* ]] || { printf '%s\n' "$health" >&2; return 3; }
}

ccr_provider_registered() {
  CFG="$_CCR_CONFIG" python3 - <<'PY'
import json, os, sys
try:
    config = json.load(open(os.environ["CFG"]))
except Exception:
    sys.exit(1)
sys.exit(0 if any(p.get("name") == "solweb" for p in config.get("Providers", [])) else 1)
PY
}

ccr_up() {
  local ccr_json
  ccr_json="$(bash "$_CCR_UP" solweb 2>/dev/null)"
  case "$ccr_json" in
    *'"up":true'*) ;;
    *) echo "{\"ok\":false,\"detail\":\"ccr not up: ${ccr_json}\"}" >&2; return 3 ;;
  esac
  # ccr up but unaware of solweb answers 404 mid-dispatch, worded as an unknown model.
  ccr_provider_registered && return 0
  echo "{\"ok\":false,\"detail\":\"ccr has no solweb provider in ${_CCR_CONFIG}; run install-ccr-provider then restart ccr\"}" >&2
  return 3
}

case "$MODE" in
  health)
    solwebd_up || exit $?
    ccr_up || exit $?
    http_json "$_SOLWEBD_URL/healthz"
    exit $?
    ;;
  list-models)
    solwebd_up || exit $?
    http_json "$_SOLWEBD_URL/v1/models"
    exit $?
    ;;
  stop)
    if [[ -n "${_SW_SOLWEBD_BIN:-}" && -z "${_SW_SOLWEBCTL_BIN:-}" ]]; then
      if ! pgrep -f "${_SOLWEBD%/*}/solwebd.py" >/dev/null 2>&1; then
        echo '{"ok":true,"detail":"solwebd was not running"}'
      else
        http_json "$_SOLWEBD_URL/shutdown" POST || exit $?
        for _ in {1..50}; do pgrep -f "${_SOLWEBD%/*}/solwebd.py" >/dev/null 2>&1 || break; sleep .1; done
        pgrep -f "${_SOLWEBD%/*}/solwebd.py" >/dev/null 2>&1 && { echo '{"ok":false,"detail":"solwebd stop timeout"}' >&2; exit 3; }
        echo '{"ok":true,"detail":"solwebd stopped"}'
      fi
    else
      "$_SOLWEBCTL" stop --json
    fi
    exit $?
    ;;
  interactive)
    echo '{"ok":false,"detail":"--interactive is not supported by wrappers/sw.sh; run claude directly in the workspace"}' >&2
    exit 2
    ;;
esac

TASK_SLUG="${TASK_SLUG//[^a-zA-Z0-9_-]/}"
RUN_ID="$(date +%Y-%m-%d-%H-%M-%S)-$$"

[[ -n "$WORKSPACE" && -d "$WORKSPACE" ]] || { echo '{"ok":false,"detail":"--workspace missing or not a dir"}' >&2; exit 2; }
[[ -n "$PROMPT" ]]    || { echo '{"ok":false,"detail":"--trust (prompt) required"}' >&2; exit 2; }
[[ -n "$TASK_SLUG" ]] || { echo '{"ok":false,"detail":"--task-slug required (log + retry hygiene)"}' >&2; exit 2; }
[[ "$MODEL" =~ ^sol-web-(instant|medium|high|xhigh|pro)$ ]] || {
  echo "{\"ok\":false,\"detail\":\"--model must be sol-web-<instant|medium|high|xhigh|pro>, got ${MODEL}\"}" >&2; exit 2; }

# 1. the browser-backed provider, then 2. the proxy in front of it. Fail-closed: no dispatch without both.
solwebd_up || exit $?
ccr_up || exit $?

# 3. environment — scoped to this subprocess, so the caller's real-Anthropic shell is untouched.
CCR_PORT="$(CFG="$_CCR_CONFIG" python3 -c 'import json,os;print(json.load(open(os.environ["CFG"])).get("PORT",3456))' 2>/dev/null || echo 3456)"
CCR_KEY="$(CFG="$_CCR_CONFIG" python3 -c 'import json,os;print(json.load(open(os.environ["CFG"])).get("APIKEY",""))' 2>/dev/null || echo "")"
export CLAUDE_CONFIG_DIR="${SW_CLAUDE_CONFIG_DIR:-$HOME/.claude-solweb}"
export ANTHROPIC_BASE_URL="http://127.0.0.1:${CCR_PORT}"
export ANTHROPIC_MODEL="solweb,${MODEL}"
export ANTHROPIC_SMALL_FAST_MODEL="solweb,${MODEL}"
export ANTHROPIC_API_KEY="$CCR_KEY"
export ANTHROPIC_AUTH_TOKEN="$CCR_KEY"
mkdir -p "$CLAUDE_CONFIG_DIR"

mkdir -p "$_LOG_DIR"
LOG="$_LOG_DIR/$RUN_ID-$TASK_SLUG.log"
RAW_OUTPUT_FILE="$_LOG_DIR/$RUN_ID-$TASK_SLUG.raw"
: > "$RAW_OUTPUT_FILE"
TRANSCRIPT_PATH="${HARNESS_TRANSCRIPT_PATH:-}"
if [[ -n "$TRANSCRIPT_PATH" ]]; then
  mkdir -p "$(dirname "$TRANSCRIPT_PATH")" || { echo '{"ok":false,"detail":"transcript dir create failed"}' >&2; exit 3; }
fi

stream_output() {
  if [[ -n "$TRANSCRIPT_PATH" ]]; then tee -a "$LOG" "$RAW_OUTPUT_FILE" "$TRANSCRIPT_PATH"
  else tee -a "$LOG" "$RAW_OUTPUT_FILE"; fi
}

emit_event_line() {
  if [[ -n "$TRANSCRIPT_PATH" ]]; then printf '%s\n' "$1" | tee -a "$LOG" "$TRANSCRIPT_PATH"
  else printf '%s\n' "$1" | tee -a "$LOG"; fi
}

# DRY: built ONCE, so the header line and the run line cannot name different models.
OUTPUT_FORMAT="json"
SESSION_STATUS=""
if [[ -n "$SESSION_ID" ]]; then
  OUTPUT_FORMAT="stream-json"
  SESSION_STATUS="=== session_id: $SESSION_ID ==="
  SW_ARGS=(timeout -k 5 "$TIMEOUT" claude -p "$PROMPT" --session-id "$SESSION_ID" --output-format "$OUTPUT_FORMAT" --dangerously-skip-permissions)
else
  SW_ARGS=(timeout -k 5 "$TIMEOUT" claude -p "$PROMPT" --output-format "$OUTPUT_FORMAT" --dangerously-skip-permissions)
fi
{
  echo "=== TOOL: sol-web (${MODEL}) via ccr -> solwebd ==="
  echo "=== CMD: $(printf '%q ' "${SW_ARGS[@]}") ==="
  echo "=== workspace: $WORKSPACE  timeout: ${TIMEOUT}s  log: $LOG ==="
  [[ -n "$SESSION_STATUS" ]] && echo "$SESSION_STATUS"
} | tee "$LOG"

# 4. dispatch FOREGROUND, in the workspace, stdin closed. Never background, never pipe to a pager.
cd "$WORKSPACE" || { echo '{"ok":false,"detail":"cd workspace failed"}' >&2; exit 2; }
"${SW_ARGS[@]}" < /dev/null 2>&1 | stream_output
RC="${PIPESTATUS[0]}"
echo "=== sol-web exit=$RC ===" | tee -a "$LOG"
finalize_terminal "$RC" 'sol-web' "$SESSION_ID" '' "$MODEL"
