#!/usr/bin/env python3
"""exp15 — assemble the FINAL 30-节 trap set: merge deterministic straddles (straddle_ledger.json:
23 T-ent control + 7 T-ana) with kimi enrichment (ana_enrichment.json: T-ana/T-ell), dedup by
(type, boundary), require each to STRADDLE a flat-A0-30 greedy cut (common denominator), and map each
to the dependent chunk (i+1) so the judge can score each arm's rendering of that chunk.

Output final_traps.json: the trap set for Q1b (oracle vs flat) and Q3a (carryover) judging.
Run: python assemble_traps.py
"""
from __future__ import annotations
import json
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))
from chunker import greedy_chunks

SD = Path("/home/ubuntu/books/gu-zhenren/exp15")
FLAT = SD / "S2prime_big_flat.txt"
OUT = SD / "final_traps.json"


def flat_boundaries(text):
    ch = greedy_chunks(text)
    offs, starts, pos = [], [0], 0
    for i, c in enumerate(ch):
        idx = text.find(c[:25], max(0, pos - 5))
        if idx < 0:
            idx = text.find(c[:25])
        if i > 0 and idx >= 0:
            offs.append(idx); starts.append(idx)
        pos = idx + len(c) if idx >= 0 else pos + len(c)
    return ch, offs, starts


def chunk_of_offset(starts, off):
    c = 0
    for i, s in enumerate(starts):
        if off >= s:
            c = i
    return c


def straddled_boundary(text, offs, a_span, d_span):
    ai = text.find(a_span[:25]); di = text.find(d_span[:25])
    if ai < 0 or di < 0:
        return None, None, None
    lo, hi = min(ai, di), max(ai + len(a_span), di + len(d_span))
    crossed = [k for k, cut in enumerate(offs) if lo < cut < hi]
    if not crossed:
        return None, ai, di
    return crossed[0], ai, di   # boundary index (0-based cut = between chunk k and k+1)


def main():
    text = FLAT.read_text(encoding="utf-8")
    ch, offs, starts = flat_boundaries(text)
    traps = []
    seen = set()   # (type, boundary_cut)

    def add(t, typ, control, a_span, d_span, phen, exp):
        bcut, ai, di = straddled_boundary(text, offs, a_span, d_span)
        if bcut is None:
            return "not_straddling"
        key = (typ, bcut)
        if key in seen:
            return "dup"
        seen.add(key)
        dep_chunk = chunk_of_offset(starts, di)      # chunk containing the dependent (scored chunk)
        anc_chunk = chunk_of_offset(starts, ai)
        traps.append({"id": f"{typ}-{len(traps)+1}", "type": typ, "control": control,
                      "boundary_cut": bcut, "anc_chunk": anc_chunk, "dep_chunk": dep_chunk,
                      "anchor_span": a_span, "dependent_span": d_span,
                      "phenomenon": phen, "expected_ru": exp})
        return "ok"

    stats = {"ok": 0, "dup": 0, "not_straddling": 0}
    # deterministic
    det = json.load(open(SD / "straddle_ledger.json"))["traps"]
    for t in det:
        r = add(t, t["type"], t.get("control", False), t["anchor_span"], t["dependent_span"],
                t["phenomenon"], t["expected_ru"])
        stats[r] += 1
    # enrichment
    enr = json.load(open(SD / "ana_enrichment.json"))["straddles"]
    for t in enr:
        r = add(t, t.get("type", "T-ana"), False, t.get("antecedent_span", ""), t.get("dependent_span", ""),
                t.get("phenomenon", ""), t.get("expected_ru", ""))
        stats[r] += 1

    from collections import Counter
    by = Counter(t["type"] for t in traps)
    prim = Counter(t["type"] for t in traps if not t["control"])
    OUT.write_text(json.dumps({
        "n": len(traps), "by_type": dict(by),
        "quota": {"T-ana": {"have": prim.get("T-ana", 0), "need": 20, "meets": prim.get("T-ana", 0) >= 20},
                  "T-ent": {"have": by.get("T-ent", 0), "need": 20, "meets": by.get("T-ent", 0) >= 20, "role": "negative-control"},
                  "T-ell": {"have": by.get("T-ell", 0), "need": 10, "meets": by.get("T-ell", 0) >= 10, "note": "secondary; short = honest-power"}},
        "undecidable_a_priori": ["T-tense", "T-cat", "T-gen", "T-reg"],
        "merge_stats": stats,
        "traps": traps,
    }, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"final trap set: {dict(by)} (total {len(traps)})  merge {stats}")
    print(f"  T-ana (primary): {prim.get('T-ana',0)} {'MEETS>=20' if prim.get('T-ana',0)>=20 else 'SHORT'}")
    print(f"  T-ent (control): {by.get('T-ent',0)} {'MEETS>=20' if by.get('T-ent',0)>=20 else 'SHORT'}")
    print(f"  T-ell (secondary): {by.get('T-ell',0)} (>=10? {by.get('T-ell',0)>=10})")
    print(f"  undecidable a-priori: T-tense/T-cat/T-gen/T-reg -> {OUT}")


if __name__ == "__main__":
    main()
