#!/usr/bin/env bash
#
# lane-brief.sh — live dispatch brief for a repo "lane" (milestone/label), straight
# from current Gitea state. Defeats stale worker self-report: workers brief from
# static notes and routinely report issues "todo" that are already CLOSED, forcing
# the orchestrator to re-verify each one before dispatch. This returns the CURRENT
# open set, classified for dispatch, in one call.
#
# Usage:
#   lane-brief.sh -r <owner/repo> [-m <milestone>] [-l <label>] [-L <login>] [-n <limit>]
#   lane-brief.sh -r usc/uconnect -m "M2M Part Search (0.0.45)"
#   lane-brief.sh -r usc/uconnect -l domain/6-security
#
# Reliable signals (closed issues are excluded by definition — that's the point):
#   - open-vs-closed : authoritative; this is the stale-intake failure mode.
#   - PR-linkage     : an open PR referencing the issue = work underway.
# Assignees/dependencies are intentionally NOT trusted as "available" signals —
# fleets that track work-state out-of-band (tmux board, issue text) leave them
# empty in Gitea. Output therefore partitions by PR presence and the OPEN-NO-PR set
# is "dispatch candidates to cross-check against the live fleet", not a blind list.
#
# Login resolution order: -L flag > $GITEA_LOGIN > owner inference (usc->usc,
# mosaicstack/mosaic->mosaicstack) > detect-platform.sh default-login fallback.
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=/dev/null
source "$SCRIPT_DIR/detect-platform.sh"

REPO="" MILESTONE="" LABEL="" LOGIN="" LIMIT=100
while getopts "r:m:l:L:n:h" opt; do
  case "$opt" in
    r) REPO="$OPTARG" ;;
    m) MILESTONE="$OPTARG" ;;
    l) LABEL="$OPTARG" ;;
    L) LOGIN="$OPTARG" ;;
    n) LIMIT="$OPTARG" ;;
    h) grep '^#' "$0" | sed 's/^# \?//'; exit 0 ;;
    *) echo "see -h" >&2; exit 2 ;;
  esac
done
[[ -n "$REPO" ]] || { echo "FATAL: -r <owner/repo> required" >&2; exit 2; }

# Resolve login: explicit -L, then $GITEA_LOGIN, then owner inference, then the
# shared default-login resolver. Owner inference comes before the shared fallback
# because the latter is not owner-aware (picks the default tea login), which is
# wrong for cross-instance lanes.
if [[ -z "$LOGIN" ]]; then
  if [[ -n "${GITEA_LOGIN:-}" ]]; then
    LOGIN="$GITEA_LOGIN"
  else
    case "${REPO%%/*}" in
      usc|USC) LOGIN=usc ;;
      mosaicstack|mosaic) LOGIN=mosaicstack ;;
      *) LOGIN="$(get_gitea_login_for_repo_override 2>/dev/null || true)" ;;
    esac
  fi
fi
[[ -n "$LOGIN" ]] || { echo "FATAL: could not resolve a Gitea login for $REPO (pass -L or set GITEA_LOGIN)" >&2; exit 2; }

command -v tea >/dev/null || { echo "FATAL: tea not found" >&2; exit 1; }
command -v jq  >/dev/null || { echo "FATAL: jq not found" >&2; exit 1; }

ISSUES_JSON="$(tea issues list --repo "$REPO" --login "$LOGIN" --state open --limit "$LIMIT" \
  --fields index,title,assignees,milestone,labels --output json 2>/dev/null)" || {
  echo "FATAL: tea issues list failed for $REPO (login=$LOGIN)" >&2; exit 1; }

