#!/usr/bin/env bash
# log-metric.sh  -  append a single event to the pipeline metrics log.
#
# Append-only JSONL at $METRICS_FILE (default $LOGS_ROOT/metrics.jsonl, LOGS_ROOT defaulting to $HOME/.claude/logs/multi-agent).
# One event per line. Schema:
#   {
#     "ts":      "<ISO 8601 timestamp>",
#     "task_id": "<jira id, github issue, or run id>",
#     "phase":   "<0..5>",
#     "event":   "<event name>",
#     "details": { ...arbitrary key/value pairs... },
#     "phaseSchema": 2
#   }
#
# phaseSchema names the phase vocabulary the line was written under, because
# this file is append-only and v19.0.0 renumbered the phases. A line with
# phase 3 means Dev under schema 1 and Review under schema 2. Lines with no
# phaseSchema field predate v19.0.0 and are schema 1. Aggregators pick their
# name table from this field; without it the two vocabularies are
# indistinguishable and every historical roll-up is quietly wrong.
#
# Usage:
#   log-metric.sh <task_id> <phase> <event> [key=value ...]
#
# Examples:
#   log-metric.sh PROJ-1234 4 review.completed raw_count=8 accepted=3 deferred=2 rejected=3 approved=true duration_ms=42000
#   log-metric.sh PROJ-1234 4 triage.edge_case case=high-rejection-rate
#   log-metric.sh PROJ-1234 3 rework.started iteration=2 accepted_blocking=2
#   log-metric.sh #316 5 task.completed phases=6 review_cycles=2 lang=tr
#
# Atomic append: each call uses `>> "$file"`, which opens with O_APPEND, and
# writes the whole line in one `echo`. On a local filesystem an O_APPEND write
# lands at the end of the file in one piece, so concurrent pipeline runs do not
# interleave.
#
# Failure mode: if the metrics directory is unwritable, we WARN to stderr but
# do NOT fail the pipeline. Telemetry is best-effort, never load-bearing.

set -uo pipefail

if [ "$#" -lt 3 ]; then
  echo "usage: log-metric.sh <task_id> <phase> <event> [key=value ...]" >&2
  exit 64
fi

TASK_ID="$1"; shift
PHASE="$1"; shift
EVENT="$1"; shift

METRICS_FILE="${METRICS_FILE:-${LOGS_ROOT:-$HOME/.claude/logs/multi-agent}/metrics.jsonl}"
METRICS_DIR="$(dirname "$METRICS_FILE")"

mkdir -p "$METRICS_DIR" 2>/dev/null || {
  echo "warn: log-metric: cannot create $METRICS_DIR  -  telemetry skipped" >&2
  exit 0
}

# Same escaping as audit-log.sh: backslash, quote, tab and CR escaped, and a
# newline written as \n, so any caller text stays inside one JSON string.
json_escape() {
  printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/	/\\t/g; s/\r/\\r/g' | awk 'NR > 1 { printf "\\n" } { printf "%s", $0 }'
}

# Build details object from key=value args. Keep it shell-portable: no jq required.
DETAILS="{"
SEP=""
for kv in "$@"; do
  KEY="${kv%%=*}"
  VAL="${kv#*=}"
  # Decide if value is bool / int / string. Keep simple  -  anything not parseable as
  # int or true/false becomes a JSON string.
  case "$VAL" in
    true|false) JSON_VAL="$VAL" ;;
    ''|*[!0-9]*) JSON_VAL="\"$(json_escape "$VAL")\"" ;;
    *) JSON_VAL="$VAL" ;;
  esac
  DETAILS="${DETAILS}${SEP}\"$(json_escape "$KEY")\":${JSON_VAL}"
  SEP=","
done
DETAILS="${DETAILS}}"

# Read from the phase contract rather than hardcoding: the next renumbering
# bumps one JSON field instead of hunting for a literal in a shell script.
PHASE_SCHEMA="$(node -e 'const c=require("'"$(cd "$(dirname "$0")" && pwd)"'/../schemas/phases.json"); process.stdout.write(String(c.phaseSchema))' 2>/dev/null)"
[ -n "$PHASE_SCHEMA" ] || PHASE_SCHEMA=2

TS="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
LINE="{\"ts\":\"${TS}\",\"task_id\":\"$(json_escape "$TASK_ID")\",\"phase\":\"$(json_escape "$PHASE")\",\"event\":\"$(json_escape "$EVENT")\",\"details\":${DETAILS},\"phaseSchema\":${PHASE_SCHEMA}}"

# Caller-supplied values can carry a credential copied out of an error.
# redact.mjs replaces it with a marker; without node the line is written as is.
REDACT="$(cd "$(dirname "$0")" && pwd)/../lib/redact.mjs"
[ -f "$REDACT" ] && LINE=$(printf '%s' "$LINE" | node "$REDACT" 2>/dev/null || printf '%s' "$LINE")

# Atomic append (one line, well under PIPE_BUF).
echo "$LINE" >> "$METRICS_FILE" 2>/dev/null || {
  echo "warn: log-metric: cannot write to $METRICS_FILE  -  telemetry skipped" >&2
  exit 0
}

# v8.3+ opt-in tracker forwarder. When LOG_METRIC_FORWARD_TO_TRACKER=1 and the
# event details carry tokens_in / tokens_out, mirror them into phase-tracker.sh
# so the cost block stays in sync with metrics.jsonl from a single call site.
# Best-effort: never fails the pipeline.
if [ "${LOG_METRIC_FORWARD_TO_TRACKER:-0}" = "1" ]; then
  TI=""; TO=""; TC=""; MODEL=""
  for kv in "$@"; do
    case "${kv%%=*}" in
      tokens_in)     TI="${kv#*=}" ;;
      tokens_out)    TO="${kv#*=}" ;;
      tokens_cached) TC="${kv#*=}" ;;
      model)         MODEL="${kv#*=}" ;;
    esac
  done
  if [ -n "$TI" ] && [ -n "$TO" ]; then
    SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
    # Sanitize the optional cached count: a non-integer (quoted, float, empty)
    # must NOT poison the whole tokens call and lose the valid in/out counts.
    # phase-tracker rejects the call on any bad arg, so drop a bad cached to 0.
    case "$TC" in
      ''|*[!0-9]*) TC=0 ;;
    esac
    if [ -x "$SCRIPT_DIR/phase-tracker.sh" ]; then
      # tokens_cached is optional (4th arg); when the host reports prompt-cache
      # reads, forwarding it lets the cost ledger price them at the cheaper rate.
      TRACKER_QUIET=1 "$SCRIPT_DIR/phase-tracker.sh" tokens "$PHASE" "$TI" "$TO" "$TC" >/dev/null 2>&1 || true
      if [ -n "$MODEL" ]; then
        TRACKER_QUIET=1 "$SCRIPT_DIR/phase-tracker.sh" model "$PHASE" "$MODEL" >/dev/null 2>&1 || true
      fi
    fi
  fi
fi
