#!/bin/sh
set -eu

DEFAULT_SLUG="planning-blockers-sprint"
FU_BASE_URL_EXPLICIT=0
if [ -n "${FU_BASE_URL:-}" ] || [ -n "${FUCR_BASE_URL:-}" ] || [ -n "${U_BASE_URL:-}" ]; then
  FU_BASE_URL_EXPLICIT=1
fi
if [ -z "${FU_BASE_URL:-}" ] && [ -n "${FUCR_BASE_URL:-}" ]; then
  FU_BASE_URL="$FUCR_BASE_URL"
fi
if [ -z "${FU_BASE_URL:-}" ] && [ -n "${U_BASE_URL:-}" ]; then
  printf "Using U_BASE_URL as FU_BASE_URL. If this was accidental, paste the command on one line or keep the backslash before the line break.\n" >&2
  FU_BASE_URL="$U_BASE_URL"
fi
FU_BASE_URL="${FU_BASE_URL:-https://www.futureunion.org.uk}"
FUCR_CACHE_DIR="${FUCR_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/future-union/fucr}"
FUCR_BIN_DIR="${FUCR_BIN_DIR:-$HOME/.local/bin}"
FUCR_WORKSPACE_ROOT="${FUCR_WORKSPACE_ROOT:-${FU_WORKSPACE_ROOT:-}}"
if [ -z "$FUCR_WORKSPACE_ROOT" ]; then
  if [ -d "$HOME/dev" ]; then
    FUCR_WORKSPACE_ROOT="$HOME/dev/future-union-mission"
  else
    FUCR_WORKSPACE_ROOT="$HOME/future-union-mission"
  fi
fi
FUCR_INSTALL="${FUCR_INSTALL:-1}"
FUCR_UPDATE="${FUCR_UPDATE:-0}"
FUCR_TUI_CHILD="${FUCR_TUI_CHILD:-0}"
FU_FORCE="${FU_FORCE:-0}"
FU_AGENT="${FU_AGENT:-auto}"
FU_START="${FU_START:-ask}"
FU_PICK="${FU_PICK:-1}"
FU_TUI="${FU_TUI:-ratatui}"
FU_YES="${FU_YES:-0}"
FU_BOOTSTRAP="${FU_BOOTSTRAP:-1}"
FU_DRY_RUN="${FU_DRY_RUN:-0}"
FU_CONTRIBUTOR_NAME="${FU_CONTRIBUTOR_NAME:-}"
FU_CONTRIBUTOR_HANDLE="${FU_CONTRIBUTOR_HANDLE:-}"
FU_CONTACT="${FU_CONTACT:-}"
FU_ANONYMOUS="${FU_ANONYMOUS:-no}"
FU_IDEA_FILE="${FU_IDEA_FILE:-}"
picker_dir=""
tmp_dir=""

trim_trailing_slashes() {
  printf "%s" "$1" | sed 's:/*$::'
}

fail() {
  printf "%s\n" "$1" >&2
  exit 1
}

mission_feed_valid() {
  candidate_url="$(trim_trailing_slashes "$1")"
  feed_tmp="$(mktemp "${TMPDIR:-/tmp}/future-union-feed.XXXXXX")"
  if ! curl -fsS "$candidate_url/api/mission-computer.json" -o "$feed_tmp" 2>/dev/null; then
    rm -f "$feed_tmp"
    return 1
  fi

  if command -v node >/dev/null 2>&1; then
    node -e '
const fs = require("node:fs");
const payload = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
if (!Array.isArray(payload.actions) || !Array.isArray(payload.missions)) {
  process.exit(1);
}
' "$feed_tmp" >/dev/null 2>&1
    feed_status="$?"
  else
    grep -q '"actions"' "$feed_tmp" && grep -q '"missions"' "$feed_tmp"
    feed_status="$?"
  fi
  rm -f "$feed_tmp"
  return "$feed_status"
}

resolve_base_url() {
  if mission_feed_valid "$base_url"; then
    return 0
  fi

  if [ "$FU_BASE_URL_EXPLICIT" = "1" ]; then
    printf "Warning: %s/api/mission-computer.json is not valid Future Union JSON.\n" "$base_url" >&2
    printf "If you are working locally, rerun with FU_BASE_URL=http://127.0.0.1:4392.\n" >&2
    return 0
  fi

  for local_candidate in http://127.0.0.1:4392 http://127.0.0.1:4391 http://127.0.0.1:4386; do
    if mission_feed_valid "$local_candidate"; then
      printf "Using local Future Union dev server: %s\n" "$local_candidate" >&2
      base_url="$local_candidate"
      FU_BASE_URL="$local_candidate"
      export FU_BASE_URL
      return 0
    fi
  done

  printf "Warning: %s/api/mission-computer.json is not valid Future Union JSON, and no local dev server was found.\n" "$base_url" >&2
}

