#!/usr/bin/env bash
# na.sh — dispatch the FREE north coder in ONE fail-closed command. north's `ca.sh` twin.
# audience: AI coding agents first. Invoke by path; do NOT re-derive the env block or the claude -p line.
#
# WHY this exists: the north rig was inline prose in the skill — 6 `export`s + a bounded `claude -p` —
# retyped every dispatch. That is the prose-as-executable hazard (re-derivation = reformulation = the
# `ccr start &` hang class). One tested wrapper removes the re-derivation AND scopes the OpenRouter env
# to this subprocess, so it never pollutes the orchestrator's real-Anthropic shell.
#
# CONTRACT: na.sh --workspace <dir> --trust "<prompt>" --task-slug <slug> [--timeout <secs>]
#   - Ensures ccr is up via the TESTED ccr-up.sh (fail-closed: proxy down -> exit 3, NO dispatch).
#   - cd <workspace> (SHORT path — long absolute CWD triggers north's phantom-tree write bug).
#   - Runs north FOREGROUND, bounded by `timeout -k 5 <secs>` (default 360); stdin </dev/null (cannot hang).
#   - Logs raw json to ~/Projects/mega-plan-harness/tmp/logs/<datetime>-<slug>.log (same dir as ca.sh, so the
#     cost-mining tooling finds both). First lines = TOOL + CMD header.
#   - Exit: north's own rc (0 ok; 124 = timeout/non-completion -> orchestrator retries ONCE or 429-waits).
#     2 = usage error; 3 = ccr down OR agent returned 0 with empty captured output (silent write failure /
#     disk full — success requires evidence). north NEVER fixes/gates — that is cursor (ca.sh) + opus.
set -uo pipefail

WORKSPACE="" PROMPT="" TASK_SLUG="" PROFILE="" TIMEOUT=360 SESSION_ID=""
MODE="dispatch"
while [[ $# -gt 0 ]]; do
  case "$1" in
    --workspace) [[ $# -lt 2 ]] && { echo '{"ok":false,"detail":"--workspace requires a value"}' >&2; exit 2; }; WORKSPACE="${2:-}"; shift 2;;
    --trust)     [[ $# -lt 2 ]] && { echo '{"ok":false,"detail":"--trust requires a value"}' >&2; exit 2; }; PROMPT="${2:-}";    shift 2;;
    --task-slug) [[ $# -lt 2 ]] && { echo '{"ok":false,"detail":"--task-slug requires a value"}' >&2; exit 2; }; TASK_SLUG="${2:-}"; shift 2;;
    --timeout)   [[ $# -lt 2 ]] && { echo '{"ok":false,"detail":"--timeout requires a value"}' >&2; exit 2; }; TIMEOUT="${2:-}";   shift 2;;
    --profile) [[ $# -lt 2 ]] && { echo '{"ok":false,"detail":"--profile requires a value"}' >&2; exit 2; }; PROFILE="${2:-}"; shift 2;;
    --session-id) [[ $# -lt 2 ]] && { echo '{"ok":false,"detail":"--session-id requires a value"}' >&2; exit 2; }; SESSION_ID="${2:-}"; shift 2;;
    --health) MODE="health"; shift;;
    --list-models) MODE="list-models"; shift;;
    --interactive) MODE="interactive"; shift;;
    *) shift;;
  esac
done
[[ -n "$PROFILE" ]] && echo "notice: --profile=$PROFILE ignored — na.sh has no account routing" >&2

SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.."
source "$SKILL_DIR/wrappers/lib/finalize.sh"
source "$SKILL_DIR/wrappers/lib/spend-cap.sh"
_CCR_UP="${_NA_CCR_UP_BIN:-"$HOME/.claude/workflows/lib/ccr-up.sh"}"
_NORTH_ENV="${_NA_NORTH_ENV:-"$SKILL_DIR/NORTH.env"}"

ccr_up() {
  local ccr_json
  ccr_json="$(bash "$_CCR_UP" north 2>/dev/null)"
  case "$ccr_json" in
    *'"up":true'*) return 0 ;;
    *) echo "{\"ok\":false,\"detail\":\"ccr not up: ${ccr_json}\"}" >&2; return 3 ;;
  esac
}

fetch_json() {
  local url="$1"
  URL="$url" python3 - <<'PY'
import json
import os
import sys
import urllib.error
import urllib.request

url = os.environ.get("URL")
if not url:
    print('{"ok":false,"detail":"missing URL"}', file=sys.stderr)
    sys.exit(2)

try:
    with urllib.request.urlopen(url, timeout=10) as response:
        body = response.read().decode("utf-8")
        if not body.strip():
            body = json.dumps({"ok": True, "url": url, "status": response.status})
        print(body)
except urllib.error.HTTPError as exc:
    detail = {"ok": False, "detail": f"HTTP {exc.code} from {url}"}
    print(json.dumps(detail), file=sys.stderr)
    sys.exit(3)
except Exception as exc:
    detail = {"ok": False, "detail": f"request failed for {url}: {exc}"}
    print(json.dumps(detail), file=sys.stderr)
    sys.exit(3)
PY
}

case "$MODE" in
  health)
    ccr_up || exit $?
    source "$_NORTH_ENV" || { echo '{"ok":false,"detail":"NORTH.env source failed — engine precondition unmet"}' >&2; exit 3; }
    fetch_json "${_NA_HEALTH_URL:-${ANTHROPIC_BASE_URL%/}/}"
    exit $?
    ;;
  list-models)
    ccr_up || exit $?
    source "$_NORTH_ENV" || { echo '{"ok":false,"detail":"NORTH.env source failed — engine precondition unmet"}' >&2; exit 3; }
    fetch_json "${_NA_MODELS_URL:-${ANTHROPIC_BASE_URL%/}/v1/models}"
    exit $?
    ;;
  interactive)
    echo '{"ok":false,"detail":"--interactive is not supported by wrappers/na.sh; run claude directly in the workspace"}' >&2
    exit 2
    ;;
