#!/usr/bin/env python
"""Detect orphan-tail bullets in a rendered resume PDF.

resume-system rule: a bullet that wraps to 3+ physical lines must have MORE
than 5 words on its final line. If the last line has <=5 words, the bullet is
an "orphan tail" and must be tightened to fit on 2 lines (or expanded so the
last line carries >5 words). Two-line bullets with a 1-2 word tail are surfaced
as non-blocking info.

Usage: python scripts/check_bullet_wrapping.py <source.md> <rendered.pdf>
Exit: 0 = clean, 1 = violations, 2 = usage/match error.
"""
import re
import sys
import unicodedata

from pypdf import PdfReader

MAX_TAIL_WORDS = 5   # final line of a 3+ line bullet must exceed this
TRIGGER_LINES = 3    # rule applies once a bullet spans this many lines


def norm(s):
    s = unicodedata.normalize("NFKC", s)
    for a, b in (
        ("’", "'"), ("‘", "'"), ("“", '"'), ("”", '"'),
        ("–", "-"), ("—", "-"),
        ("•", " "), ("", " "), ("�", " "),
    ):
        s = s.replace(a, b)
    return re.sub(r"\s+", " ", s).strip()


def strip_marker(s):
    return re.sub(r"^[\W_]+\s+", "", s).strip()


def main():
    if len(sys.argv) != 3:
        print(__doc__)
        return 2
    md_path, pdf_path = sys.argv[1], sys.argv[2]

    bullets = []
    with open(md_path, encoding="utf-8") as fh:
        for line in fh:
            line = line.rstrip("\n")
            if line.startswith("- "):
                bullets.append(line[2:].strip())

    reader = PdfReader(pdf_path)
    phys = []
    for pg in reader.pages:
        for ln in pg.extract_text().split("\n"):
            if ln.strip():
                phys.append(ln)
    phys_norm = [norm(strip_marker(l)) for l in phys]

    violations, info, unmatched = [], [], []
    pos = 0
    for b in bullets:
        bn = norm(b)
        bn_low = bn.lower()
        start = None
        for i in range(pos, len(phys_norm)):
            cand = phys_norm[i]
            if len(cand) < 6:
                continue
            head = cand[: min(len(cand), 45)].lower()
            if bn_low.startswith(head):
                start = i
                break
        if start is None:
            unmatched.append(b)
            continue
        joined, used, j = "", 0, start
        while j < len(phys) and used < 6 and len(norm(strip_marker(joined))) < len(bn) - 2:
            joined += " " + phys[j]
            used += 1
            j += 1
        last_words = len(norm(strip_marker(phys[start + used - 1])).split())
        rec = (b, used, last_words)
        if used >= TRIGGER_LINES and last_words <= MAX_TAIL_WORDS:
            violations.append(rec)
        elif used == 2 and last_words <= 2:
            info.append(rec)
        pos = start + used

    def short(b):
        return (b[:72] + "...") if len(b) > 72 else b

    print(f"Checked {len(bullets)} bullets in {pdf_path} ({len(reader.pages)} page(s)).")
    if unmatched:
        print(f"  ! {len(unmatched)} bullet(s) not matched to PDF text - check manually:")
        for b in unmatched:
            print("     -", short(b))
    if info:
        print(f"  (info) {len(info)} two-line bullet(s) with a 1-2 word tail (optional tighten):")
        for b, u, w in info:
            print(f"     - [{w}w tail] {short(b)}")
    if violations:
        print(f"\nORPHAN-TAIL VIOLATIONS: {len(violations)} bullet(s) wrap to "
              f"{TRIGGER_LINES}+ lines with <= {MAX_TAIL_WORDS} words on the last line.")
        print("Fix: tighten each to 2 lines, or expand the last line past 5 words.")
        for b, u, w in violations:
            print(f"     - [{u} lines, {w}-word tail] {short(b)}")
        return 1
    print("OK: no orphan-tail violations.")
    return 0


if __name__ == "__main__":
    sys.exit(main())
