#!/usr/bin/env bash
#
# _lib.sh — Shared functions for r0 coordinator scripts
#
# Usage: source ~/.config/mosaic/tools/orchestrator/_lib.sh
#
# Provides state file access, TASKS.md parsing, session lock management,
# process health checks, and formatting utilities.

MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}"
ORCH_SUBDIR=".mosaic/orchestrator"
MISSION_FILE="mission.json"
SESSION_LOCK_FILE="session.lock"
NEXT_TASK_FILE="next-task.json"
MANIFEST_FILE="docs/MISSION-MANIFEST.md"
TASKS_MD="docs/TASKS.md"
SCRATCHPAD_DIR="docs/scratchpads"

# Thresholds (seconds)
STALE_THRESHOLD=300   # 5 minutes
DEAD_THRESHOLD=1800   # 30 minutes

# ─── Color support ───────────────────────────────────────────────────────────

if [[ -t 1 ]]; then
  C_GREEN='\033[0;32m'
  C_RED='\033[0;31m'
  C_YELLOW='\033[0;33m'
  C_CYAN='\033[0;36m'
  C_BOLD='\033[1m'
  C_DIM='\033[2m'
  C_RESET='\033[0m'
else
  C_GREEN='' C_RED='' C_YELLOW='' C_CYAN='' C_BOLD='' C_DIM='' C_RESET=''
fi

# ─── Dependency checks ──────────────────────────────────────────────────────

_require_jq() {
  if ! command -v jq &>/dev/null; then
    echo -e "${C_RED}Error: jq is required but not installed${C_RESET}" >&2
    return 1
  fi
}

coord_runtime() {
  local runtime="${MOSAIC_COORD_RUNTIME:-claude}"
  case "$runtime" in
    claude|codex) echo "$runtime" ;;
    *) echo "claude" ;;
  esac
}

coord_launch_command() {
  local runtime
  runtime="$(coord_runtime)"
  echo "mosaic $runtime"
}

coord_run_command() {
  local runtime
  runtime="$(coord_runtime)"
  if [[ "$runtime" == "claude" ]]; then
    echo "mosaic coord run"
  else
    echo "mosaic coord run --$runtime"
  fi
}

# ─── Project / state file access ────────────────────────────────────────────

# Return the orchestrator directory for a project
orch_dir() {
  local project="${1:-.}"
  echo "$project/$ORCH_SUBDIR"
}

# Return the mission.json path for a project
mission_path() {
  local project="${1:-.}"
  echo "$(orch_dir "$project")/$MISSION_FILE"
}

next_task_capsule_path() {
  local project="${1:-.}"
  echo "$(orch_dir "$project")/$NEXT_TASK_FILE"
}

# Exit with error if mission.json is missing or inactive
require_mission() {
  local project="${1:-.}"
  local mp
  mp="$(mission_path "$project")"

  if [[ ! -f "$mp" ]]; then
    echo -e "${C_RED}No mission found at $mp${C_RESET}" >&2
    echo "Initialize one with: mosaic coord init --name \"Mission Name\"" >&2
    return 1
  fi

  _require_jq || return 1

  local status
  status="$(jq -r '.status // "inactive"' "$mp")"
  if [[ "$status" == "inactive" ]]; then
    echo -e "${C_YELLOW}Mission exists but is inactive. Initialize with: mosaic coord init${C_RESET}" >&2
    return 1
  fi
}

# Cat mission.json (caller pipes to jq)
load_mission() {
  local project="${1:-.}"
  cat "$(mission_path "$project")"
}

# ─── Atomic JSON write ──────────────────────────────────────────────────────

write_json() {
  local path="$1"
  local content="$2"
  local tmp
  tmp="$(mktemp "${path}.tmp.XXXXXX")"
  echo "$content" > "$tmp"
  mv "$tmp" "$path"
}

# ─── TASKS.md parsing ───────────────────────────────────────────────────────

