#!/usr/bin/env bash
# scripts/backlog-lint — flag DONE-marked items still sitting in docs/backlog/.
#
# The backlog is ACTIVE work only; a shipped/done item is gunk (git log holds
# the record — delete-on-ship, docs/README.md). "Done" is a judgment (some
# items say "SHIPPED" but keep an open remainder), so this only SURFACES
# candidates — a file whose title/headings carry an explicit done-marker —
# it never deletes. Prune on completion; this catches the stragglers. Advisory
# (exit 0), like docs-orphans / memory-lint. Bunched into /whatneedsdoing.
set -euo pipefail
cd "$(dirname "$0")/.."
D="${1:-docs/backlog}"

# Title-level done-markers: a heading, or a list item whose bold title / leading
# clause carries DONE/RESOLVED/RETIRED/✅/[x]/"SHIPPED + …".
MARK='(DONE|RESOLVED|RETIRED|✅|\[x\]|SHIPPED \+|— *SHIPPED)'
# Exclude partially-open items — a shipped sub-part under still-open work —
# and definition bullets ("**DONE** → x" maps a state name, claims nothing).
OPEN='(still (open|unverified|pending)|remain|NEXT =|Phase-2|un(shipped|deployed)|not deployed|NOT deployed|slices? .*remain|round-trip|\*\* *→)'

hits="$(grep -rnE "^(#{1,6} .*${MARK}|- .*${MARK})" "$D" --include='*.md' 2>/dev/null | grep -v "$D/README.md" | grep -viE "$OPEN" || true)"

# Two buckets. The OPEN regex above only sees ONE line, so it cannot catch the
# commonest shape by far: a full spec — still open by construction — whose
# decisions log reports a RESOLVED sub-part. Those tripped the headline count
# every session and cost a re-verification each time. A file carrying
# "## Acceptance criteria" IS a full spec, so only a done-marker on its H1
# title claims the whole item is done; inner markers are sub-part reports.
# Nothing is dropped — sub-part hits still print, they just don't drive the
# count (an all-done spec whose H1 was never annotated lands there too).
loud=''
quiet=''
while IFS= read -r line; do
    [[ -z "$line" ]] && continue
    file="${line%%:*}"
    text="${line#*:*:}"
    # Open by construction: an Acceptance-criteria spec, or any item whose own
    # front-matter status: says it is not finished (idea/draft/ready/in-progress).
    st="$(awk 'NR==1 && $0!="---"{exit} NR>1 && $0=="---"{exit} /^status: */{sub(/^status: */,""); print; exit}' "$file" 2>/dev/null)"
    if { grep -qE '^## Acceptance criteria' "$file" 2>/dev/null \
         || [[ "$st" =~ ^(idea|draft|ready|in-progress)$ ]]; } \
       && [[ "$text" != '# '* ]]; then
        quiet+="$line"$'\n'
    else
        loud+="$line"$'\n'
    fi
done <<< "$hits"

fmt() { printf '%s' "$1" | sed -E 's/^([^:]+):([0-9]+):(.{0,72}).*/  \1:\2  \3/'; }
n="$(printf '%s' "$loud" | grep -c . || true)"
q="$(printf '%s' "$quiet" | grep -c . || true)"

if [[ "$n" -eq 0 ]]; then
    echo "backlog-lint: ✓ no done-marked gunk in $D"
else
    echo "backlog-lint: $n item(s) marked done but still in $D — verify shipped, then DELETE (git log keeps it). Listed because the file has no open-spec signal (## Acceptance criteria, or front-matter status: idea/draft/ready/in-progress) or the marker is on an H1:"
    fmt "$loud"
fi
if [[ "$q" -ne 0 ]]; then
    echo "  ($q done-marked sub-part(s) inside still-open specs — normal, not gunk)"
fi

# --- front-matter hygiene -------------------------------------------------
# Every item carries `status:` and `pillar:` in closed sets. Absent front
# matter is not neutral: scripts/docs-index renders such a file as `idea`, so
# an untyped item silently claims a readiness nobody chose. Rules taken from
# the abandoned 2026-09-23 compaction tree at the 2026-09-30 product-plan
# review; the sweep that applied them ran the same day.
ALLOWED='idea|draft|ready|in-progress|canonical'
PILLARS='memory-graph|3d-design|local-compute|quests|personal|platform'
bare=''
badstatus=''
badpillar=''
for f in "$D"/*.md; do
    b="$(basename "$f")"
    [[ "$b" == INDEX.md || "$b" == README.md || "$b" == TEMPLATE.md ]] && continue
    s="$(sed -n 's/^status: *//p' "$f" | head -1)"
    if [[ -z "$s" ]]; then
        bare+="  $f  (no status:)"$'\n'
    elif ! [[ "$s" =~ ^(${ALLOWED})$ ]]; then
        badstatus+="  $f  status: $s"$'\n'
    fi
    p="$(sed -n 's/^pillar: *//p' "$f" | head -1)"
    if [[ -z "$p" ]]; then
        badpillar+="  $f  (no pillar:)"$'\n'
    elif ! [[ "$p" =~ ^(${PILLARS})$ ]]; then
        badpillar+="  $f  pillar: $p"$'\n'
    fi
done
if [[ -n "$bare" ]]; then
    echo "backlog-lint: $(printf '%s' "$bare" | grep -c .) item(s) with no front-matter status: (docs-index will call them 'idea'):"
    printf '%s' "$bare"
fi
if [[ -n "$badstatus" ]]; then
    echo "backlog-lint: status must be one of ${ALLOWED//|/ / }:"
    printf '%s' "$badstatus"
fi
if [[ -n "$badpillar" ]]; then
    echo "backlog-lint: $(printf '%s' "$badpillar" | grep -c .) item(s) with no or unknown pillar: (one of ${PILLARS//|/ / }; docs/roadmap.md):"
    printf '%s' "$badpillar"
fi

# --- prefix crowding ------------------------------------------------------
# A family of >8 files sharing a leading token is the merge signal: it means
# one subject got filed N times instead of once with N sections.
crowd="$(ls "$D"/*.md 2>/dev/null | sed -E 's|.*/||; s/-.*//' | sort | uniq -c \
         | awk '$1 > 8 {printf "  %s-*  %d files\n", $2, $1}')"
if [[ -n "$crowd" ]]; then
    echo "backlog-lint: crowded prefixes — candidates for one umbrella item:"
    printf '%s\n' "$crowd"
fi
exit 0