resolve_runner_path() {
  if command -v fucr >/dev/null 2>&1; then
    command -v fucr
    return 0
  fi

  case "$0" in
    /*) printf "%s\n" "$0" ;;
    *) printf "%s/%s\n" "$(pwd)" "$0" ;;
  esac
}

install_fucr_command() {
  if [ "$FUCR_INSTALL" = "0" ]; then
    return 0
  fi

  case "$0" in
    /*) source_path="$0" ;;
    *) source_path="$(pwd)/$0" ;;
  esac

  if [ ! -f "$source_path" ]; then
    return 0
  fi

  mkdir -p "$FUCR_BIN_DIR"
  target="$FUCR_BIN_DIR/fucr"
  if [ ! -f "$target" ] || ! cmp -s "$source_path" "$target"; then
    cp "$source_path" "$target"
    chmod +x "$target"
    printf "Installed Future Union Control Room command: %s\n" "$target" >&2
  fi

  case ":$PATH:" in
    *":$FUCR_BIN_DIR:"*) ;;
    *) printf "Add %s to PATH to run \`fucr\` from anywhere.\n" "$FUCR_BIN_DIR" >&2 ;;
  esac
}

run_fucr_doctor() {
  printf "# Future Union Control Room Doctor\n\n"
  printf "Base URL: %s\n" "$base_url"
  printf "Cache: %s\n" "$FUCR_CACHE_DIR"
  printf "Command: %s\n" "$(resolve_runner_path)"
  [ -f "$FUCR_CACHE_DIR/version.txt" ] && printf "Cached version: %s\n" "$(cat "$FUCR_CACHE_DIR/version.txt")"
  [ -f "$FUCR_CACHE_DIR/pi-autoresearch-setup" ] && printf "Pi autoresearch setup: %s\n" "$(cat "$FUCR_CACHE_DIR/pi-autoresearch-setup")"
  printf "\n"
  for tool in curl cargo git node npm python3 pi codex claude rad rad-experiment; do
    if command -v "$tool" >/dev/null 2>&1; then
      printf "OK   %-7s %s\n" "$tool" "$(command -v "$tool")"
    else
      printf "MISS %-7s\n" "$tool"
    fi
  done
  if command -v pi >/dev/null 2>&1; then
    if pi list 2>/dev/null | grep -Fq "pi-autoresearch"; then
      printf "OK   %-7s %s\n" "pi-ar" "pi-autoresearch installed"
    else
      printf "MISS %-7s %s\n" "pi-ar" "pi-autoresearch extension"
    fi
    if pi list 2>/dev/null | grep -Fq "pi-cc"; then
      printf "OK   %-7s %s\n" "pi-cc" "Community Computer extension installed (can publish to Community Computer)"
    else
      printf "INFO %-7s %s\n" "pi-cc" "not installed; FU publish lane works without it"
    fi
  fi
  if command -v rad >/dev/null 2>&1; then
    rad self >/dev/null 2>&1 && printf "OK   %-7s identity ready\n" "rad" || printf "WARN %-7s run rad auth before publishing\n" "rad"
    if rad node status 2>&1 | grep -Fq "Node is stopped"; then
      printf "WARN %-7s run rad node start before publishing\n" "node"
    else
      printf "OK   %-7s node running or reachable\n" "node"
    fi
  fi
  printf "\nRun \`fucr update\` to refresh the cached Ratatui control room.\n"
  printf "Run \`fucr sync all\` from your mission parent folder to update old workspace helpers without touching packet work.\n"
}

workspace_slug_from_dir() {
  workspace_path="$1"
  if command -v node >/dev/null 2>&1 && [ -f "$workspace_path/context/action.json" ]; then
    node -e '
const fs = require("node:fs");
const payload = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
process.stdout.write(payload.action?.slug || "");
' "$workspace_path/context/action.json" 2>/dev/null || true
  fi
}

workspace_slug() {
  workspace_path="$1"
  slug_value="$(workspace_slug_from_dir "$workspace_path")"
  if [ -n "$slug_value" ]; then
    printf "%s\n" "$slug_value"
    return 0
  fi
  basename "$workspace_path" | sed 's/^future-union-mission-//'
}

repair_fetch_file() {
  url="$1"
  destination="$2"
  if [ "$FU_DRY_RUN" = "1" ]; then
    printf "DRY RUN: would refresh %s from %s\n" "$destination" "$url"
    return 0
  fi
  mkdir -p "$(dirname "$destination")"
  printf "Refreshing %s\n" "$destination"
  if curl -fsS "$url" -o "$destination"; then
    return 0
  fi

  printf "WARN: could not refresh %s from %s. Existing local file was left alone if present.\n" "$destination" "$url" >&2
  return 0
}

write_action_context_from_feed() {
  feed_file="$1"
  action_slug="$2"
  destination="$3"
  command -v node >/dev/null 2>&1 || return 1
  node -e '
const fs = require("node:fs");
const [feedPath, slug, destination] = process.argv.slice(1);
const payload = JSON.parse(fs.readFileSync(feedPath, "utf8"));
const action = (payload.actions || []).find((item) => item.slug === slug);
if (!action) process.exit(2);
const missionSlug = action.parentMissionSlug || action.missionSlug;
const mission = (payload.missions || []).find((item) => item.slug === missionSlug) || null;
const body = {
  site: {
    name: payload.missionComputer?.title ? "Future Union" : "Future Union",
    url: payload.missionRunner?.siteUrl || "",
  },
  action,
  mission,
  missionComputer: payload.missionComputer || {},
  missionRunner: payload.missionRunner || {},
  agentPack: payload.agentPack || {},
  repairedFromMissionFeed: true,
};
fs.writeFileSync(destination, JSON.stringify(body, null, 2) + "\n");
' "$feed_file" "$action_slug" "$destination"
}

repair_action_context() {
  action_slug="$1"
  destination="$2"
  action_url="$base_url/api/actions/$action_slug.json"
  if [ "$FU_DRY_RUN" = "1" ]; then
    printf "DRY RUN: would refresh %s from %s\n" "$destination" "$action_url"
    return 0
  fi

  mkdir -p "$(dirname "$destination")"
  printf "Refreshing %s\n" "$destination"
  if curl -fsS "$action_url" -o "$destination"; then
    return 0
  fi

  feed_tmp="$(mktemp "${TMPDIR:-/tmp}/future-union-feed.XXXXXX")"
  if curl -fsS "$base_url/api/mission-computer.json" -o "$feed_tmp" \
    && write_action_context_from_feed "$feed_tmp" "$action_slug" "$destination"; then
    rm -f "$feed_tmp"
    printf "Rebuilt %s from /api/mission-computer.json because %s was unavailable.\n" "$destination" "$action_url"
    return 0
  fi

  rm -f "$feed_tmp"
  printf "WARN: could not rebuild action context for %s. Try FU_BASE_URL=https://www.futureunion.org.uk fucr sync %s\n" "$action_slug" "$destination" >&2
  return 0
}

write_repaired_runner_json() {
  workspace_path="$1"
  action_slug="$2"
  destination="$workspace_path/context/runner.json"
  if [ "$FU_DRY_RUN" = "1" ]; then
    printf "DRY RUN: would refresh %s\n" "$destination"
    return 0
  fi
  mkdir -p "$workspace_path/context"
  {
    printf "{\n"
    printf "  \"baseUrl\": \"%s\",\n" "$base_url"
    printf "  \"actionSlug\": \"%s\",\n" "$action_slug"
    printf "  \"generatedAt\": \"%s\",\n" "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
    printf "  \"runner\": \"fucr sync\"\n"
    printf "}\n"
  } > "$destination"
}

repair_workspace() {
  workspace_path="${1%/}"
  if [ ! -d "$workspace_path" ]; then
    fail "Workspace does not exist: $workspace_path"
  fi
  if [ ! -f "$workspace_path/.future-union-runner" ] && [ ! -f "$workspace_path/context/runner.json" ]; then
    fail "Refusing to sync $workspace_path because it is not a Future Union mission workspace."
  fi

  slug="$(workspace_slug "$workspace_path")"
  if [ -z "$slug" ]; then
    fail "Could not infer action slug for $workspace_path"
  fi

  printf "Syncing %s (%s)\n" "$workspace_path" "$slug"
  printf "Preserving packet, sources, contribution, credit, share, Radicle, and room notes.\n"

  repair_action_context "$slug" "$workspace_path/context/action.json"
  repair_fetch_file "$base_url/api/mission-computer.json" "$workspace_path/context/future-union-context.json"
  repair_fetch_file "$base_url/agent-pack/actions/$slug/mission.json" "$workspace_path/context/mission.json"
  repair_fetch_file "$base_url/agent-pack/context/safety-rules.md" "$workspace_path/context/safety-rules.md"
  repair_fetch_file "$base_url/agent-pack/context/review-policy.md" "$workspace_path/context/review-policy.md"
  repair_fetch_file "$base_url/agent-pack/AGENTS.md" "$workspace_path/AGENTS.md"
  repair_fetch_file "$base_url/agent-pack/RADICLE_NETWORK.md" "$workspace_path/RADICLE_NETWORK.md"
  repair_fetch_file "$base_url/agent-pack/bin/fu-mission" "$workspace_path/bin/fu-mission"
  repair_fetch_file "$base_url/agent-pack/actions/$slug/SKILL.md" "$workspace_path/.agents/skills/future-union/SKILL.md"
  write_repaired_runner_json "$workspace_path" "$slug"

  if [ "$FU_DRY_RUN" != "1" ]; then
    chmod +x "$workspace_path/bin/fu-mission"
    (
      cd "$workspace_path"
      FU_AGENT=auto FU_BASE_URL="$base_url" ./bin/fu-mission experiment-init >/dev/null 2>&1 || true
      FU_AGENT=auto FU_BASE_URL="$base_url" ./bin/fu-mission mark-state synced >/dev/null 2>&1 || true
    )
    printf "Synced %s. Helper version: %s\n" "$workspace_path" "$("$workspace_path/bin/fu-mission" version 2>/dev/null || printf unknown)"
  fi
}

repair_workspaces() {
  target="${1:-all}"
  if [ "$target" = "all" ]; then
    found=0
    for candidate in future-union-mission-*; do
      [ -d "$candidate" ] || continue
      found=1
      repair_workspace "$candidate"
    done
    if [ "$found" = "0" ]; then
      printf "No future-union-mission-* folders found under %s.\n" "$(pwd)"
    fi
    return 0
  fi

  repair_workspace "$target"
}

confirm_install() {
  label="$1"
  command_text="$2"

  if [ "$FU_DRY_RUN" = "1" ]; then
    printf "DRY RUN: would install %s with: %s\n" "$label" "$command_text" >&2
    return 1
  fi

  if [ "$FU_YES" = "1" ]; then
    return 0
  fi

  if [ -t 0 ]; then
    printf "Install %s now with %s? [y/N] " "$label" "$command_text" >&2
    read answer
    case "$answer" in
      y|Y|yes|YES) return 0 ;;
    esac
  fi

  printf "Skipped %s install. Continuing with available local tools.\n" "$label" >&2
  return 1
}

validate_agent() {
  case "$FU_AGENT" in
    py|pyresearch|pi-autoresearch) FU_AGENT="pi" ;;
    auto|pi|codex|claude|none) ;;
    *) fail "FU_AGENT must be auto, pi, codex, claude, or none." ;;
  esac
}

validate_start_mode() {
  case "$FU_START" in
    ask|auto|1|yes|true|0|no|false) ;;
    *) fail "FU_START must be ask, auto, 1, or 0." ;;
  esac
}

validate_tui_mode() {
  case "$FU_TUI" in
    0|no|false|1|yes|true|ratatui|python|textual|lite|node) ;;
    *) fail "FU_TUI must be 0, ratatui, lite, textual, or node." ;;
  esac
}

ensure_cargo() {
  if command -v cargo >/dev/null 2>&1; then
    return 0
  fi

  if [ "$FU_BOOTSTRAP" = "0" ]; then
    return 1
  fi

  install_command="curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal"
  if confirm_install "Rust toolchain" "$install_command"; then
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal >&2
    PATH="$HOME/.cargo/bin:$PATH"
    export PATH
  fi

  command -v cargo >/dev/null 2>&1
}

maybe_setup_pi_autoresearch() {
  if [ "${FUCR_SETUP:-1}" = "0" ]; then
    return 0
  fi
  if ! command -v pi >/dev/null 2>&1; then
    return 0
  fi

  marker="$FUCR_CACHE_DIR/pi-autoresearch-setup"
  if pi list 2>/dev/null | grep -Fq "pi-autoresearch"; then
    mkdir -p "$FUCR_CACHE_DIR"
    printf "installed\n" > "$marker"
    return 0
  fi

  mkdir -p "$FUCR_CACHE_DIR"
  if confirm_install "pi-autoresearch extension" "pi install https://github.com/davebcn87/pi-autoresearch"; then
    pi install https://github.com/davebcn87/pi-autoresearch >&2 || true
    printf "installed\n" > "$marker"
  else
    printf "skipped\n" > "$marker"
  fi
}

maybe_setup_community_computer_tools() {
  if [ "${FUCR_SETUP:-1}" = "0" ]; then
    return 0
  fi
  if [ "${FU_ENABLE_PI_CC:-0}" != "1" ]; then
    return 0
  fi

  missing=0
  command -v rad-experiment >/dev/null 2>&1 || missing=1
  if command -v pi >/dev/null 2>&1 && ! pi list 2>/dev/null | grep -Fq "pi-cc"; then
    missing=1
  fi
  [ "$missing" = "1" ] || return 0

  printf "FU_ENABLE_PI_CC=1 set: installing the Community Computer bridge. This may add pi-cc, which can publish autoresearch tapes to Community Computer after explicit tool use.\n" >&2
  if confirm_install "Community Computer Radicle/Pi tools" "curl -sSf https://community.computer/install | sh"; then
    curl -sSf https://community.computer/install | sh >&2 || true
  fi
}

launch_ratatui_tui() {
  if ! ensure_cargo; then
    printf "Rust/cargo is not available, so the Ratatui Control Room cannot start.\n" >&2
    return 1
  fi
  maybe_setup_pi_autoresearch
  maybe_setup_community_computer_tools

  rust_dir="${FU_RATATUI_DIR:-$FUCR_CACHE_DIR/src}"
  binary_dir="$FUCR_CACHE_DIR/bin"
  binary="$binary_dir/fucr-control-room"
  local_version_file="$FUCR_CACHE_DIR/version.txt"
  mkdir -p "$FUCR_WORKSPACE_ROOT"
  workdir="$FUCR_WORKSPACE_ROOT"
  runner_path="$(resolve_runner_path)"
  manifest_url="$base_url/agent-pack/fu-control-room-rs/manifest.txt"
  version_url="$base_url/agent-pack/fu-control-room-rs/version.txt"
  manifest_file="$rust_dir/manifest.txt"
  remote_version="$(curl -fsS "$version_url" 2>/dev/null || printf "dev")"
  remote_version="$(printf "%s" "$remote_version" | tr -d '\r\n')"
  local_version=""
  [ -f "$local_version_file" ] && local_version="$(cat "$local_version_file")"

  if [ "$FUCR_UPDATE" = "1" ] || [ ! -x "$binary" ] || [ "$remote_version" != "$local_version" ]; then
    mkdir -p "$rust_dir" "$binary_dir"
    printf "Building Future Union Control Room %s from %s\n" "$remote_version" "$base_url" >&2
    curl -fsS "$manifest_url" -o "$manifest_file"

    while IFS= read -r file_path; do
      case "$file_path" in
        ""|\#*) continue ;;
      esac
      mkdir -p "$rust_dir/$(dirname "$file_path")"
      curl -fsS "$base_url/agent-pack/fu-control-room-rs/$file_path" -o "$rust_dir/$file_path"
    done < "$manifest_file"

    cargo build --release --manifest-path "$rust_dir/Cargo.toml" >&2
    cp "$rust_dir/target/release/fu-control-room" "$binary"
    chmod +x "$binary"
    printf "%s\n" "$remote_version" > "$local_version_file"
  else
    printf "Opening cached Future Union Control Room %s\n" "$local_version" >&2
  fi

  "$binary" --base "$base_url" --runner "$runner_path" --workdir "$workdir"
}

ensure_textual_tui() {
  tui_python="python3"
  venv_dir="${FU_TUI_VENV:-.future-union/tui-venv}"

  if python3 -c 'import textual, rich' >/dev/null 2>&1; then
    printf "%s\n" "$tui_python"
    return 0
  fi

  if [ -x "$venv_dir/bin/python" ] && "$venv_dir/bin/python" -c 'import textual, rich' >/dev/null 2>&1; then
    printf "%s\n" "$venv_dir/bin/python"
    return 0
  fi

  if [ "$FU_BOOTSTRAP" = "0" ]; then
    return 1
  fi

  install_command="python3 -m venv $venv_dir && $venv_dir/bin/python -m pip install --quiet textual rich"

  if confirm_install "Future Union Textual TUI" "$install_command"; then
    mkdir -p "$(dirname "$venv_dir")"
    if python3 -m venv "$venv_dir" >&2 && "$venv_dir/bin/python" -m pip install --quiet textual rich >&2; then
      printf "%s\n" "$venv_dir/bin/python"
      return 0
    fi
    printf "Textual TUI install failed. Falling back to the lite terminal UI when possible.\n" >&2
  fi

  return 1
}

cleanup() {
  if [ -n "$picker_dir" ] && [ -d "$picker_dir" ]; then
    rm -rf "$picker_dir"
  fi

  if [ -n "$tmp_dir" ] && [ -d "$tmp_dir" ]; then
    rm -rf "$tmp_dir"
  fi
}

launch_tui() {
  picker_dir=$(mktemp -d "${TMPDIR:-/tmp}/future-union-tui.XXXXXX")
  tui_file="$picker_dir/fu-control-room.py"
  lite_tui_file="$picker_dir/fu-control-room-lite.py"
  fallback_tui_file="$picker_dir/fu-control-room.mjs"
  runner_path="$(resolve_runner_path)"
  mkdir -p "$FUCR_WORKSPACE_ROOT"
  cd "$FUCR_WORKSPACE_ROOT"
  printf "Opening Future Union Control Room from %s\n" "$base_url" >&2

  if [ "$FU_TUI" = "ratatui" ] || [ "$FU_TUI" = "1" ] || [ "$FU_TUI" = "yes" ] || [ "$FU_TUI" = "true" ]; then
    if launch_ratatui_tui; then
      exit 0
    fi
    printf "Falling back to the Python terminal Control Room.\n" >&2
    FU_TUI="lite"
  fi

  if [ "$FU_TUI" != "node" ] && command -v python3 >/dev/null 2>&1; then
    curl -fsS "$base_url/agent-pack/fu-control-room.py" -o "$tui_file"
    curl -fsS "$base_url/agent-pack/fu-control-room-lite.py" -o "$lite_tui_file"
    if [ "$FU_TUI" = "lite" ]; then
      FU_BASE_URL="$base_url" FU_RUNNER_PATH="$runner_path" python3 "$lite_tui_file" --base "$base_url" --runner "$runner_path"
      exit "$?"
    fi

    tui_python="$(ensure_textual_tui || true)"
    tui_python="${tui_python:-python3}"
    FU_BASE_URL="$base_url" FU_RUNNER_PATH="$runner_path" "$tui_python" "$tui_file" --base "$base_url" --runner "$runner_path"
    exit "$?"
  fi

  curl -fsS "$base_url/agent-pack/fu-control-room.mjs" -o "$fallback_tui_file"
  FU_BASE_URL="$base_url" FU_RUNNER_PATH="$runner_path" node "$fallback_tui_file" --base "$base_url" --runner "$runner_path"
  exit "$?"
}

pick_action() {
  if ! command -v node >/dev/null 2>&1; then
    printf "Node is not available, so the mission picker is using the default action: %s\n" "$DEFAULT_SLUG" >&2
    printf "%s\n" "$DEFAULT_SLUG"
    return 0
  fi

  picker_dir=$(mktemp -d "${TMPDIR:-/tmp}/future-union-picker.XXXXXX")
  actions_file="$picker_dir/actions.json"
  actions_table="$picker_dir/actions.tsv"

  printf "Loading Future Union action board from %s/api/actions.json\n" "$base_url" >&2
  curl -fsS "$base_url/api/actions.json" -o "$actions_file"

  node -e '
const fs = require("node:fs");
const actions = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
actions
  .filter((action) => action.status !== "shipped")
  .forEach((action, index) => {
    const label = [action.columnTitle, action.timeNeeded].filter(Boolean).join(" / ");
    console.log(`${index + 1}\t${action.slug}\t${action.title}\t${label}`);
  });
' "$actions_file" > "$actions_table"

  if [ ! -s "$actions_table" ]; then
    printf "No open actions found, using default action: %s\n" "$DEFAULT_SLUG" >&2
    printf "%s\n" "$DEFAULT_SLUG"
    return 0
  fi

  printf "\nFuture Union missions\n" >&2
  awk -F '\t' '{ printf "  %s. %s\n     %s [%s]\n", $1, $3, $2, $4 }' "$actions_table" >&2
  printf "\nChoose a mission [1]: " >&2
  read choice
  choice="${choice:-1}"

  slug_choice=$(awk -F '\t' -v choice="$choice" '$1 == choice { print $2 }' "$actions_table")
  if [ -z "$slug_choice" ]; then
    fail "No mission matched choice: $choice"
  fi

  printf "%s\n" "$slug_choice"
}

should_start_agent() {
  case "$FU_START" in
    1|yes|true) return 0 ;;
    0|no|false) return 1 ;;
  esac

  if [ "$FU_AGENT" = "none" ]; then
    return 1
  fi

  if [ ! -t 0 ]; then
    return 1
  fi

  printf "\nStart the selected agent in this workspace now? [Y/n] "
  read answer
  case "${answer:-Y}" in
    n|N|no|NO) return 1 ;;
    *) return 0 ;;
  esac
}

base_url=$(trim_trailing_slashes "$FU_BASE_URL")
resolve_base_url
trap cleanup EXIT HUP INT TERM
validate_start_mode
validate_tui_mode

if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ] || [ "${1:-}" = "help" ]; then
  cat <<EOF
Future Union Control Room

Usage:
  fucr
  fucr control-room
  fucr mission <action-slug>
  fucr sync [all|/path/to/workspace]
  fucr doctor
  fucr update

Legacy:
  sh fu-runner.sh [action-slug]

No slug opens the Ratatui Control Room when running in a terminal.

Environment:
  FUCR_BASE_URL=http://127.0.0.1:4392
  FUCR_CACHE_DIR=$FUCR_CACHE_DIR
  FUCR_WORKSPACE_ROOT=$FUCR_WORKSPACE_ROOT
  FUCR_INSTALL=0
  FU_BASE_URL=http://127.0.0.1:4391
  FU_AGENT=auto|pi|codex|claude|none
  FU_START=ask|1|0
  FU_PICK=0
  FU_TUI=ratatui|lite|textual|node|0
  FU_RATATUI_DIR=.future-union/fu-control-room-rs
  FU_TUI_VENV=.future-union/tui-venv
  FU_ENABLE_PI_CC=1
  FU_BOOTSTRAP=0
  FU_FORCE=1
  FU_DRY_RUN=1
  FU_CONTRIBUTOR_NAME="Your name"
  FU_CONTRIBUTOR_HANDLE="@handle"
  FU_CONTACT="email@example.com"
EOF
  exit 0
fi

case "${1:-}" in
  doctor)
    install_fucr_command
    run_fucr_doctor
    exit 0
    ;;
  update)
    install_fucr_command
    FUCR_UPDATE=1
    launch_tui
    ;;
  sync|repair)
    install_fucr_command
    shift
    repair_workspaces "${1:-all}"
    exit 0
    ;;
  control-room)
    install_fucr_command
    launch_tui
    ;;
  mission)
    shift
    slug="${1:-$DEFAULT_SLUG}"
    ;;
  "")
    install_fucr_command
    if [ "$FU_PICK" != "0" ] && [ -t 0 ]; then
      if [ "$FU_TUI" != "0" ]; then
        launch_tui
      fi
      slug=$(pick_action)
    else
      slug="$DEFAULT_SLUG"
    fi
    ;;
  *)
    install_fucr_command
    slug="$1"
    ;;
esac

if [ -n "${slug:-}" ]; then
  :
elif [ -n "${1:-}" ]; then
  slug="$1"
elif [ "$FU_PICK" != "0" ] && [ -t 0 ]; then
  if [ "$FU_TUI" != "0" ]; then
    launch_tui
  fi
  slug=$(pick_action)
else
  slug="$DEFAULT_SLUG"
fi

validate_agent
maybe_setup_pi_autoresearch
maybe_setup_community_computer_tools

mkdir -p "$FUCR_WORKSPACE_ROOT"
if [ -n "${FU_DIR:-}" ]; then
  workspace="$FU_DIR"
else
  workspace="$FUCR_WORKSPACE_ROOT/future-union-mission-$slug"
fi
marker_file=".future-union-runner"

case "$workspace" in
  ""|"/"|".")
    fail "Refusing to use an unsafe workspace path: $workspace"
    ;;
esac

cat <<EOF
Future Union runner
Action: $slug
Base URL: $base_url
Workspace: $workspace
Agent: $FU_AGENT
Start: $FU_START
Bootstrap: $FU_BOOTSTRAP
Contributor: ${FU_CONTRIBUTOR_NAME:-anonymous or unset}
Idea file: ${FU_IDEA_FILE:-none}
EOF

if [ "$FU_DRY_RUN" = "1" ]; then
  cat <<EOF

DRY RUN: no files or tools will be changed.
Would fetch action context, create the mission workspace, run doctor/status/claim, bootstrap the selected agent when safe, and ask whether to start it.
Would fetch:
  $base_url/api/actions/$slug.json
  $base_url/api/mission-computer.json
  $base_url/agent-pack/actions/$slug/README.md
  $base_url/agent-pack/actions/$slug/ACTION_PROPOSAL.md
  $base_url/agent-pack/actions/$slug/CONTRIBUTION.md
  $base_url/agent-pack/actions/$slug/CREDIT.md
Would create:
  RADICLE_NETWORK.md
  .future-union/rooms/$slug.md
  .git/
  .community-computer/session.json
  EXPERIMENT.md
  autoresearch.md
  autoresearch.sh
EOF
  exit 0
fi

if [ -e "$workspace" ]; then
  if [ ! -f "$workspace/$marker_file" ]; then
    fail "Refusing to overwrite $workspace because it is not marked as a Future Union runner workspace."
  fi

  if [ "$FU_FORCE" = "1" ]; then
    rm -rf "$workspace"
  else
    printf "\nWorkspace already exists. Syncing generated helper/context files and preserving your packet work.\n"
    repair_workspace "$workspace"

    if [ "$FUCR_TUI_CHILD" = "1" ]; then
      cat <<EOF

Workspace ready: $workspace
Use a to resume the agent, r to repair again, x to pause, p to preview, or P to publish after preview.
EOF
      exit 0
    fi

    (
      cd "$workspace"
      ./bin/fu-mission status
    )

    if should_start_agent; then
      (
        cd "$workspace"
        FU_AGENT="$FU_AGENT" FU_YES="$FU_YES" ./bin/fu-mission start
      )
    elif [ "$FU_TUI" != "0" ] && [ -t 0 ]; then
      cat <<EOF

Opening the Future Union Control Room with this existing workspace selected.
EOF
      FUCR_SELECT_SLUG="$slug"
      export FUCR_SELECT_SLUG
      launch_tui
    else
      cat <<EOF

Resume this mission:
  cd $workspace
  ./bin/fu-mission status
  ./bin/fu-mission start

Nothing was overwritten.
EOF
    fi
    exit 0
  fi
fi

tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/future-union-runner.XXXXXX")

fetch_file() {
  url="$1"
  destination="$2"
  printf "Fetching %s\n" "$url"
  curl -fsS "$url" -o "$destination"
}

fetch_action_context() {
  action_slug="$1"
  destination="$2"
  action_url="$base_url/api/actions/$action_slug.json"
  printf "Fetching %s\n" "$action_url"
  if curl -fsS "$action_url" -o "$destination"; then
    return 0
  fi

  feed_tmp="$tmp_dir/mission-computer-for-action.json"
  printf "Action endpoint unavailable; rebuilding context from %s/api/mission-computer.json\n" "$base_url" >&2
  fetch_file "$base_url/api/mission-computer.json" "$feed_tmp"
  if write_action_context_from_feed "$feed_tmp" "$action_slug" "$destination"; then
    return 0
  fi

  fail "Action not found in Future Union mission feed: $action_slug"
}

# Validate the action before we create the local workspace.
fetch_action_context "$slug" "$tmp_dir/action.json"

mkdir -p \
  "$tmp_dir/bin" \
  "$tmp_dir/context" \
  "$tmp_dir/.future-union/rooms" \
  "$tmp_dir/.agents/skills/future-union"

printf "Future Union runner workspace for %s\n" "$slug" > "$tmp_dir/$marker_file"
{
  printf "{\n"
  printf "  \"baseUrl\": \"%s\",\n" "$base_url"
  printf "  \"actionSlug\": \"%s\",\n" "$slug"
  printf "  \"generatedAt\": \"%s\",\n" "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
  printf "  \"runner\": \"fucr\"\n"
  printf "}\n"
} > "$tmp_dir/context/runner.json"

mv "$tmp_dir/action.json" "$tmp_dir/context/action.json"
fetch_file "$base_url/api/mission-computer.json" "$tmp_dir/context/future-union-context.json"
fetch_file "$base_url/agent-pack/actions/$slug/mission.json" "$tmp_dir/context/mission.json"
fetch_file "$base_url/agent-pack/context/safety-rules.md" "$tmp_dir/context/safety-rules.md"
fetch_file "$base_url/agent-pack/context/review-policy.md" "$tmp_dir/context/review-policy.md"

fetch_file "$base_url/agent-pack/AGENTS.md" "$tmp_dir/AGENTS.md"
fetch_file "$base_url/agent-pack/RADICLE_NETWORK.md" "$tmp_dir/RADICLE_NETWORK.md"
fetch_file "$base_url/agent-pack/bin/fu-mission" "$tmp_dir/bin/fu-mission"
fetch_file "$base_url/agent-pack/actions/$slug/README.md" "$tmp_dir/README.md"
fetch_file "$base_url/agent-pack/actions/$slug/MISSION.md" "$tmp_dir/MISSION.md"
fetch_file "$base_url/agent-pack/actions/$slug/PROMPT.md" "$tmp_dir/PROMPT.md"
fetch_file "$base_url/agent-pack/actions/$slug/PACKET.md" "$tmp_dir/PACKET.md"
fetch_file "$base_url/agent-pack/actions/$slug/ACTION_PROPOSAL.md" "$tmp_dir/ACTION_PROPOSAL.md"
fetch_file "$base_url/agent-pack/actions/$slug/SOURCES.csv" "$tmp_dir/SOURCES.csv"
fetch_file "$base_url/agent-pack/actions/$slug/CONTRIBUTION.md" "$tmp_dir/CONTRIBUTION.md"
fetch_file "$base_url/agent-pack/actions/$slug/CREDIT.md" "$tmp_dir/CREDIT.md"
fetch_file "$base_url/agent-pack/actions/$slug/INTAKE_IDEA.md" "$tmp_dir/INTAKE_IDEA.md"
fetch_file "$base_url/agent-pack/actions/$slug/SUBMIT.md" "$tmp_dir/SUBMIT.md"
fetch_file \
  "$base_url/agent-pack/actions/$slug/SKILL.md" \
  "$tmp_dir/.agents/skills/future-union/SKILL.md"

if [ -n "$FU_IDEA_FILE" ]; then
  if [ ! -f "$FU_IDEA_FILE" ]; then
    fail "FU_IDEA_FILE does not exist: $FU_IDEA_FILE"
  fi

  cp "$FU_IDEA_FILE" "$tmp_dir/context/intake-idea.json"
  {
    printf "\n## Attached Intake File\n\n"
    printf "The runner copied a local idea file into \`context/intake-idea.json\`.\n\n"
    printf "%s\n" "- Source path: $FU_IDEA_FILE"
    printf "%s\n" "- Attached at: $(date -u +"%Y-%m-%dT%H:%M:%SZ")"
  } >> "$tmp_dir/INTAKE_IDEA.md"
fi

{
  printf "\n## Runner Supplied Credit Fields\n\n"
  printf "%s\n" "- Contributor name: $FU_CONTRIBUTOR_NAME"
  printf "%s\n" "- Contributor handle: $FU_CONTRIBUTOR_HANDLE"
  printf "%s\n" "- Review contact: $FU_CONTACT"
  printf "%s\n" "- Anonymous public credit? $FU_ANONYMOUS"
} >> "$tmp_dir/CREDIT.md"

{
  printf "\n## Runner Metadata\n\n"
  printf "%s\n" "- Generated at: $(date -u +"%Y-%m-%dT%H:%M:%SZ")"
  printf "%s\n" "- Base URL: $base_url"
  printf "%s\n" "- Runner action slug: $slug"
  printf "%s\n" "- Selected agent: $FU_AGENT"
  printf "%s\n" "- Contributor name: $FU_CONTRIBUTOR_NAME"
  printf "%s\n" "- Contributor handle: $FU_CONTRIBUTOR_HANDLE"
  printf "%s\n" "- Review contact: $FU_CONTACT"
} >> "$tmp_dir/CONTRIBUTION.md"

action_title=""
if command -v node >/dev/null 2>&1; then
  action_title=$(node -e '
const fs = require("node:fs");
const payload = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
process.stdout.write(payload.action?.title || "");
' "$tmp_dir/context/action.json")
fi
action_title="${action_title:-$slug}"
{
  printf "# Action Room: %s\n\n" "$action_title"
  printf "This is the local coordination note for one Future Union action. It is not live chat, and nothing here is sent anywhere by default.\n\n"
  printf "## Status\n\n"
  printf "%s\n" "- Created: $(date -u +"%Y-%m-%dT%H:%M:%SZ")"
  printf "%s\n" "- Current blocker:"
  printf "%s\n\n" "- Next handoff:"
  printf "## Sources Found\n\n-\n\n"
  printf "## Claims And Work In Flight\n\n-\n\n"
  printf "## Reviewer Questions\n\n-\n\n"
  printf "## Network Notes\n\n"
  printf "Use this file to prepare a clean public note or Radicle packet. Do not paste secrets, raw transcripts, contact details, or unreviewed personal data here.\n"
} > "$tmp_dir/.future-union/rooms/$slug.md"

chmod +x "$tmp_dir/bin/fu-mission"
mv "$tmp_dir" "$workspace"
tmp_dir=""

(
  cd "$workspace"
  ./bin/fu-mission experiment-init >/dev/null 2>&1 || true
  ./bin/fu-mission mark-state workspace_created empty >/dev/null 2>&1 || true
)

if [ "$FUCR_TUI_CHILD" = "1" ]; then
  cat <<EOF

Created $workspace
Workspace is ready. Use the Control Room keys to start or resume an agent, pause safely, preview a packet, or open the local room.
EOF
  exit 0
fi

(
  cd "$workspace"
  ./bin/fu-mission doctor
  if [ "$FU_BOOTSTRAP" != "0" ]; then
    FU_AGENT="$FU_AGENT" FU_YES="$FU_YES" ./bin/fu-mission bootstrap
  else
    printf "\nBootstrap skipped because FU_BOOTSTRAP=0.\n"
  fi
  printf "\n"
  ./bin/fu-mission status
)

cat <<EOF

Created $workspace
EOF

if should_start_agent; then
  (
    cd "$workspace"
    FU_AGENT="$FU_AGENT" FU_YES="$FU_YES" ./bin/fu-mission start
  )
elif [ "$FU_TUI" != "0" ] && [ -t 0 ]; then
  cat <<EOF

Opening the Future Union Control Room with this workspace ready.
Use w for workspace, a for agent/resume, x to pause, p to preview, and ? for help.
EOF
  FUCR_SELECT_SLUG="$slug"
  export FUCR_SELECT_SLUG
  launch_tui
else
  cat <<EOF

When you are ready:
  cd $workspace
  ./bin/fu-mission room
  ./bin/fu-mission start

If you stop midway:
  ./bin/fu-mission pause
  ./bin/fu-mission start

After the run:
  ./bin/fu-mission finish
  ./bin/fu-mission experiment-preview
  ./bin/fu-mission experiment-publish
EOF
fi