# Parse TASKS.md pipe-delimited table and output JSON counts
count_tasks_md() {
  local project="${1:-.}"
  local tasks_file="$project/$TASKS_MD"

  if [[ ! -f "$tasks_file" ]]; then
    echo '{"total":0,"done":0,"in_progress":0,"pending":0,"failed":0,"blocked":0}'
    return
  fi

  awk -F'|' '
    /^\|.*[Ii][Dd].*[Ss]tatus/ { header=1; next }
    header && /^\|.*---/ { data=1; next }
    data && /^\|/ {
      gsub(/^[ \t]+|[ \t]+$/, "", $3)
      status = tolower($3)
      total++
      if (status == "done" || status == "completed") done++
      else if (status == "in-progress" || status == "in_progress") inprog++
      else if (status == "not-started" || status == "pending" || status == "todo") pending++
      else if (status == "failed") failed++
      else if (status == "blocked") blocked++
    }
    data && !/^\|/ && total > 0 { exit }
    END {
      printf "{\"total\":%d,\"done\":%d,\"in_progress\":%d,\"pending\":%d,\"failed\":%d,\"blocked\":%d}\n",
        total, done, inprog, pending, failed, blocked
    }
  ' "$tasks_file"
}

# Return the ID of the first not-started/pending task
find_next_task() {
  local project="${1:-.}"
  local tasks_file="$project/$TASKS_MD"

  if [[ ! -f "$tasks_file" ]]; then
    echo ""
    return
  fi

  awk -F'|' '
    /^\|.*[Ii][Dd].*[Ss]tatus/ { header=1; next }
    header && /^\|.*---/ { data=1; next }
    data && /^\|/ {
      gsub(/^[ \t]+|[ \t]+$/, "", $2)
      gsub(/^[ \t]+|[ \t]+$/, "", $3)
      status = tolower($3)
      if (status == "not-started" || status == "pending" || status == "todo") {
        print $2
        exit
      }
    }
  ' "$tasks_file"
}

# ─── Session lock management ────────────────────────────────────────────────

session_lock_path() {
  local project="${1:-.}"
  echo "$(orch_dir "$project")/$SESSION_LOCK_FILE"
}

session_lock_read() {
  local project="${1:-.}"
  local lp
  lp="$(session_lock_path "$project")"
  if [[ -f "$lp" ]]; then
    cat "$lp"
    return 0
  fi
  return 1
}

session_lock_write() {
  local project="${1:-.}"
  local session_id="$2"
  local runtime="$3"
  local pid="$4"
  local milestone_id="${5:-}"

  local lp
  lp="$(session_lock_path "$project")"

  _require_jq || return 1

  local json
  json=$(jq -n \
    --arg sid "$session_id" \
    --arg rt "$runtime" \
    --arg pid "$pid" \
    --arg ts "$(iso_now)" \
    --arg pp "$(cd "$project" && pwd)" \
    --arg mid "$milestone_id" \
    '{
      session_id: $sid,
      runtime: $rt,
      pid: ($pid | tonumber),
      started_at: $ts,
      project_path: $pp,
      milestone_id: $mid
    }')

  write_json "$lp" "$json"
}

session_lock_clear() {
  local project="${1:-.}"
  local lp
  lp="$(session_lock_path "$project")"
  rm -f "$lp"
}

# ─── Process health checks ──────────────────────────────────────────────────

is_pid_alive() {
  local pid="$1"
  kill -0 "$pid" 2>/dev/null
}

detect_agent_runtime() {
  local pid="$1"
  local cmdline
  if [[ -f "/proc/$pid/cmdline" ]]; then
    cmdline="$(tr '\0' ' ' < "/proc/$pid/cmdline")"
    if [[ "$cmdline" == *claude* ]]; then
      echo "claude"
    elif [[ "$cmdline" == *codex* ]]; then
      echo "codex"
    elif [[ "$cmdline" == *opencode* ]]; then
      echo "opencode"
    else
      echo "unknown"
    fi
  else
    echo "unknown"
  fi
}

