#!/usr/bin/env bash
###############################################################################
# run.sh — Proxym End-to-End Example: TypeScript TODO App
#
# This script demonstrates the full proxym workflow:
#   1. Creates a real TypeScript project on disk
#   2. Registers it in proxym
#   3. Configures environment & tools
#   4. Creates a sprint with tickets
#   5. Dispatches tickets to AI tools
#   6. Verifies each step automatically
#   7. Creates "human" tickets for anything that fails
#
# Usage:
#   bash examples/todo-app-ts/run.sh                     # full run
#   bash examples/todo-app-ts/run.sh --dry-run           # plan only
#   bash examples/todo-app-ts/run.sh --skip-dispatch     # setup only
#   bash examples/todo-app-ts/run.sh --project-dir /tmp/my-todo  # custom path
#
# Prerequisites:
#   - proxym server running (proxym serve)
#   - Node.js >= 18 installed
#
# The project is created at PROJECT_DIR (default: ~/projects/todo-app-ts)
# and persists on disk even after proxym stops.
###############################################################################
set -euo pipefail

# ── Configuration ────────────────────────────────────────────────────────────

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROXYM_URL="${PROXYM_URL:-http://localhost:4000}"
PROJECT_DIR="${PROJECT_DIR:-$HOME/projects/todo-app-ts}"
TOOL="${TOOL:-aider}"
MODEL="${MODEL:-}"
DRY_RUN=false
SKIP_DISPATCH=false
CLEAN=false
LOG_FILE=""
SPRINT_ID=""
PROJECT_ID=""
ENV_ID=""

# ── API Path Constants ──────────────────────────────────────────────────────

API_ENVIRONMENTS="/environments"
API_PROJECTS="/projects"
API_TICKETS="/tickets"
API_SPRINTS="/sprints"
API_TOOLS_QUEUE="/tools/queue"
API_TOOLS_JOBS="/tools/queue/jobs"
API_USERS="/users"
API_TOOLS_DISPATCH="/tools/dispatch"
API_ENVIRONMENTS_ASSIGN="/environments/assign"

# ── Template Directory ───────────────────────────────────────────────────────

TEMPLATES_DIR="${SCRIPT_DIR}/templates"

# Parse CLI arguments
while [[ $# -gt 0 ]]; do
  case "$1" in
    --dry-run)       DRY_RUN=true; shift ;;
    --skip-dispatch) SKIP_DISPATCH=true; shift ;;
    --clean)         CLEAN=true; shift ;;
    --project-dir)   PROJECT_DIR="$2"; shift 2 ;;
    --tool)          TOOL="$2"; shift 2 ;;
    --model)         MODEL="$2"; shift 2 ;;
    --proxy)         PROXYM_URL="$2"; shift 2 ;;
    --help|-h)
      sed -n '2,/^###/p' "$0" | head -n -1 | sed 's/^# \?//'
      exit 0
      ;;
    *) echo "Unknown option: $1"; exit 1 ;;
  esac
done

CLI="/home/tom/github/wronai/proxy/.venv/bin/proxym --proxy $PROXYM_URL"
LOG_FILE="$PROJECT_DIR/.proxym/run.log"

# ── Trap for graceful shutdown ────────────────────────────────────────────────

_cleanup() {
  local exit_code=$?
  if [[ $exit_code -ne 0 ]]; then
    echo -e "\n${RED}${BOLD}Interrupted (exit code ${exit_code})${RESET}"
    _log "INTERRUPTED: exit code ${exit_code}, SPRINT=${SPRINT_ID}, PROJECT=${PROJECT_ID}, TICKETS=${#TICKET_IDS[@]}"
    echo -e "  ${DIM}State saved to: ${LOG_FILE}${RESET}"
  fi
}
trap _cleanup EXIT INT TERM

# ── Color helpers ────────────────────────────────────────────────────────────

RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; DIM='\033[2m'; RESET='\033[0m'

_hdr()  { echo -e "\n${BOLD}${CYAN}═══ $1 ═══${RESET}"; }
_step() { echo -e "  ${GREEN}▶${RESET} $1"; }
_ok()   { echo -e "  ${GREEN}✓${RESET} $1"; }
_warn() { echo -e "  ${YELLOW}⚠${RESET} $1"; }
_fail() { echo -e "  ${RED}✗${RESET} $1"; }
_info() { echo -e "  ${DIM}$1${RESET}"; }
_log()  { echo "[$(date +%H:%M:%S)] $1" >> "$LOG_FILE" 2>/dev/null || true; }
_date_iso() { date -u +"%Y-%m-%dT%H:%M:%S%z" 2>/dev/null || date -Iseconds 2>/dev/null || date; }

# ── API helpers ──────────────────────────────────────────────────────────────

_api() {
  # Usage: _api METHOD PATH [BODY]
  local method="$1" path="$2" body="${3:-}"
  local url="${PROXYM_URL}/dashboard${path}"
  local args=(-sf -X "$method" -H "Content-Type: application/json")
  [[ -n "$body" ]] && args+=(-d "$body")
  curl "${args[@]}" "$url" 2>/dev/null
}