# Open PRs, to cross-ref which issues already have work in flight. An issue is
# "work underway" if an open PR links to it. Two link signals are honored:
#   (a) a closing keyword in the PR BODY — Gitea's auto-close set (close/closes/
#       closed, fix/fixes/fixed, resolve/resolves/resolved), case-insensitive,
#       directly preceding `#N`. This is the AUTHORITATIVE link Gitea itself uses
#       to associate a PR with the issue it resolves; a body-only "Closes #546"
#       is the common case and MUST count. The earlier version inspected only the
#       PR index/title/head TSV (never the body or Gitea linkage), so a body-only
#       reference was invisible and the linked OPEN issue was misclassified as a
#       dispatch candidate — re-dispatchable in-flight work (the #546/#547 defect).
#   (b) a bare #N in the PR title, or an issue number embedded in the head branch
#       (feat/546-x, fix-546) — the weaker heuristic preserved from prior behavior.
# Bare #N mentions in the BODY are deliberately NOT treated as links: PR bodies
# routinely name unrelated issues in prose ("relevant to the #538 line of work"),
# and counting those would wrongly mark live, dispatchable issues as in-flight.
# Only the closing-keyword form is a commitment to resolve that issue. Requiring
# `#` to directly follow the keyword also keeps cross-repo `owner/repo#N` forms
# from leaking a foreign issue number into this per-repo lane (cross-repo lanes
# are run per-repo). JSON (not TSV) is used so multi-line bodies parse cleanly.
PRS_JSON="$(tea pulls list --repo "$REPO" --login "$LOGIN" --state open \
  --fields index,title,head,body --output json 2>/dev/null || echo '[]')"
[[ -n "$PRS_JSON" ]] || PRS_JSON='[]'

# \b anchors the keyword to a word start so embedded substrings do not match
# (e.g. "prefix #5", "disclosed #7" must NOT be read as "fix #5" / "closed #7").
GITEA_CLOSE_KW='close[sd]?|fix(e[sd])?|resolve[sd]?'
PR_BODY_REFS="$(printf '%s' "$PRS_JSON" | jq -r '.[] | .body // ""' 2>/dev/null \
  | grep -oiE "\\b(${GITEA_CLOSE_KW})[[:space:]:]+#[0-9]+" | grep -oE '[0-9]+' || true)"
PR_TITLE_HEAD_REFS="$(printf '%s' "$PRS_JSON" \
  | jq -r '.[] | [ (.title // ""), (.head // "" | if type=="object" then (.ref // "") else . end) ] | join(" ")' 2>/dev/null \
  | grep -oE '#[0-9]+|[/-][0-9]{3,}' | grep -oE '[0-9]+' || true)"
PR_ISSUE_REFS="$(printf '%s\n%s\n' "$PR_BODY_REFS" "$PR_TITLE_HEAD_REFS" | grep -E '^[0-9]+$' | sort -u || true)"

ts="$(date -u '+%Y-%m-%d %H:%MZ' 2>/dev/null || echo '?')"
filt="$REPO"; [[ -n "$MILESTONE" ]] && filt="$filt · milestone:'$MILESTONE'"; [[ -n "$LABEL" ]] && filt="$filt · label:'$LABEL'"
echo "LANE BRIEF — $filt · $ts (login=$LOGIN)"
echo "(open issues only; closed are excluded by definition — that's the point)"
echo

# Label match is exact-token against tea's space-separated labels string (so -l
# "security" does NOT match label "domain/6-security"). Caveat: label names that
# themselves contain spaces aren't distinguishable in tea's string form.
printf '%s' "$ISSUES_JSON" | jq -r --arg ms "$MILESTONE" --arg lb "$LABEL" --arg prs "$PR_ISSUE_REFS" '
  ($prs | split("\n") | map(select(length>0))) as $prrefs
  | map(
      select( ($ms=="" or .milestone==$ms)
          and ($lb=="" or ((.labels//"") | split(" ") | index($lb) != null)) )
      | . + { assigned: ((.assignees//"")|length>0),
              haspr: (.index as $ix | ($prrefs | index($ix)) != null) }
    )
  | (map(select(.haspr|not)))  as $candidates
  | (map(select(.haspr)))      as $inflight
  | "DISPATCH CANDIDATES (open · no open PR) — \($candidates|length)  [cross-check vs live fleet]:",
    ( $candidates[] | "  #\(.index)  \(.title[0:90])\(if .assigned then "  (gitea-assignee set)" else "" end)" ),
    "",
    "WORK UNDERWAY (open · PR in flight) — \($inflight|length):",
    ( $inflight[] | "  #\(.index)  \(.title[0:80])  [PR open]" )
'
echo
echo "Closed issues are excluded — do NOT take a worker's self-reported 'todo' on faith."
echo "Candidates = open + no PR; confirm against the live fleet before dispatch"
echo "(fleets that don't self-assign in Gitea leave 'unassigned' meaningless)."