# ─── Time / formatting utilities ────────────────────────────────────────────

iso_now() {
  date -u +"%Y-%m-%dT%H:%M:%SZ"
}

epoch_now() {
  date +%s
}

# Convert ISO timestamp to epoch seconds
iso_to_epoch() {
  local ts="$1"
  date -d "$ts" +%s 2>/dev/null || echo 0
}

# Return most recent modification time (epoch) of key project files
last_activity_time() {
  local project="${1:-.}"
  local latest=0
  local ts

  for f in \
    "$project/$TASKS_MD" \
    "$project/$(orch_dir "$project")/$MISSION_FILE" \
    "$(orch_dir "$project")/state.json"; do
    if [[ -f "$f" ]]; then
      ts="$(stat -c %Y "$f" 2>/dev/null || echo 0)"
      (( ts > latest )) && latest=$ts
    fi
  done

  # Also check git log for last commit time
  if git -C "$project" rev-parse --is-inside-work-tree &>/dev/null; then
    ts="$(git -C "$project" log -1 --format=%ct 2>/dev/null || echo 0)"
    (( ts > latest )) && latest=$ts
  fi

  echo "$latest"
}

# Format seconds-ago into human-readable string
format_ago() {
  local epoch="$1"
  local now
  now="$(epoch_now)"
  local diff=$(( now - epoch ))

  if (( diff < 60 )); then
    echo "${diff}s ago"
  elif (( diff < 3600 )); then
    echo "$(( diff / 60 ))m ago"
  elif (( diff < 86400 )); then
    echo "$(( diff / 3600 ))h $(( (diff % 3600) / 60 ))m ago"
  else
    echo "$(( diff / 86400 ))d ago"
  fi
}

# Format seconds into duration string
format_duration() {
  local secs="$1"
  if (( secs < 60 )); then
    echo "${secs}s"
  elif (( secs < 3600 )); then
    echo "$(( secs / 60 ))m $(( secs % 60 ))s"
  else
    echo "$(( secs / 3600 ))h $(( (secs % 3600) / 60 ))m"
  fi
}

# ─── Session ID generation ──────────────────────────────────────────────────

next_session_id() {
  local project="${1:-.}"
  local mp
  mp="$(mission_path "$project")"

  if [[ ! -f "$mp" ]]; then
    echo "sess-001"
    return
  fi

  _require_jq || { echo "sess-001"; return; }

  local count
  count="$(jq '.sessions | length' "$mp")"
  printf "sess-%03d" "$(( count + 1 ))"
}

# ─── Milestone helpers ───────────────────────────────────────────────────────

# Get current milestone (first in-progress, or first pending)
current_milestone_id() {
  local project="${1:-.}"
  _require_jq || return 1

  local mp
  mp="$(mission_path "$project")"
  [[ -f "$mp" ]] || return 1

  local mid
  mid="$(jq -r '[.milestones[] | select(.status == "in-progress")][0].id // empty' "$mp")"
  if [[ -z "$mid" ]]; then
    mid="$(jq -r '[.milestones[] | select(.status == "pending")][0].id // empty' "$mp")"
  fi
  echo "$mid"
}

# Get milestone name by ID
milestone_name() {
  local project="${1:-.}"
  local mid="$2"
  _require_jq || return 1

  local mp
  mp="$(mission_path "$project")"
  [[ -f "$mp" ]] || return 1

  jq -r --arg id "$mid" '.milestones[] | select(.id == $id) | .name // empty' "$mp"
}

# ─── Next-task capsule helpers ───────────────────────────────────────────────

