#!/usr/bin/env bash
# render-agent-log-cost.sh  -  v8.3.0
#
# Renders the agent-log.md "Cost Breakdown" section: one row per phase
# with model, tokens in/out, and estimated USD. Reads phase-tracker.json
# (task-local) and cost-table.json (per-model prices).
#
# This is the agent-log counterpart to render-cost-summary.sh (which
# targets PR/Jira channel bodies). The agent-log version is rendered
# unconditionally on every Phase 7 run; the channels version is opt-in
# via prefs.global.reportContent.costSummary.
#
# Usage:
#   render-agent-log-cost.sh <task-id> [--otel-spans <path>]
#
# Exit codes:
#   0  -  section rendered (empty section still prints with placeholder row)
#   2  -  tracker JSON missing AND spans file missing (caller should skip section)

set -euo pipefail

TASK_ID="${1:?usage: render-agent-log-cost.sh <task-id> [--otel-spans <path>]}"
shift || true

SPANS_FILE=""
while [ $# -gt 0 ]; do
  case "$1" in
    --otel-spans) SPANS_FILE="${2:-}"; shift 2 ;;
    *) shift ;;
  esac
done

# cost-table.json/cost-lib.sh ship as siblings of this script both in the
# repo (pipeline/scripts/) and once installed (~/.claude/scripts/) - a
# repo-root-relative path (../../pipeline/scripts/...) only resolves in the
# repo checkout and silently misses in the installed layout. Match
# phase-tracker.sh's own resolution instead of re-deriving a repo root.
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
COST_TABLE="$SCRIPT_DIR/cost-table.json"
COST_LIB="$SCRIPT_DIR/cost-lib.sh"
if [ -f "$COST_LIB" ]; then . "$COST_LIB"; else COST_JQ_DEFS=""; fi

command -v jq >/dev/null 2>&1 || { echo "render-agent-log-cost: jq required" >&2; exit 2; }

tracker_file=""
task_id_bare="${TASK_ID##*-}"
for candidate in \
  "$PWD/.worktrees/$TASK_ID/phase-tracker.json" \
  "$PWD/.worktrees/$task_id_bare/phase-tracker.json" \
  "$PWD/.worktrees/task-$task_id_bare/phase-tracker.json" \
  "$HOME/.claude/logs/multi-agent/$TASK_ID/tracker-state.json" \
  "$HOME/.claude/logs/multi-agent/$task_id_bare/tracker-state.json"
do
  [ -f "$candidate" ] && { tracker_file="$candidate"; break; }
done

if [ -z "$tracker_file" ] && [ -z "$SPANS_FILE" ]; then
  exit 2
fi

