#!/usr/bin/env bash
# Tapestry traffic roll-up for the active dev session.
#
# Replaces the five separate grep/sort/uniq invocations from V2 plan §3:
#   grep -c 'tapestry/page' temp/dev-logs/latest/net.log
#   grep -oE 'tapestry/page/[0-9]+' … | sort -u | wc -l
#   grep -c 'tapestry/bucket' temp/dev-logs/latest/net.log
#   grep -c 'tapestry/since'  temp/dev-logs/latest/net.log
#   grep ' 429 ' temp/dev-logs/latest/net.log
#
# Reads `temp/dev-logs/latest/net.log` (RN side) and optionally folds in
# `watch-app.log`'s `:net]` lines via --watch. Slices to a marker if you
# ran `since.sh mark` first.
#
# Usage:
#   tapestry-stats.sh                    full session
#   tapestry-stats.sh --since            slice from marker (uses since.sh)
#   tapestry-stats.sh --budget           include lowest /api/v1/tapestry/* rate-limit remaining
#   tapestry-stats.sh --watch            also fold watch-app.log's `:net]` lines
#   tapestry-stats.sh --raw              JSON output (for scripts/dev/verify/*)

set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
cd "$REPO_ROOT"

SESSION="temp/dev-logs/latest"
if [[ ! -e "$SESSION" ]]; then
  echo "tapestry-stats: no session at $SESSION — run scripts/dev/tail-logs.sh" >&2
  exit 1
fi
SESSION_REAL="$(cd "$SESSION" && pwd -P)"

USE_SINCE=0
BUDGET=0
INCLUDE_WATCH=0
RAW=0
for arg in "$@"; do
  case "$arg" in
    --since)   USE_SINCE=1 ;;
    --budget)  BUDGET=1 ;;
    --watch)   INCLUDE_WATCH=1 ;;
    --raw)     RAW=1 ;;
    -h|--help)
      sed -n '2,21p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
      exit 0
      ;;
    *)
      echo "tapestry-stats: unknown flag: $arg" >&2
      exit 2
      ;;
  esac
done

# ---- Slice helpers -----------------------------------------------------------
# If --since is set, slice the named stream from since.sh's marker;
# otherwise read the whole file. Returns 0 even if the marker is missing
# (slices from the start in that case — same shape as bare cat).
slice() {
  local stream="$1"
  local f="$SESSION_REAL/$stream.log"
  [[ -f "$f" ]] || return 0
  if [[ "$USE_SINCE" -eq 1 ]]; then
    local marker="$SESSION_REAL/.markers/$stream"
    if [[ -f "$marker" ]]; then
      local n
      n=$(cat "$marker")
      awk -v n="$n" 'NR > n' "$f"
    else
      cat "$f"
    fi
  else
    cat "$f"
  fi
}

# ---- iOS side counts ---------------------------------------------------------
# net.log shape: ` LOG  [net] #N METHOD host/path STATUS Nms bytes=N`
# Status 429 lands between the path and the duration, so ` 429 ` matches
# only the response code (`#429 GET …` is preceded by `#`, not space).

net_lines=$(slice net)

count_pattern() {
  if [[ -z "${net_lines:-}" ]]; then echo 0; return; fi
  printf '%s\n' "$net_lines" | grep -c "$1" || true
}
count_unique() {
  if [[ -z "${net_lines:-}" ]]; then echo 0; return; fi
  printf '%s\n' "$net_lines" | grep -oE "$1" | sort -u | wc -l | tr -d ' '
}

page_total=$(count_pattern 'tapestry/page/[0-9]')
page_unique=$(count_unique 'tapestry/page/[0-9]+')
bucket_total=$(count_pattern 'tapestry/bucket/[0-9]')
bucket_unique=$(count_unique 'tapestry/bucket/[0-9]+')
since_total=$(count_pattern 'tapestry/since/[0-9]')

# Rate-limited responses (status 429). Restrict to tapestry/* so an unrelated
# 429 (markers, notifications) doesn't bias the picture.
ratelimited=$(printf '%s\n' "${net_lines:-}" | grep -E 'tapestry/(page|bucket|since)/[0-9]+ 429 ' | wc -l | tr -d ' ')