write_next_task_capsule() {
  local project="${1:-.}"
  local runtime="${2:-claude}"
  local mission_id="${3:-}"
  local mission_name="${4:-}"
  local project_path="${5:-}"
  local quality_gates="${6:-}"
  local current_ms_id="${7:-}"
  local current_ms_name="${8:-}"
  local next_task="${9:-}"
  local tasks_done="${10:-0}"
  local tasks_total="${11:-0}"
  local pct="${12:-0}"
  local current_branch="${13:-}"

  _require_jq || return 1
  mkdir -p "$(orch_dir "$project")"

  local payload
  payload="$(jq -n \
    --arg generated_at "$(iso_now)" \
    --arg runtime "$runtime" \
    --arg mission_id "$mission_id" \
    --arg mission_name "$mission_name" \
    --arg project_path "$project_path" \
    --arg quality_gates "$quality_gates" \
    --arg current_ms_id "$current_ms_id" \
    --arg current_ms_name "$current_ms_name" \
    --arg next_task "$next_task" \
    --arg current_branch "$current_branch" \
    --arg tasks_done "$tasks_done" \
    --arg tasks_total "$tasks_total" \
    --arg pct "$pct" \
    '{
      generated_at: $generated_at,
      runtime: $runtime,
      mission_id: $mission_id,
      mission_name: $mission_name,
      project_path: $project_path,
      quality_gates: $quality_gates,
      current_milestone: {
        id: $current_ms_id,
        name: $current_ms_name
      },
      next_task: $next_task,
      progress: {
        tasks_done: ($tasks_done | tonumber),
        tasks_total: ($tasks_total | tonumber),
        pct: ($pct | tonumber)
      },
      current_branch: $current_branch
    }')"

  write_json "$(next_task_capsule_path "$project")" "$payload"
}

build_codex_strict_kickoff() {
  local project="${1:-.}"
  local continuation_prompt="${2:-}"

  _require_jq || return 1

  local capsule_path
  capsule_path="$(next_task_capsule_path "$project")"
  local capsule='{}'
  if [[ -f "$capsule_path" ]]; then
    capsule="$(cat "$capsule_path")"
  fi

  local mission_id next_task project_path quality_gates
  mission_id="$(echo "$capsule" | jq -r '.mission_id // "unknown"')"
  next_task="$(echo "$capsule" | jq -r '.next_task // "none"')"
  project_path="$(echo "$capsule" | jq -r '.project_path // "."')"
  quality_gates="$(echo "$capsule" | jq -r '.quality_gates // "none"')"

  cat <<EOF
Now initiating Orchestrator mode...

STRICT EXECUTION PROFILE FOR CODEX (HARD GATE)
- Do NOT ask clarifying questions before your first tool actions unless a Mosaic escalation trigger is hit.
- Your first actions must be reading mission state files in order.
- Treat the next-task capsule as authoritative execution input.

REQUIRED FIRST ACTIONS (IN ORDER)
1. Read ~/.config/mosaic/guides/ORCHESTRATOR-PROTOCOL.md
2. Read docs/MISSION-MANIFEST.md
3. Read docs/scratchpads/${mission_id}.md
4. Read docs/TASKS.md
5. Begin execution on next task: ${next_task}

WORKING CONTEXT
- Project: ${project_path}
- Quality gates: ${quality_gates}
- Capsule file: .mosaic/orchestrator/next-task.json

Task capsule (JSON):
\`\`\`json
${capsule}
\`\`\`

Continuation prompt:
${continuation_prompt}
EOF
}

# Get next milestone after the given one
next_milestone_id() {
  local project="${1:-.}"
  local current_id="$2"
  _require_jq || return 1

  local mp
  mp="$(mission_path "$project")"
  [[ -f "$mp" ]] || return 1

  jq -r --arg cid "$current_id" '
    .milestones as $ms |
    ($ms | to_entries | map(select(.value.id == $cid)) | .[0].key // -1) as $idx |
    if $idx >= 0 and ($idx + 1) < ($ms | length) then
      $ms[$idx + 1].id
    else
      empty
    end
  ' "$mp"
}

# ─── Slugify ─────────────────────────────────────────────────────────────────

slugify() {
  echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g; s/--*/-/g; s/^-//; s/-$//'
}