rows_json="[]"
if [ -n "$tracker_file" ]; then
  # Tracker JSON ships in two shapes:
  #   array shape   -  phase-tracker.sh writes phases:[{id, name, model, tokens_in, ...}]
  #   object shape  -  older fixture / hand-built variant phases:{ "1": {...}, ... }
  rows_json=$(jq '
    if (.phases | type) == "array" then
      [.phases[] | {
        phase: .id,
        phase_name: (.name // .id),
        model: (.model // " - "),
        tokens_in: (.tokens_in // 0),
        tokens_out: (.tokens_out // 0),
        tokens_cached: (.tokens_cached // 0)
      }]
    else
      [(.phases // {} | to_entries[]) | {
        phase: .key,
        phase_name: (.value.name // .key),
        model: (.value.model // " - "),
        tokens_in: (.value.tokens_in // 0),
        tokens_out: (.value.tokens_out // 0),
        tokens_cached: (.value.tokens_cached // 0)
      }]
    end
    | sort_by(.phase | tonumber? // 999)' "$tracker_file")
fi

if [ "$rows_json" = "[]" ] && [ -n "$SPANS_FILE" ] && [ -f "$SPANS_FILE" ]; then
  rows_json=$(jq -s '
    [ .[] | select(.name | startswith("phase.tokens")) ] |
    group_by(.attributes.phase_id // .attributes.phase // "?") |
    map({
      phase: (.[0].attributes.phase_id // .[0].attributes.phase // "?"),
      phase_name: (.[0].attributes.phase_name // (.[0].attributes.phase_id // "?")),
      model: (.[0].attributes.model // " - "),
      tokens_in: ([.[].attributes.tokens_in_delta // 0] | add),
      tokens_out: ([.[].attributes.tokens_out_delta // 0] | add),
      tokens_cached: ([.[].attributes.tokens_cached_delta // 0] | add)
    }) | sort_by(.phase | tonumber? // 999)
  ' "$SPANS_FILE")
fi

joined=$(jq -n --argjson rows "$rows_json" --slurpfile prices "$COST_TABLE" "$COST_JQ_DEFS"'
  $rows | map(
    . as $r |
    ($prices[0].prices[$r.model] // null) as $p |
    # tokens_in is freshly-billed input (cache-exclusive, matching the host
    # input_tokens field); tokens_cached is the separate prompt-cache-read
    # count, billed at the discounted cacheReadPerMtok rate. The two counts are
    # disjoint - never subtract one from the other - so this renderer and
    # aggregate-metrics.mjs agree on what "total input" means.
    ($r.tokens_cached // 0) as $cached |
    . + {
      tokens_cached: $cached,
      usd: cost_usd_of($p; $r.tokens_in; $r.tokens_out; $cached)
    }
  )
')

totals=$(jq -n --argjson rows "$joined" '
  ($rows | map(select(.usd != null)) | sort_by(-.usd) | .[0]) as $top |
  ($rows | map(select(.usd != null) | .usd) | add) as $sum |
  {
    tokens_in:  ($rows | map(.tokens_in)  | add // 0),
    tokens_out: ($rows | map(.tokens_out) | add // 0),
    tokens_cached: ($rows | map(.tokens_cached) | add // 0),
    usd:        $sum,
    any_missing_usd: ($rows | map(.usd == null) | any),
    top_driver: (
      if ($top == null or $sum == null or $sum <= 0) then null
      else { name: ($top.phase_name // $top.phase), usd: $top.usd, pct: (($top.usd / $sum) * 100 | floor) }
      end
    )
  }
')

printf '## Cost Breakdown\n\n'
printf '| Phase | Model | Tokens in | Tokens out | Est. USD |\n'
printf '|-------|-------|-----------|------------|----------|\n'

n_rows=$(jq 'length' <<< "$joined")
if [ "$n_rows" = "0" ]; then
  printf '| *(no token data captured for %s)* |  -  |  -  |  -  |  -  |\n' "$TASK_ID"
  exit 0
fi

jq -r '.[] |
  [
    (.phase_name // .phase),
    .model,
    (.tokens_in  | tostring | (if (length > 3) then [splits("(?=(\\d{3})+$)")] | map(select(. != "")) | join(",") else . end)),
    (.tokens_out | tostring | (if (length > 3) then [splits("(?=(\\d{3})+$)")] | map(select(. != "")) | join(",") else . end)),
    (if .usd == null then " - " else "$" + ((.usd * 100 | floor) / 100 | tostring) end)
  ] | "| " + join(" | ") + " |"
' <<< "$joined"

ti=$(jq -r '.tokens_in  | tostring | (if (length > 3) then [splits("(?=(\\d{3})+$)")] | map(select(. != "")) | join(",") else . end)' <<< "$totals")
to=$(jq -r '.tokens_out | tostring | (if (length > 3) then [splits("(?=(\\d{3})+$)")] | map(select(. != "")) | join(",") else . end)' <<< "$totals")
usd_total=$(jq -r 'if .usd == null then " - " else "$" + ((.usd * 100 | floor) / 100 | tostring) end' <<< "$totals")
missing=$(jq -r '.any_missing_usd' <<< "$totals")

printf '| **Total** |  | **%s** | **%s** | **%s** |\n' "$ti" "$to" "$usd_total"

# Top cost driver  -  answers "where did the tokens go", not just "how much".
top_line=$(jq -r '
  if .top_driver == null then empty
  else "\n*Top cost driver: " + (.top_driver.name | tostring)
     + "  -  $" + ((.top_driver.usd * 100 | floor) / 100 | tostring)
     + " (" + (.top_driver.pct | tostring) + "% of total).*"
  end' <<< "$totals")
[ -n "$top_line" ] && printf '%b\n' "$top_line"

# Cache-read visibility  -  only when the tracker recorded cache hits.
cached_total=$(jq -r '.tokens_cached // 0' <<< "$totals")
if [ "$cached_total" != "0" ] && [ "$cached_total" != "null" ]; then
  cached_fmt=$(jq -r '.tokens_cached | tostring | (if (length > 3) then [splits("(?=(\\d{3})+$)")] | map(select(. != "")) | join(",") else . end)' <<< "$totals")
  printf '*Cache reads: %s tokens billed at the discounted cache-read rate (resume / prompt-cache reuse).*\n' "$cached_fmt"
fi

if [ "$missing" = "true" ]; then
  printf '\n*USD unavailable for one or more rows  -  add model id to `pipeline/scripts/cost-table.json`.*\n'
fi

exit 0