_api_ok() {
  # Silent API call, return 0 if HTTP 2xx
  local method="$1" path="$2" body="${3:-}"
  local url="${PROXYM_URL}/dashboard${path}"
  local args=(-s -o /dev/null -w "%{http_code}" -X "$method" -H "Content-Type: application/json")
  [[ -n "$body" ]] && args+=(-d "$body")
  local code
  code=$(curl "${args[@]}" "$url" 2>/dev/null || echo "000")
  [[ "$code" =~ ^2 ]]
}

_api_with_status() {
  # Usage: _api_with_status METHOD PATH [BODY]
  # Prints: HTTP_CODE<TAB>BODY
  local method="$1" path="$2" body="${3:-}"
  local url="${PROXYM_URL}/dashboard${path}"
  local args=(-s -w "\n%{http_code}" -X "$method" -H "Content-Type: application/json")
  [[ -n "$body" ]] && args+=(-d "$body")
  local raw
  raw=$(curl "${args[@]}" "$url" 2>/dev/null || echo -e "\n000")
  local code
  code=$(echo "$raw" | tail -1)
  local resp_body
  resp_body=$(echo "$raw" | sed '$d')
  echo -e "${code}\t${resp_body}"
}

_api_check() {
  # Usage: local result; result=$(_api_check METHOD PATH [BODY])
  # Returns body if 2xx, empty + warns if error
  local raw
  raw=$(_api_with_status "$@")
  local code
  code=$(echo "$raw" | cut -f1)
  local body
  body=$(echo "$raw" | cut -f2-)
  if [[ "$code" =~ ^2 ]]; then
    echo "$body"
  else
    _warn "API ${1} ${2} returned HTTP ${code}"
    _log "API ERROR: ${1} ${2} → HTTP ${code}: ${body}"
    echo ""
  fi
}

_json_build() {
  # Build a JSON object from key=value pairs using jq if available, else python3.
  # Usage: _json_build key1=val1 key2=val2 ...
  # Values that look like numbers or booleans are kept as-is; others become strings.
  if command -v jq &>/dev/null; then
    local args=()
    local filter="{"
    local first=true
    for pair in "$@"; do
      local key="${pair%%=*}"
      local val="${pair#*=}"
      if $first; then first=false; else filter+=","; fi
      filter+="\"${key}\": \$${key}"
      args+=(--arg "$key" "$val")
    done
    filter+="}"
    jq -n "${args[@]}" "$filter"
  else
    python3 -c "
import json, sys
d = {}
for arg in sys.argv[1:]:
    k, _, v = arg.partition('=')
    d[k] = v
print(json.dumps(d))
" "$@"
  fi
}

