import json
import re
import sys
from pathlib import Path


_FENCE_RE = re.compile(r"```(?:c\b[^\n]*)?\n(.*?)```", re.DOTALL)


def read(p):
    return Path(p).read_text()

def write(p, t):
    Path(p).write_text(t)

def log(msg="", log_file=None):
    print(msg, flush=True)

    if log_file is not None:
        with open(log_file, "a") as f:
            f.write(msg + "\n")

def stream(chunk, log_file=None):
    sys.stdout.write(chunk)
    sys.stdout.flush()

    if log_file is not None:
        with open(log_file, "a") as f:
            f.write(chunk)

def load_plan(plan_file):
    try:
        return json.loads(Path(plan_file).read_text())
    except Exception:
        return None

def save_plan(plan, plan_file):
    write(plan_file, json.dumps(plan, indent=2))

def parse_plan(raw):
    raw = re.sub(r"^```[a-z]*\n?", "", raw.strip(), flags=re.MULTILINE)
    raw = re.sub(r"```$", "", raw, flags=re.MULTILINE)
    m = re.search(r"\{.*\}", raw, re.DOTALL)
    if not m:
        return None
    try:
        obj = json.loads(m.group())
        if "optimizations" in obj and isinstance(
            obj["optimizations"],
            list
        ):
            return obj
    except json.JSONDecodeError:
        pass
    return None

def plan_summary(plan):
    lines = []
    for o in plan["optimizations"]:
        status = o.get("status", "pending")
        symbol = {
            "pending": "[ ]",
            "ok": "[✓]",
            "failed": "[✗]",
            "skipped": "[-]"
        }.get(status, "[ ]")
        note = ""
        if o.get("note"):
            note = "  (" + o["note"] + ")"
        lines.append(
            f"  {symbol} {o['id']}. "
            f"[{o['name']}] "
            f"{o['description']}{note}"
        )
    return "\n".join(lines)

def extract_code(raw):
    m = _FENCE_RE.search(raw)
    if m:
        return m.group(1).strip()
    return raw.strip()

def is_complete(code):
    if not code.strip():
        return False, "empty"
    if code.count("{") != code.count("}"):
        return False, "unbalanced braces"
    if code.count("/*") > code.count("*/"):
        return False, "unterminated block comment"
    return True, ""

def is_sane(code):
    if len(code) > 120000:
        return False, "exceeds 120KB"
    if len(code.splitlines()) > 2400:
        return False, "exceeds 2400 lines"
    if "```" in code:
        return False, "contains markdown fences"
    return True, ""

def outputs_match(run_binary, write_fn,base_bin, opt_bin, base_out_file, opt_out_file):
    _, base_out, _ = run_binary(str(base_bin))
    rc, opt_out, opt_err = run_binary(str(opt_bin))
    write_fn(base_out_file, base_out)
    write_fn(opt_out_file, opt_out)

    if rc != 0:
        return (
            False,
            "non-zero exit "
            + str(rc)
            + ": "
            + opt_err[:300].strip()
        )

    base_lines = base_out.strip().splitlines()
    opt_lines = opt_out.strip().splitlines()
    mismatches = []

    for i, (b, o) in enumerate(zip(base_lines, opt_lines)):
        if b != o:
            mismatches.append(
                f"  line {i+1}: base={b!r}  opt={o!r}"
            )
    if len(base_lines) != len(opt_lines):
        mismatches.append(
            "  line count: base="
            + str(len(base_lines))
            + "  opt="
            + str(len(opt_lines)))
    if mismatches:
        return (
            False,
            "output mismatch:\n"
            + "\n".join(mismatches[:6]))
    return True, ""