# ---- Watch side fold-in ------------------------------------------------------
# DebugRecorder emits one line per request on the watch under category `net`,
# routed through grep ':net]' is not free in tail-logs.sh today (only :img]
# is carved out), so we slice watch-app.log here and filter inline.
watch_page=0
watch_bucket=0
if [[ "$INCLUDE_WATCH" -eq 1 ]]; then
  watch_lines=$(slice watch-app | grep ':net\]' || true)
  if [[ -n "$watch_lines" ]]; then
    watch_page=$(printf '%s\n' "$watch_lines"  | grep -c 'tapestry/page' || true)
    watch_bucket=$(printf '%s\n' "$watch_lines" | grep -c 'tapestry/bucket' || true)
  fi
fi

# ---- Rate budget -------------------------------------------------------------
# `[api obs]` lines emit `RateLimit=<remaining>/<limit>` for every tapestry/*
# response. The metro.log carries the WARN lines (the [net] demuxer drops
# them because it greps for [net], not [api obs]). Walk the slice and surface
# the lowest remaining across all tapestry requests in window.
budget_line=""
if [[ "$BUDGET" -eq 1 ]]; then
  metro_lines=$(slice metro)
  if [[ -n "$metro_lines" ]]; then
    budget_line=$(printf '%s\n' "$metro_lines" \
      | grep '\[api obs\]' \
      | grep 'tapestry/' \
      | grep -oE 'RateLimit=[0-9]+/[0-9]+' \
      | awk -F'[=/]' '
          { if (n == 0 || $2+0 < min) { min = $2+0; lim = $3+0 } ; n++ }
          END {
            if (n > 0) printf "%d/%d (over %d responses)", min, lim, n
          }
        ')
  fi
fi

# ---- Output ------------------------------------------------------------------
if [[ "$RAW" -eq 1 ]]; then
  printf '{'
  printf '"page_walks_total":%d,'   "$page_total"
  printf '"page_walks_unique":%d,'  "$page_unique"
  printf '"bucket_polls_total":%d,' "$bucket_total"
  printf '"bucket_polls_unique":%d,' "$bucket_unique"
  printf '"since_calls":%d,'        "$since_total"
  printf '"rate_limited":%d'        "$ratelimited"
  if [[ "$INCLUDE_WATCH" -eq 1 ]]; then
    printf ',"watch_page_walks":%d,"watch_bucket_polls":%d' "$watch_page" "$watch_bucket"
  fi
  if [[ "$BUDGET" -eq 1 && -n "$budget_line" ]]; then
    # Pull the leading "min/lim" pair for the JSON shape; drop the over-N
    # human suffix.
    head=$(echo "$budget_line" | awk '{print $1}')
    printf ',"rate_budget_min":"%s"' "$head"
  fi
  printf '}\n'
  exit 0
fi

# Human card.
slice_hint=$([[ "$USE_SINCE" -eq 1 ]] && echo " (since marker)" || echo "")
printf "Tapestry traffic%s\n" "$slice_hint"
printf "                  total   unique\n"
printf "Page walks:     %7d %8d  (cap: MAX_BACKFILL_PAGES=48 per cold mount)\n" "$page_total" "$page_unique"
printf "Bucket polls:   %7d %8d  (one per ~3 s while foregrounded)\n"            "$bucket_total" "$bucket_unique"
printf "Since calls:    %7d        (gap-fill on mount)\n"                       "$since_total"
if [[ "$ratelimited" -gt 0 ]]; then
  printf "Rate-limited:   %7d        ⚠ 429s in window\n" "$ratelimited"
else
  printf "Rate-limited:         0\n"
fi
if [[ "$INCLUDE_WATCH" -eq 1 ]]; then
  printf "Watch /page:    %7d\n"  "$watch_page"
  printf "Watch /bucket:  %7d\n"  "$watch_bucket"
fi
if [[ "$BUDGET" -eq 1 ]]; then
  if [[ -n "$budget_line" ]]; then
    printf "Rate budget:    lowest seen %s\n" "$budget_line"
  else
    printf "Rate budget:    (no [api obs] lines in window)\n"
  fi
fi
