"""Score one run's transcript against meeting truth.json (the agent never sees this).
Usage: grade.py <lang> <transcript.md> [--json out.json]"""
import json, re, statistics, sys
from pathlib import Path
from rapidfuzz import fuzz

LANG, TR = sys.argv[1], Path(sys.argv[2])
D = Path(__file__).parent / f"meeting_{LANG}"
truth = json.loads((D / "truth.json").read_text(encoding="utf-8"))
names = json.loads((D / "config.json").read_text(encoding="utf-8")).get("scored_terms", [])
CN = "零一二三四五六七八九"


def norm(s: str) -> str:
    s = s.lower()
    s = re.sub(r"\d", lambda m: CN[int(m.group())], s)  # 15 -> 一五, script uses 十五; handled by fuzzy match
    return re.sub(r"[\W_]+", "", s)


TS = re.compile(r"\[(\d{1,2}):(\d{2}):(\d{2})(?:\.\d+)?\]")
raw = TR.read_text(encoding="utf-8") if TR.exists() else ""
paras = []  # (seconds, text)
for m in re.finditer(TS, raw):
    paras.append([int(m[1]) * 3600 + int(m[2]) * 60 + int(m[3]), m.end()])
for k, p in enumerate(paras):
    end = paras[k + 1][1] - 12 if k + 1 < len(paras) else len(raw)
    p.append(raw[p[1]:end])
flat = norm(raw if not paras else "".join(p[2] for p in paras))
utts = truth["utterances"]
THRESH = 70


def locate(u_norm, hay):
    if len(u_norm) < 6 or not hay:
        return None
    al = fuzz.partial_ratio_alignment(u_norm, hay)
    return al if al and al.score >= THRESH else None


res = []
for u in utts:
    un = norm(u["text"])
    al = locate(un, flat)
    covered = al is not None
    dup = False
    if covered:
        rest = flat[:al.dest_start] + "#" + flat[al.dest_end:]
        dup = locate(un, rest) is not None
    res.append({"i": u["i"], "speaker": u["speaker"], "start": u["start"], "covered": covered, "dup": dup})

# timestamps: for each paragraph, the utterance that best matches its opening
ts_err = []
for sec, _, text in paras:
    head = norm(text)[:40]
    if len(head) < 10:
        continue
    best = max(utts, key=lambda u: fuzz.partial_ratio(head, norm(u["text"])))
    if fuzz.partial_ratio(head, norm(best["text"])) < 80:
        continue
    # paragraph may start mid-utterance; allow the truth interval
    err = 0 if best["start"] - 10 <= sec <= best["end"] + 10 else min(abs(sec - best["start"]), abs(sec - best["end"]))
    ts_err.append({"para_ts": sec, "utt_start": best["start"], "err": round(err, 1), "in_part2": best["start"] >= truth["part1_end"]})

phone = [r for r in res if r["speaker"] == truth["phone_speaker"]]
overlap = [r for r in res if truth["part2_start"] - 5 <= r["start"] <= truth["part1_end"] + 5]
p2 = [e for e in ts_err if e["in_part2"]]
term_hits = {t: (raw.count(t), sum(u["text"].count(t) for u in utts)) for t in names}
out = {
    "transcript_exists": TR.exists(), "chars": len(flat),
    "covered": f"{sum(r['covered'] for r in res)}/{len(res)}",
    "phone_covered": f"{sum(r['covered'] for r in phone)}/{len(phone)}",
    "non_phone_covered": f"{sum(r['covered'] for r in res if r['speaker'] != truth['phone_speaker'])}/{len(res) - len(phone)}",
    "duplicated_utterances": sum(r["dup"] for r in res),
    "overlap_zone_duplicated": f"{sum(r['dup'] for r in overlap)}/{len(overlap)}",
    "timestamp_paragraphs_checked": len(ts_err),
    "timestamp_off_by_more_than_10s": sum(e["err"] > 0 for e in ts_err),
    "part2_timestamp_median_err": statistics.median([e["err"] for e in p2]) if p2 else None,
    "terms_written_vs_spoken": term_hits,
}
print(json.dumps(out, ensure_ascii=False, indent=1))
if "--json" in sys.argv:
    Path(sys.argv[sys.argv.index("--json") + 1]).write_text(json.dumps({"summary": out, "utterances": res, "timestamps": ts_err}, ensure_ascii=False, indent=1), encoding="utf-8")
