#!/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=""

# 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="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[@]:-0}"
    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
}

_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 ─────────────────────────────────────────

_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"

  # ── package.json ──
  cat > "${PROJECT_DIR}/package.json" << 'PACKAGE_JSON'
{
  "name": "todo-app-ts",
  "version": "0.1.0",
  "description": "CLI TODO list manager — generated by proxym AI orchestration",
  "main": "dist/index.js",
  "bin": {
    "todo": "dist/index.js"
  },
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "test": "vitest run",
    "test:watch": "vitest",
    "lint": "eslint src/",
    "typecheck": "tsc --noEmit",
    "clean": "rm -rf dist/"
  },
  "keywords": ["todo", "cli", "typescript", "proxym-example"],
  "license": "MIT",
  "devDependencies": {
    "typescript": "^5.4.0",
    "vitest": "^1.6.0",
    "@types/node": "^20.12.0"
  },
  "dependencies": {
    "commander": "^12.1.0"
  }
}
PACKAGE_JSON
  _ok "package.json"

  # ── tsconfig.json ──
  cat > "${PROJECT_DIR}/tsconfig.json" << 'TSCONFIG'
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "lib": ["ES2022"],
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "tests"]
}
TSCONFIG
  _ok "tsconfig.json"

  # ── vitest.config.ts ──
  cat > "${PROJECT_DIR}/vitest.config.ts" << 'VITEST_CONFIG'
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    environment: 'node',
    include: ['tests/**/*.test.ts'],
    coverage: {
      provider: 'v8',
      include: ['src/**/*.ts'],
    },
  },
});
VITEST_CONFIG
  _ok "vitest.config.ts"

  # ── src/types.ts — the data model ──
  cat > "${PROJECT_DIR}/src/types.ts" << 'TYPES_TS'
/**
 * Core data types for the TODO application.
 * Generated by proxym AI orchestration system.
 */

export type Priority = 'low' | 'medium' | 'high';

export interface Todo {
  id: string;
  title: string;
  description: string;
  done: boolean;
  priority: Priority;
  createdAt: string;
  updatedAt: string;
}

export interface TodoCreateInput {
  title: string;
  description?: string;
  priority?: Priority;
}

export interface TodoUpdateInput {
  title?: string;
  description?: string;
  priority?: Priority;
  done?: boolean;
}

export interface TodoStats {
  total: number;
  done: number;
  pending: number;
  byPriority: Record<Priority, number>;
}
TYPES_TS
  _ok "src/types.ts"

  # ── Placeholder files for AI to fill ──
  cat > "${PROJECT_DIR}/src/store.ts" << 'STORE_STUB'
/**
 * TODO Store — JSON file-based persistence.
 *
 * Responsibilities:
 *   - Load/save todos from/to a JSON file
 *   - CRUD operations (create, read, update, delete)
 *   - Atomic file writes (write to temp, rename)
 *   - Auto-create storage directory on first use
 *
 * Storage path: TODO_FILE env var or ~/.todo-app/todos.json
 *
 * @see spec.md for full requirements
 */
import { Todo, TodoCreateInput, TodoUpdateInput, TodoStats, Priority } from './types.js';

// TODO: Implement TodoStore class
// This file will be completed by an AI tool via proxym ticket dispatch.
STORE_STUB
  _ok "src/store.ts (stub)"

  cat > "${PROJECT_DIR}/src/commands.ts" << 'COMMANDS_STUB'
/**
 * CLI Command handlers — business logic for each CLI action.
 *
 * Each exported function corresponds to a CLI command:
 *   addCommand, listCommand, showCommand, doneCommand,
 *   undoneCommand, editCommand, removeCommand, statsCommand, clearCommand
 *
 * @see spec.md for full requirements
 */
import { TodoStore } from './store.js';

// TODO: Implement command handlers
// This file will be completed by an AI tool via proxym ticket dispatch.
COMMANDS_STUB
  _ok "src/commands.ts (stub)"

  cat > "${PROJECT_DIR}/src/format.ts" << 'FORMAT_STUB'
/**
 * Output formatting helpers — colored terminal output.
 *
 * Functions:
 *   formatTodoRow(todo)   — single-line table row
 *   formatTodoDetail(todo) — multi-line detail view
 *   formatStats(stats)    — statistics summary
 *   colorPriority(p)      — color code for priority
 *   shortId(id)           — first 8 chars of UUID
 *
 * @see spec.md for full requirements
 */
import { Todo, TodoStats, Priority } from './types.js';

// TODO: Implement formatting functions
// This file will be completed by an AI tool via proxym ticket dispatch.
FORMAT_STUB
  _ok "src/format.ts (stub)"

  cat > "${PROJECT_DIR}/src/index.ts" << 'INDEX_STUB'
/**
 * CLI entry point — Commander.js setup.
 *
 * Registers all commands: add, list, show, done, undone, edit, remove, stats, clear
 * Uses commander for argument parsing.
 *
 * @see spec.md for full requirements
 */

// TODO: Implement CLI entry point with Commander.js
// This file will be completed by an AI tool via proxym ticket dispatch.
INDEX_STUB
  _ok "src/index.ts (stub)"

  # ── Test stubs ──
  cat > "${PROJECT_DIR}/tests/store.test.ts" << 'STORE_TEST_STUB'