esac

# Sanitize TASK_SLUG — strip path-traversal chars; only [a-zA-Z0-9_-] allowed
TASK_SLUG="${TASK_SLUG//[^a-zA-Z0-9_-]/}"
RUN_ID="$(date +%Y-%m-%d-%H-%M-%S)-$$"

[[ -n "$WORKSPACE" && -d "$WORKSPACE" ]] || { echo '{"ok":false,"detail":"--workspace missing or not a dir"}' >&2; exit 2; }
[[ -n "$PROMPT" ]]    || { echo '{"ok":false,"detail":"--trust (prompt) required"}' >&2; exit 2; }
[[ -n "$TASK_SLUG" ]] || { echo '{"ok":false,"detail":"--task-slug required (log + retry hygiene)"}' >&2; exit 2; }

spend_cap_refuse_if_capped north 'north' "$SESSION_ID" '' 'cohere/north-mini-code:free'

# SOURCE OF TRUTH: ~/.claude/skills/north-orchestrator/na.sh - the canonical vendor reference for the north engine wrapper.

# 1. ccr up — TESTED script, fail-closed. north has no point without the proxy.
ccr_up || exit $?

# 2. environment — sourced HERE, scoped to this subprocess (never leaks to the caller).
source "$_NORTH_ENV" || { echo '{"ok":false,"detail":"NORTH.env source failed — engine precondition unmet"}' >&2; exit 3; }

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

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

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

# DRY: build the dispatch ONCE; header line and run line use the same array so they cannot diverge.
OUTPUT_FORMAT="json"
SESSION_STATUS=""
if [[ -n "$SESSION_ID" ]]; then
  OUTPUT_FORMAT="stream-json"
  SESSION_STATUS="=== session_id: $SESSION_ID ==="
  NORTH_ARGS=(timeout -k 5 "$TIMEOUT" claude -p "$PROMPT" --session-id "$SESSION_ID" --output-format "$OUTPUT_FORMAT" --dangerously-skip-permissions)
else
  NORTH_ARGS=(timeout -k 5 "$TIMEOUT" claude -p "$PROMPT" --output-format "$OUTPUT_FORMAT" --dangerously-skip-permissions)
fi
{
  echo "=== TOOL: north (cohere/north-mini-code:free) via ccr ==="
  echo "=== CMD: $(printf '%q ' "${NORTH_ARGS[@]}") ==="
  echo "=== workspace: $WORKSPACE  timeout: ${TIMEOUT}s  log: $LOG ==="
  [[ -n "$SESSION_STATUS" ]] && echo "$SESSION_STATUS"
} | tee "$LOG"

# 3. dispatch FOREGROUND, in the (short) workspace, stdin closed. Never background, never pipe to a pager.
cd "$WORKSPACE" || { echo '{"ok":false,"detail":"cd workspace failed"}' >&2; exit 2; }
"${NORTH_ARGS[@]}" < /dev/null 2>&1 | stream_output
RC="${PIPESTATUS[0]}"
raw_output="$(cat "$RAW_OUTPUT_FILE")"
echo "=== north exit=$RC ===" | tee -a "$LOG"
finalize_terminal "$RC" 'north' "$SESSION_ID" '' 'cohere/north-mini-code:free'
