"""#293: convert every `X.read_event()` call site to the house bounded form.

House form, measured at de5a44bc on the six already-correct methods
(net_dial:1905, net_dial_loopback:1955, net_open_stream_classed:2002,
net_stream_send:2039):

    self.send(KIND_..., req)?;
    let deadline = self.call_deadline();      <- AFTER the send, above the loop
    loop { match self.read_event_until(deadline)? { ... } }

so the deadline is per CALL (one timer for the whole loop), never per frame.
Run with --apply to write; default is a dry-run report.
"""
import io, os, re, sys

APPLY = "--apply" in sys.argv
ROOT = "crates"
CALL = re.compile(r"([A-Za-z_][A-Za-z0-9_]*)\.read_event\(\)")
LOOP_STARTS = ("loop {", "loop{", "while ", "for ")
FN_STARTS = ("fn ", "pub fn ", "pub(crate) fn ", "async fn ", "pub async fn ")


def indent_of(s):
    return len(s) - len(s.lstrip())


def is_fn_line(stripped):
    return any(stripped.startswith(p) for p in FN_STARTS)


report = {"loop": 0, "noloop": 0, "manual": [], "files": 0, "collide": []}

for dirpath, _dirs, files in os.walk(ROOT):
    for f in sorted(files):
        if not f.endswith(".rs"):
            continue
        p = os.path.join(dirpath, f).replace("\\", "/")
        raw = io.open(p, "r", encoding="utf-8", newline="").read()
        if ".read_event()" not in raw:
            continue
        nl = "\r\n" if "\r\n" in raw else "\n"
        lines = raw.split(nl)
        hits = [i for i, ln in enumerate(lines) if ".read_event()" in ln]
        inserted_at = set()
        # reverse so earlier indices stay valid
        for i in sorted(hits, reverse=True):
            ln = lines[i]
            m = CALL.search(ln)
            if not m:
                report["manual"].append((p, i + 1, ln.strip()[:70]))
                continue
            recv = m.group(1)
            hit_ind = indent_of(ln)
            # walk up for the enclosing loop header, stopping at the fn boundary
            loop_line = None
            probe_ind = hit_ind
            stripped_hit = ln.strip()
            if stripped_hit.startswith(LOOP_STARTS):
                loop_line = i
            else:
                j = i - 1
                while j >= 0:
                    s = lines[j].strip()
                    if not s or s.startswith("//"):
                        j -= 1
                        continue
                    ind = indent_of(lines[j])
                    if ind < probe_ind:
                        if s.startswith(LOOP_STARTS):
                            loop_line = j
                            break
                        if is_fn_line(s) or ind == 0:
                            break
                        probe_ind = ind
                    j -= 1
            new_call = "{}.read_event_until(deadline)".format(recv)
            if loop_line is None:
                # one-shot read: hoist immediately above this statement so the
                # borrow is unambiguous and the form still reads as per-call.
                report["noloop"] += 1
                lines[i] = ln.replace("{}.read_event()".format(recv), new_call, 1)
                hoist = " " * hit_ind + "let deadline = {}.call_deadline();".format(recv)
                lines.insert(i, hoist)
            else:
                report["loop"] += 1
                lines[i] = ln.replace("{}.read_event()".format(recv), new_call, 1)
                if loop_line not in inserted_at:
                    inserted_at.add(loop_line)
                    li = indent_of(lines[loop_line])
                    hoist = " " * li + "let deadline = {}.call_deadline();".format(recv)
                    lines.insert(loop_line, hoist)
        out = nl.join(lines)
        report["files"] += 1
        if APPLY:
            io.open(p, "w", encoding="utf-8", newline="").write(out)

print("files touched", report["files"])
print("sites in a loop (hoisted above the loop)", report["loop"])
print("one-shot sites (hoisted above the statement)", report["noloop"])
print("MANUAL (multi-line chain, not rewritten):")
for p, n, t in report["manual"]:
    print("   ", p + ":" + str(n), t)
print("APPLIED" if APPLY else "DRY RUN")