/**
 * Unit tests for TodoStore.
 *
 * Test cases:
 *   - Create a todo and verify fields
 *   - List todos (empty, non-empty)
 *   - Update todo fields
 *   - Mark as done/undone
 *   - Delete a todo
 *   - Stats calculation
 *   - File persistence (write and re-read)
 *   - Error handling (invalid ID, missing file)
 *
 * Use a temp directory for the JSON file in tests.
 */
import { describe, it, expect } from 'vitest';

// TODO: Implement store tests
// This file will be completed by an AI tool via proxym ticket dispatch.

describe('TodoStore', () => {
  it.todo('should be implemented by AI tool');
});
STORE_TEST_STUB
  _ok "tests/store.test.ts (stub)"

  cat > "${PROJECT_DIR}/tests/commands.test.ts" << 'CMD_TEST_STUB'
/**
 * Unit tests for command handlers.
 */
import { describe, it, expect } from 'vitest';

describe('Commands', () => {
  it.todo('should be implemented by AI tool');
});
CMD_TEST_STUB
  _ok "tests/commands.test.ts (stub)"

  # ── README ──
  cat > "${PROJECT_DIR}/README.md" << 'README_MD'
# TODO App (TypeScript CLI)

> This project was automatically generated and built by
> [proxym](https://github.com/wronai/proxy) AI tool orchestration.

## Quick Start

```bash
npm install
npm run build
node dist/index.js add "My first task" --priority high
node dist/index.js list
```

## Commands

| Command | Description |
|---------|-------------|
| `todo add <title>` | Create a new task |
| `todo list` | List all tasks |
| `todo show <id>` | Show task details |
| `todo done <id>` | Mark as complete |
| `todo remove <id>` | Delete a task |
| `todo stats` | Show statistics |

## Development

```bash
npm test          # run tests
npm run build     # compile TypeScript
npm run typecheck # type check without emit
```

## How It Was Built

This project was generated step-by-step using proxym:

1. Project scaffold created on disk
2. Registered in proxym as a managed project
3. Sprint created with implementation tickets
4. Each ticket dispatched to an AI coding tool
5. Results verified automatically after each step
6. Failed steps escalated to human via tickets

See `examples/todo-app-ts/` in the proxym repo for the full orchestration script.
README_MD
  _ok "README.md"

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

  # ── .gitignore ──
  cat > "${PROJECT_DIR}/.gitignore" << 'GITIGNORE'
node_modules/
dist/
*.js.map
.todo-app/
.proxym/
coverage/
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

  _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 "/environments" "$env_body") || true

  if [[ -n "$env_resp" ]]; then
    ENV_ID=$(echo "$env_resp" | _jq .id)
    _ok "Environment created: ${ENV_ID}"
    _log "Environment: ${ENV_ID}"
  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 "/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 "/projects" "$proj_body") || true

  if [[ -n "$proj_resp" ]]; then
    PROJECT_ID=$(echo "$proj_resp" | _jq .id)
    if [[ -n "$PROJECT_ID" && "$PROJECT_ID" != "null" && "$PROJECT_ID" != "" ]]; then
      _ok "Project registered: ${PROJECT_ID} (todo-app-ts)"
      _log "Project: ${PROJECT_ID}"
    else
      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 "/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

  # 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:-}"

  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
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],
}, ensure_ascii=False))
" "$title" "$desc" "$type" "$priority" "${PROJECT_ID}" "${SPRINT_ID}" "$tool")

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

  if [[ -n "$tid" && "$tid" != "null" && "$tid" != "" ]]; then
    _ok "  ${tid}  ${title}  → ${tool}"
    TICKET_IDS+=("$tid")
    TICKET_NAMES+=("$title")
    TICKET_TOOLS+=("$tool")
    _log "Ticket: ${tid} = ${title} (${tool})"
  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
    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)}')
" "$tickets_file" "$idx" "$TOOL" "$spec_context")"
    _create_ticket "$t_title" "$t_type" "$t_priority" "$t_tool" "$t_desc"
  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 "/tools/queue/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 "/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 "/tools/queue/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 "/tools/queue/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 "/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_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

  # 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"
  else
    _fail "npm install failed"
    verify_ok=false
  fi

  # TypeScript compile check
  _step "Running typecheck..."
  local tsc_output
  if tsc_output=$(cd "${PROJECT_DIR}" && npx tsc --noEmit 2>&1); then
    _ok "TypeScript compilation: no errors"
  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}"
    verify_ok=false
  fi

  # Build
  _step "Running npm run build..."
  local build_output
  if build_output=$(cd "${PROJECT_DIR}" && npm run build 2>&1); then
    _ok "Build succeeded"
  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}"
    verify_ok=false
  fi

  # 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/^/    /'
  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}"
    verify_ok=false
  fi

  # Functional smoke test
  if [[ -f "${PROJECT_DIR}/dist/index.js" ]]; then
    _step "Running smoke test..."

    # 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"
    fi
  else
    _warn "dist/index.js not found — skipping smoke test"
    verify_ok=false
  fi

  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