# Validate API response contains required fields
# Usage: _validate_api_response RESPONSE_JSON FIELD1 FIELD2 ...
# Returns 0 if all fields present and non-empty, 1 otherwise
_validate_api_response() {
  local resp="$1"
  shift
  local missing=()

  for field in "$@"; do
    local val
    val=$(echo "$resp" | _jq ".${field}" 2>/dev/null || echo "")
    if [[ -z "$val" || "$val" == "null" ]]; then
      missing+=("$field")
    fi
  done

  if [[ ${#missing[@]} -gt 0 ]]; then
    _warn "API response missing required field(s): ${missing[*]}"
    return 1
  fi
  return 0
}

_jq() {
  # Portable jq: use python if jq not installed
  # Always returns raw strings (no surrounding quotes)
  # Supports: .field, .field.sub, .[] | .field
  if command -v jq &>/dev/null; then
    jq -r "$@"
  else
    python3 -c "
import sys, json
data = json.load(sys.stdin)
path = sys.argv[1] if len(sys.argv) > 1 else '.'

# Handle '.[] | .field' pattern
if '.[]' in path:
    parts = path.split('|')
    field = parts[1].strip().lstrip('.') if len(parts) > 1 else ''
    items = data if isinstance(data, list) else list(data.values()) if isinstance(data, dict) else []
    for item in items:
        if field and isinstance(item, dict):
            v = item.get(field, '')
            print(v if isinstance(v, str) else json.dumps(v))
        else:
            print(item if isinstance(item, str) else json.dumps(item))
else:
    # Simple .field or .field.subfield extraction
    parts = [p for p in path.split('.') if p]
    for p in parts:
        if isinstance(data, dict):
            data = data.get(p, '')
        elif isinstance(data, list) and p.isdigit():
            data = data[int(p)] if int(p) < len(data) else ''
        else:
            data = ''
            break
    if isinstance(data, str):
        print(data)
    else:
        print(json.dumps(data))
" "$@"
  fi
}

# ── Precondition checks ─────────────────────────────────────────────────────

_check_prerequisites() {
  _hdr "Prerequisites"

  # Check proxym server
  _step "Checking proxym server at ${PROXYM_URL}..."
  if curl -sf "${PROXYM_URL}/health" >/dev/null 2>&1; then
    _ok "Proxym server is running"
  else
    _fail "Proxym server not reachable at ${PROXYM_URL}"
    echo "    Start it with: proxym serve"
    echo "    Or set PROXYM_URL=http://host:port"
    exit 1
  fi

  # Check Node.js
  _step "Checking Node.js..."
  if command -v node &>/dev/null; then
    local node_version
    node_version=$(node --version)
    _ok "Node.js ${node_version}"
  else
    _warn "Node.js not found — npm install/build/test steps will be skipped"
    _info "Install: https://nodejs.org/ or nvm install 20"
  fi

  # Check npm
  if command -v npm &>/dev/null; then
    _ok "npm $(npm --version)"
  fi

  # Check selected tool
  _step "Checking tool '${TOOL}'..."
  if $CLI tools show "$TOOL" >/dev/null 2>&1; then
    _ok "Tool '${TOOL}' available in proxym"
  else
    _warn "Tool '${TOOL}' not found — will use LLM fallback"
  fi
}

# ── Phase 1: Create project on disk ─────────────────────────────────────────

_write_package_json() {
  cp "${TEMPLATES_DIR}/package.json" "${PROJECT_DIR}/package.json"
  _ok "package.json"
}

_write_tsconfig() {
  cp "${TEMPLATES_DIR}/tsconfig.json" "${PROJECT_DIR}/tsconfig.json"
  _ok "tsconfig.json"
}

_write_vitest_config() {
  cp "${TEMPLATES_DIR}/vitest.config.ts" "${PROJECT_DIR}/vitest.config.ts"
  _ok "vitest.config.ts"
}

_write_source_stubs() {
  # Copy all source stubs from templates
  cp "${TEMPLATES_DIR}/types.ts" "${PROJECT_DIR}/src/types.ts"
  _ok "src/types.ts"
  
  cp "${TEMPLATES_DIR}/store.ts" "${PROJECT_DIR}/src/store.ts"
  _ok "src/store.ts (stub)"
  
  cp "${TEMPLATES_DIR}/commands.ts" "${PROJECT_DIR}/src/commands.ts"
  _ok "src/commands.ts (stub)"
  
  cp "${TEMPLATES_DIR}/format.ts" "${PROJECT_DIR}/src/format.ts"
  _ok "src/format.ts (stub)"
  
  cp "${TEMPLATES_DIR}/index.ts" "${PROJECT_DIR}/src/index.ts"
  _ok "src/index.ts (stub)"
}

_write_test_stubs() {
  # Copy test stubs from templates
  cp "${TEMPLATES_DIR}/store.test.ts" "${PROJECT_DIR}/tests/store.test.ts"
  _ok "tests/store.test.ts (stub)"
  
  cp "${TEMPLATES_DIR}/commands.test.ts" "${PROJECT_DIR}/tests/commands.test.ts"
  _ok "tests/commands.test.ts (stub)"
}

_write_readme() {
  cp "${TEMPLATES_DIR}/README.md" "${PROJECT_DIR}/README.md"
  _ok "README.md"
}

_init_git() {
  # Copy spec into project
  cp "${SCRIPT_DIR}/spec.md" "${PROJECT_DIR}/spec.md" 2>/dev/null || true
  _ok "spec.md copied"

  # .gitignore
  cp "${TEMPLATES_DIR}/.gitignore" "${PROJECT_DIR}/.gitignore"
  _ok ".gitignore"

  # Initialize git
  if command -v git &>/dev/null && [[ ! -d "${PROJECT_DIR}/.git" ]]; then
    (cd "${PROJECT_DIR}" && git init -q && git add -A && git commit -q -m "Initial scaffold (proxym)" 2>/dev/null) || true
    _ok "Git repository initialized"
  fi
}

_create_project_scaffold() {
  _hdr "Phase 1 · Create Project on Disk"
  _step "Project directory: ${PROJECT_DIR}"

  if [[ -f "${PROJECT_DIR}/package.json" ]]; then
    _warn "Project already exists at ${PROJECT_DIR}"
    _info "Using existing project (delete to start fresh)"
    return 0
  fi

  if $DRY_RUN; then
    _info "[dry-run] Would create project at ${PROJECT_DIR}"
    return 0
  fi

  mkdir -p "${PROJECT_DIR}"/{src,tests,.proxym}
  _ok "Created directory structure"

  # Write all configuration files
  _write_package_json
  _write_tsconfig
  _write_vitest_config
  _write_source_stubs
  _write_test_stubs
  _write_readme
  _init_git

  _log "Phase 1 complete: scaffold at ${PROJECT_DIR}"
}

# ── Phase 2: Register in proxym ──────────────────────────────────────────────

_register_project() {
  _hdr "Phase 2 · Register Project in Proxym"

  if $DRY_RUN; then
    _info "[dry-run] Would register ${PROJECT_DIR} as 'todo-app-ts'"
    return 0
  fi

  # Create environment pointing to the project directory
  _step "Creating environment..."
  local env_body
  env_body=$(python3 -c "
import json, sys
print(json.dumps({
    'name': 'todo-app-ts-local',
    'kind': 'local_path',
    'path': sys.argv[1],
    'notes': 'Local development environment for todo-app-ts example',
}))
" "$PROJECT_DIR")

  local env_resp
  env_resp=$(_api POST "$API_ENVIRONMENTS" "$env_body") || true

  if [[ -n "$env_resp" ]]; then
    if _validate_api_response "$env_resp" "id" "name"; then
      ENV_ID=$(echo "$env_resp" | _jq .id)
      _ok "Environment created: ${ENV_ID}"
      _log "Environment: ${ENV_ID}"
    else
      _warn "Environment creation response invalid"
      _log "Environment response invalid: ${env_resp}"
    fi
  else
    _warn "Could not create environment (may already exist)"
  fi

  # Assign tool to environment
  if [[ -n "$ENV_ID" && "$ENV_ID" != "null" ]]; then
    _step "Assigning tool '${TOOL}' to environment..."
    local assign_body
    assign_body=$(python3 -c "
import json, sys
print(json.dumps({'tool': sys.argv[1], 'environment_id': sys.argv[2], 'instance_name': 'host'}))
" "$TOOL" "$ENV_ID")
    _api POST "$API_ENVIRONMENTS_ASSIGN" "$assign_body" >/dev/null 2>&1 || true
    _ok "Tool '${TOOL}' assigned to environment"
  fi

  # Register the project
  _step "Registering project..."
  local proj_body
  proj_body=$(python3 -c "
import json, sys
env_id = sys.argv[3]
print(json.dumps({
    'name': 'todo-app-ts',
    'path': sys.argv[1],
    'default_tool': sys.argv[2],
    'description': 'TypeScript CLI TODO app — proxym example project',
    'tool_names': [sys.argv[2], 'human'],
    'environment_ids': [env_id] if env_id else [],
}))
" "$PROJECT_DIR" "$TOOL" "${ENV_ID:-}")

  local proj_resp
  proj_resp=$(_api POST "$API_PROJECTS" "$proj_body") || true

  if [[ -n "$proj_resp" ]]; then
    if _validate_api_response "$proj_resp" "id" "name"; then
      PROJECT_ID=$(echo "$proj_resp" | _jq .id)
      _ok "Project registered: ${PROJECT_ID} (todo-app-ts)"
      _log "Project: ${PROJECT_ID}"
    else
      _warn "Project creation returned invalid response"
      _log "Project response invalid: ${proj_resp}"
      PROJECT_ID=""
    fi
  fi

  # Fallback: look up existing project by name
  if [[ -z "$PROJECT_ID" ]]; then
    # May already exist — try to find it
    _warn "Project creation failed or already exists — looking up by name..."
    PROJECT_ID=$(_api GET "$API_PROJECTS" | python3 -c "
import sys, json
data = json.load(sys.stdin)
projects = data if isinstance(data, list) else data.get('projects', [])
for p in projects:
    if p.get('name') == 'todo-app-ts':
        print(p['id']); break
" 2>/dev/null || echo "")
    [[ -n "$PROJECT_ID" ]] && _ok "Found existing project: ${PROJECT_ID}"
  fi

  # Refresh the project metadata if we have a project id.
  if [[ -n "$PROJECT_ID" ]]; then
    local project_update_body project_update_resp
    project_update_body=$(python3 -c "
import json, sys
print(json.dumps({
    'name': 'todo-app-ts',
    'default_tool': sys.argv[1],
    'description': 'TypeScript CLI TODO app — proxym example project',
    'tool_names': [sys.argv[1], 'human'],
    'environment_ids': [sys.argv[2]] if sys.argv[2] else [],
}, ensure_ascii=False))
" "$TOOL" "${ENV_ID:-}")
    project_update_resp=$(_api PUT "$API_PROJECTS/$PROJECT_ID" "$project_update_body") || true
    if [[ -n "$project_update_resp" ]] && _validate_api_response "$project_update_resp" "id" "name"; then
      _ok "Project metadata refreshed: ${PROJECT_ID}"
    fi
  fi

  # Save metadata locally (use python to guarantee valid JSON)
  python3 -c "
import json, sys
print(json.dumps({
    'project_id': sys.argv[1],
    'environment_id': sys.argv[2],
    'proxym_url': sys.argv[3],
    'tool': sys.argv[4],
    'created_at': sys.argv[5],
    'project_dir': sys.argv[6],
}, indent=2))
" "${PROJECT_ID}" "${ENV_ID}" "${PROXYM_URL}" "${TOOL}" "$(_date_iso)" "${PROJECT_DIR}" \
    > "${PROJECT_DIR}/.proxym/metadata.json"
  _ok "Metadata saved to .proxym/metadata.json"
}

# ── Phase 3: Create sprint & tickets ────────────────────────────────────────

# Ticket tracking arrays
declare -a TICKET_IDS=()
declare -a TICKET_NAMES=()
declare -a TICKET_TOOLS=()

_create_ticket() {
  # Usage: _create_ticket "title" "type" "priority" "tool" "description"
  local title="$1" type="$2" priority="$3" tool="$4" desc="${5:-}" context_files_json="${6:-[]}" depends_on_json="${7:-[]}"

  if $DRY_RUN; then
    _info "[dry-run] Would create: ${title} (${type}, ${priority}, ${tool})"
    TICKET_IDS+=("DRY-RUN")
    TICKET_NAMES+=("$title")
    TICKET_TOOLS+=("$tool")
    return 0
  fi

  # Build JSON safely via python to handle multiline descriptions, quotes, etc.
  local tool_json
  if [[ "$tool" == "none" ]]; then
    tool_json="null"
  else
    tool_json="\"${tool}\""
  fi

  local body
  body=$(python3 -c "
import json, sys
context_files = json.loads(sys.argv[8]) if len(sys.argv) > 8 and sys.argv[8] else []
depends_on = json.loads(sys.argv[9]) if len(sys.argv) > 9 and sys.argv[9] else []
print(json.dumps({
    'task': sys.argv[1],
    'description': sys.argv[2],
    'type': sys.argv[3],
    'priority': sys.argv[4],
    'project_id': sys.argv[5],
    'sprint_id': sys.argv[6],
    'tool': None if sys.argv[7] == 'none' else sys.argv[7],
    'context_files': context_files,
    'depends_on': depends_on,
}, ensure_ascii=False))
" "$title" "$desc" "$type" "$priority" "${PROJECT_ID}" "${SPRINT_ID}" "$tool" "$context_files_json" "$depends_on_json")

  local resp
  resp=$(_api POST "$API_TICKETS" "$body") || true
  local tid
  tid=$(echo "$resp" | _jq .id)

  if [[ -n "$tid" && "$tid" != "null" && "$tid" != "" ]]; then
    if ! _validate_api_response "$resp" "id" "title" "status"; then
      _warn "Ticket response was missing required fields; continuing with id ${tid}"
      TICKET_TOOLS+=("$tool")
    fi
  else
    _fail "  Failed to create: ${title}"
    _log "FAIL ticket: ${title}"
    TICKET_IDS+=("FAILED")
    TICKET_NAMES+=("$title")
    TICKET_TOOLS+=("$tool")
  fi
}

_create_sprint_and_tickets() {
  _hdr "Phase 3 · Create Sprint & Tickets"

  # ── Idempotency: check if a previous run already created tickets ──
  local manifest="${PROJECT_DIR}/.proxym/tickets.manifest"
  if [[ -f "$manifest" ]] && ! $CLEAN; then
    local existing_count
    existing_count=$(grep -c '^TKT-' "$manifest" 2>/dev/null || echo 0)
    if [[ "$existing_count" -gt 0 ]]; then
      _warn "Found existing manifest with ${existing_count} tickets (from previous run)"
      _info "Use --clean to start fresh, or continuing with existing tickets"
      # Load existing ticket IDs from manifest
      while IFS=$'\t ' read -r tid ttool tname; do
        [[ "$tid" == \#* || -z "$tid" ]] && continue
        TICKET_IDS+=("$tid")
        TICKET_TOOLS+=("$ttool")
        TICKET_NAMES+=("$tname")
      done < "$manifest"
      _ok "Loaded ${#TICKET_IDS[@]} existing tickets from manifest"
      return 0
    fi
  fi

  if $DRY_RUN; then
    _info "[dry-run] Would create sprint and tickets"
  fi

  # Create sprint
  _step "Creating sprint..."
  if ! $DRY_RUN; then
    local sprint_resp
    sprint_resp=$($CLI sprint create \
      --name "TODO App MVP" \
      --goal "Generate a fully working TypeScript TODO CLI app" 2>&1) || true
    SPRINT_ID=$(echo "$sprint_resp" | grep -oE 'SPR-[A-F0-9]+' | head -1)
    if [[ -n "$SPRINT_ID" ]]; then
      _ok "Sprint created: ${SPRINT_ID}"
      _log "Sprint: ${SPRINT_ID}"
    else
      _warn "Sprint creation returned: ${sprint_resp}"
      SPRINT_ID=""
    fi
  else
    _info "[dry-run] Would create sprint 'TODO App MVP'"
  fi

  # ── Ticket definitions (loaded from tickets.json) ──
  # Each ticket maps to a specific piece of the TODO app.
  # The AI tool receives the ticket description + spec.md as context.
  _step "Creating tickets from ${SCRIPT_DIR}/tickets.json..."

  local tickets_file="${SCRIPT_DIR}/tickets.json"
  if [[ ! -f "$tickets_file" ]]; then
    _fail "tickets.json not found at ${tickets_file}"
    return 1
  fi

  local spec_context
  spec_context="Refer to spec.md in the project root for the full specification."

  # Read tickets from JSON and create each one
  local ticket_count
  ticket_count=$(python3 -c "import json; print(len(json.load(open('${tickets_file}'))))")

  for idx in $(seq 0 $((ticket_count - 1))); do
    local t_title t_type t_priority t_tool t_desc t_context_files t_depends_on
    eval "$(python3 -c "
import json, sys, shlex
tickets = json.load(open(sys.argv[1]))
t = tickets[int(sys.argv[2])]
tool = t['tool'].replace('__TOOL__', sys.argv[3])
desc = t['description'].replace('__SPEC_CONTEXT__', sys.argv[4])
print(f't_title={shlex.quote(t[\"title\"])}')
print(f't_type={shlex.quote(t[\"type\"])}')
print(f't_priority={shlex.quote(t[\"priority\"])}')
print(f't_tool={shlex.quote(tool)}')
print(f't_desc={shlex.quote(desc)}')
print(f't_context_files={shlex.quote(json.dumps(t.get(\"context_files\", []), ensure_ascii=False))}')
print(f't_depends_on={shlex.quote(json.dumps(t.get(\"depends_on\", []), ensure_ascii=False))}')
" "$tickets_file" "$idx" "$TOOL" "$spec_context")"
    _create_ticket "$t_title" "$t_type" "$t_priority" "$t_tool" "$t_desc" "$t_context_files" "$t_depends_on"
  done

  echo ""
  _ok "Created ${#TICKET_IDS[@]} tickets"

  # Save ticket manifest
  if ! $DRY_RUN; then
    {
      echo "# Ticket Manifest — generated $(_date_iso)"
      echo "# Sprint: ${SPRINT_ID}"
      echo "# Project: ${PROJECT_ID} (${PROJECT_DIR})"
      echo "#"
      for i in "${!TICKET_IDS[@]}"; do
        echo "${TICKET_IDS[$i]}  ${TICKET_TOOLS[$i]}  ${TICKET_NAMES[$i]}"
      done
    } > "${PROJECT_DIR}/.proxym/tickets.manifest"
    _ok "Manifest saved to .proxym/tickets.manifest"
  fi
}

# ── Phase 4: Dispatch & Execute ──────────────────────────────────────────────

_wait_for_job() {
  # Wait for a job to complete, return 0 if success, 1 if failed
  local job_id="$1" ticket_id="$2" max_wait="${3:-300}"
  local elapsed=0 poll=5

  while (( elapsed < max_wait )); do
    local status
    status=$(_api GET "$API_TOOLS_JOBS/${job_id}" | _jq .status 2>/dev/null || echo "unknown")

    case "$status" in
      done)
        return 0
        ;;
      failed)
        return 1
        ;;
      cancelled)
        return 2
        ;;
    esac

    sleep "$poll"
    elapsed=$((elapsed + poll))
    printf "\r  ${DIM}  ⏳ %s — %ds / %ds${RESET}" "$status" "$elapsed" "$max_wait"
  done

  echo ""
  _warn "Timeout waiting for job ${job_id} (${max_wait}s)"
  return 3
}

_dispatch_ticket() {
  local ticket_id="$1" ticket_name="$2" tool="$3"

  if [[ "$tool" == "none" || "$tool" == "human" ]]; then
    _info "  Skipping dispatch for '${ticket_name}' (tool=${tool})"
    return 0
  fi

  if [[ "$ticket_id" == "FAILED" || "$ticket_id" == "DRY-RUN" ]]; then
    return 1
  fi

  _step "Dispatching: ${ticket_name}"
  _info "  Ticket: ${ticket_id} → Tool: ${tool}"

  local dispatch_body
  dispatch_body=$(python3 -c "
import json, sys
d = {'ticket_id': sys.argv[1], 'tool': sys.argv[2], 'mode': 'code', 'project_dir': sys.argv[3]}
model = sys.argv[4] if len(sys.argv) > 4 and sys.argv[4] else ''
if model:
    d['model'] = model
print(json.dumps(d))
" "$ticket_id" "$tool" "$PROJECT_DIR" "${MODEL:-}")

  local resp
  resp=$(_api POST "$API_TOOLS_DISPATCH" "$dispatch_body") || true

  local job_id
  job_id=$(echo "$resp" | python3 -c "
import sys, json
d = json.load(sys.stdin)
j = d.get('job', {})
print(j.get('id', j.get('job_id', '')))
" 2>/dev/null || echo "")

  if [[ -z "$job_id" ]]; then
    _fail "  Dispatch failed for ${ticket_id}"
    _log "DISPATCH FAIL: ${ticket_id} — ${resp}"
    _create_human_ticket "Dispatch failed: ${ticket_name}" \
      "Automatic dispatch of ticket ${ticket_id} failed. Response: ${resp}. Please investigate and complete manually."
    return 1
  fi

  _ok "  Job queued: ${job_id}"
  _log "Job: ${job_id} for ${ticket_id}"

  # Wait for completion
  if _wait_for_job "$job_id" "$ticket_id" 300; then
    echo ""
    _ok "  Job ${job_id} completed successfully"
    _log "Job ${job_id} DONE"
    return 0
  else
    echo ""
    local final_status
    final_status=$(_api GET "$API_TOOLS_JOBS/${job_id}" | _jq .status 2>/dev/null || echo "unknown")
    _fail "  Job ${job_id} ended with status: ${final_status}"
    _log "Job ${job_id} FAILED: ${final_status}"

    # Get error details
    local error_detail
    error_detail=$(_api GET "$API_TOOLS_JOBS/${job_id}" | python3 -c "
import sys, json
d = json.load(sys.stdin)
err = d.get('error', '') or ''
result = d.get('result', {}) or {}
stderr = result.get('stderr', '') or ''
print((err or stderr)[:500])
" 2>/dev/null || echo "unknown error")

    _create_human_ticket \
      "Fix failed: ${ticket_name}" \
      "Job ${job_id} for ticket ${ticket_id} failed.
Tool: ${tool}
Error: ${error_detail}
Project: ${PROJECT_DIR}
Please fix manually and update the ticket status."
    return 1
  fi
}

_create_human_ticket() {
  local title="$1" description="$2"
  _warn "  Creating human ticket: ${title}"
  local body
  body=$(python3 -c "
import json, sys
print(json.dumps({
    'task': sys.argv[1],
    'description': sys.argv[2],
    'type': 'task',
    'priority': 'high',
    'project_id': sys.argv[3],
    'sprint_id': sys.argv[4],
    'tool': 'human',
}, ensure_ascii=False))
" "$title" "$description" "${PROJECT_ID}" "${SPRINT_ID}")
  local resp
  resp=$(_api POST "$API_TICKETS" "$body") || true
  local htid
  htid=$(echo "$resp" | _jq .id 2>/dev/null || echo "?")
  _info "  Human ticket: ${htid}"
  _log "Human ticket: ${htid} — ${title}"
}

_dispatch_all() {
  _hdr "Phase 4 · Dispatch Tickets to AI Tools"

  if $SKIP_DISPATCH; then
    _info "Skipping dispatch (--skip-dispatch)"
    return 0
  fi

  if $DRY_RUN; then
    _info "[dry-run] Would dispatch ${#TICKET_IDS[@]} tickets"
    for i in "${!TICKET_IDS[@]}"; do
      _info "  → ${TICKET_NAMES[$i]} (${TICKET_TOOLS[$i]})"
    done
    return 0
  fi

  local dispatched=0 failed=0

  for i in "${!TICKET_IDS[@]}"; do
    local tid="${TICKET_IDS[$i]}"
    local tname="${TICKET_NAMES[$i]}"
    local ttool="${TICKET_TOOLS[$i]}"

    if _dispatch_ticket "$tid" "$tname" "$ttool"; then
      dispatched=$((dispatched + 1))
    else
      failed=$((failed + 1))
    fi
    echo ""
  done

  _ok "Dispatch complete: ${dispatched} succeeded, ${failed} failed"
  _log "Dispatch: ${dispatched} ok, ${failed} fail"
}

# ── Phase 5: Build & Verify ─────────────────────────────────────────────────

_verify_npm_install() {
  _step "Running npm install..."
  if (cd "${PROJECT_DIR}" && npm install --no-fund --no-audit 2>&1 | tail -3); then
    _ok "npm install succeeded"
    return 0
  else
    _fail "npm install failed"
    return 1
  fi
}

_verify_typecheck() {
  _step "Running typecheck..."
  local tsc_output
  if tsc_output=$(cd "${PROJECT_DIR}" && npx tsc --noEmit 2>&1); then
    _ok "TypeScript compilation: no errors"
    return 0
  else
    _warn "TypeScript errors found:"
    echo "$tsc_output" | head -20 | sed 's/^/    /'
    _create_human_ticket \
      "Fix TypeScript compilation errors in todo-app-ts" \
      "tsc --noEmit reported errors:
$(echo "$tsc_output" | head -30)
Project: ${PROJECT_DIR}"
    return 1
  fi
}

_verify_build() {
  _step "Running npm run build..."
  local build_output
  if build_output=$(cd "${PROJECT_DIR}" && npm run build 2>&1); then
    _ok "Build succeeded"
    return 0
  else
    _warn "Build failed:"
    echo "$build_output" | tail -10 | sed 's/^/    /'
    _create_human_ticket \
      "Fix build errors in todo-app-ts" \
      "npm run build failed:
$(echo "$build_output" | tail -20)
Project: ${PROJECT_DIR}"
    return 1
  fi
}

_verify_tests() {
  _step "Running npm test..."
  local test_output
  if test_output=$(cd "${PROJECT_DIR}" && npm test 2>&1); then
    _ok "Tests passed"
    echo "$test_output" | grep -E "(Tests|✓|✗|PASS|FAIL)" | head -10 | sed 's/^/    /'
    return 0
  else
    _warn "Tests failed:"
    echo "$test_output" | tail -15 | sed 's/^/    /'
    _create_human_ticket \
      "Fix failing tests in todo-app-ts" \
      "npm test failed:
$(echo "$test_output" | tail -20)
Project: ${PROJECT_DIR}"
    return 1
  fi
}

_verify_smoke() {
  if [[ ! -f "${PROJECT_DIR}/dist/index.js" ]]; then
    _warn "dist/index.js not found — skipping smoke test"
    return 1
  fi

  _step "Running smoke test..."
  local verify_ok=true

  # Add a task
  local add_output
  if add_output=$(cd "${PROJECT_DIR}" && node dist/index.js add "Smoke test task" --priority high 2>&1); then
    _ok "  add: OK"
  else
    _warn "  add: failed — ${add_output}"
    verify_ok=false
  fi

  # List tasks
  local list_output
  if list_output=$(cd "${PROJECT_DIR}" && node dist/index.js list 2>&1); then
    _ok "  list: OK"
    echo "$list_output" | head -5 | sed 's/^/      /'
  else
    _warn "  list: failed"
    verify_ok=false
  fi

  # Stats
  local stats_output
  if stats_output=$(cd "${PROJECT_DIR}" && node dist/index.js stats 2>&1); then
    _ok "  stats: OK"
  else
    _warn "  stats: failed"
    verify_ok=false
  fi

  $verify_ok
}

_verify_project() {
  _hdr "Phase 5 · Build & Verify"

  if $DRY_RUN; then
    _info "[dry-run] Would run npm install, build, test"
    return 0
  fi

  if ! command -v node &>/dev/null; then
    _warn "Node.js not installed — skipping build verification"
    _create_human_ticket \
      "Install Node.js and verify todo-app-ts build" \
      "Node.js is not installed. Please:
1. Install Node.js >= 18
2. cd ${PROJECT_DIR}
3. npm install
4. npm run build
5. npm test
6. Update ticket status when done."
    return 0
  fi

  local verify_ok=true

  # Run verification steps
  _verify_npm_install || verify_ok=false
  _verify_typecheck || verify_ok=false
  _verify_build || verify_ok=false
  _verify_tests || verify_ok=false
  _verify_smoke || verify_ok=false

  if $verify_ok; then
    _ok "All verifications passed! ✨"
  else
    _warn "Some verifications failed — human tickets created for manual fixes"
  fi

  _log "Verify: ok=${verify_ok}"
}

# ── Phase 6: Save report ────────────────────────────────────────────────────

_save_report() {
  _hdr "Phase 6 · Save Report"

  local report_file="${PROJECT_DIR}/.proxym/report.md"

  cat > "$report_file" << REPORT
# Proxym Run Report

- **Date**: $(_date_iso)
- **Project**: todo-app-ts
- **Directory**: ${PROJECT_DIR}
- **Proxym URL**: ${PROXYM_URL}
- **Tool**: ${TOOL}
- **Sprint**: ${SPRINT_ID}
- **Project ID**: ${PROJECT_ID}
- **Environment ID**: ${ENV_ID}

## Tickets Created

| # | Ticket ID | Tool | Title |
|---|-----------|------|-------|
$(for i in "${!TICKET_IDS[@]}"; do
echo "| $((i+1)) | ${TICKET_IDS[$i]} | ${TICKET_TOOLS[$i]} | ${TICKET_NAMES[$i]} |"
done)

## How to Inspect (even after proxym stops)

The project lives at \`${PROJECT_DIR}\` and is fully self-contained:

\`\`\`bash
# The project is a regular Node.js project on disk:
cd ${PROJECT_DIR}
cat package.json
ls src/ tests/

# Build and run without proxym:
npm install
npm run build
node dist/index.js add "Hello from the generated app"
node dist/index.js list

# Run tests:
npm test

# Proxym metadata (ticket IDs, environment, etc.):
cat .proxym/metadata.json
cat .proxym/tickets.manifest
cat .proxym/run.log
\`\`\`

## Reproduce This Run

\`\`\`bash
# From the proxym repo root:
bash examples/todo-app-ts/run.sh \\
  --project-dir ${PROJECT_DIR} \\
  --tool ${TOOL} \\
  --proxy ${PROXYM_URL}
\`\`\`
REPORT

  _ok "Report saved to ${report_file}"

  # Append to log
  _log "=== RUN COMPLETE ==="
}

# ── Phase 7: Summary ────────────────────────────────────────────────────────

_print_summary() {
  echo ""
  echo -e "${BOLD}╔══════════════════════════════════════════════════════════════╗${RESET}"
  echo -e "${BOLD}║              Proxym Example — Run Complete                  ║${RESET}"
  echo -e "${BOLD}╠══════════════════════════════════════════════════════════════╣${RESET}"
  echo -e "${BOLD}║${RESET}  Project:     ${PROJECT_DIR}"
  echo -e "${BOLD}║${RESET}  Sprint:      ${SPRINT_ID:-n/a}"
  echo -e "${BOLD}║${RESET}  Tickets:     ${#TICKET_IDS[@]}"
  echo -e "${BOLD}║${RESET}  Tool:        ${TOOL}"
  echo -e "${BOLD}║${RESET}"
  echo -e "${BOLD}║${RESET}  ${GREEN}The project exists on disk and works without proxym.${RESET}"
  echo -e "${BOLD}║${RESET}"
  echo -e "${BOLD}║${RESET}  Next steps:"
  echo -e "${BOLD}║${RESET}    cd ${PROJECT_DIR}"
  echo -e "${BOLD}║${RESET}    npm install && npm run build && npm test"
  echo -e "${BOLD}║${RESET}    node dist/index.js add 'My first task'"
  echo -e "${BOLD}║${RESET}    node dist/index.js list"
  echo -e "${BOLD}║${RESET}"
  echo -e "${BOLD}║${RESET}  Proxym status:"
  echo -e "${BOLD}║${RESET}    proxym --proxy ${PROXYM_URL} ticket list"
  echo -e "${BOLD}║${RESET}    proxym --proxy ${PROXYM_URL} ticket board"
  echo -e "${BOLD}║${RESET}    proxym --proxy ${PROXYM_URL} tools jobs"
  echo -e "${BOLD}╚══════════════════════════════════════════════════════════════╝${RESET}"
  echo ""
}

# ── Main ─────────────────────────────────────────────────────────────────────

main() {
  echo -e "${BOLD}${CYAN}"
  echo "  ╔═══════════════════════════════════════════════════════════╗"
  echo "  ║  Proxym Example: TypeScript TODO App                     ║"
  echo "  ║  Automatic project generation via AI tool orchestration  ║"
  echo "  ╚═══════════════════════════════════════════════════════════╝"
  echo -e "${RESET}"

  # Handle --clean: remove existing project and start fresh
  if $CLEAN; then
    if [[ -d "${PROJECT_DIR}" ]]; then
      _warn "Removing existing project at ${PROJECT_DIR}..."
      rm -rf "${PROJECT_DIR}"
      _ok "Cleaned ${PROJECT_DIR}"
    fi
  fi

  # Initialize log
  mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true
  _log "=== RUN START: $(_date_iso) ==="
  _log "Config: TOOL=${TOOL} PROJECT_DIR=${PROJECT_DIR} PROXYM_URL=${PROXYM_URL}"
  _log "Flags: DRY_RUN=${DRY_RUN} SKIP_DISPATCH=${SKIP_DISPATCH} CLEAN=${CLEAN}"

  _check_prerequisites
  _create_project_scaffold
  _register_project
  _create_sprint_and_tickets
  _dispatch_all
  _verify_project
  _save_report
  _print_summary
}

main
