def _normalize_prefix(role):
    r = (role or "").lower()
    return "gl" if r in ("planner", "pathmanager", "loader") else "sumstats"

def _format_toolcall(x, prefix):
    if isinstance(x, dict):
        nm = x.get("name") or x.get("tool")
        args = x.get("args")
        return f"{prefix}.{nm}({args})" if nm else str(x)
    if isinstance(x, str):
        # Preserve assignment-style logs and chained object calls
        if ("=" in x) or x.startswith("subset_"):
            return x
        return x if x.startswith("sumstats.") or x.startswith("gl.") else f"{prefix}.{x}"
    return str(x)

def extract_toolcalls(archive, role, role_key="gwaslab_agent"):
    calls = []
    prefix = _normalize_prefix(role)
    for item in archive:
        if not isinstance(item, dict):
            continue
        if item.get(role_key) != role:
            continue
        if "toolcalls" not in item:
            continue
        tc = item["toolcalls"]
        if isinstance(tc, list):
            for x in tc:
                calls.append(_format_toolcall(x, prefix))
        else:
            calls.append(_format_toolcall(tc, prefix))
    return calls

def extract_all_toolcalls(archive, exclude=None):
    calls = []
    ex = set(exclude or [])
    for item in archive:
        if not isinstance(item, dict):
            continue
        if "toolcalls" not in item:
            continue

        rk = "gwaslab_agent" if "gwaslab_agent" in item else ("role" if "role" in item else None)
        role = item.get(rk) if rk else None
        prefix = _normalize_prefix(role)
        tc = item["toolcalls"]
        
        items = tc if isinstance(tc, list) else [tc]
        for x in items:
            name = None
            if isinstance(x, str):
                head = x.split("(", 1)[0].strip()
                name = head.split(".")[-1] if "." in head else head
            elif isinstance(x, dict):
                name = x.get("name") or x.get("tool")
            if name in ex:
                continue
            calls.append(_format_toolcall(x, prefix))
    return calls
 
