from __future__ import annotations
from typing import Dict, Any, List, Set

class ONNError(Exception):
    pass

def toposort_layers(layers: List[dict]) -> List[dict]:
    """
    Topologically sort graph-like layers using their ids and outbound connections.
    Returns a *list of layer dicts* in dependency order.
    If there are multiple inputs/branches, keeps deterministic ordering by id.
    """
    id_to_node = {n["id"]: n for n in layers}
    indeg = {n["id"]: 0 for n in layers}
    for n in layers:
        for to_id in n.get("connections", []) or []:
            if to_id in indeg:
                indeg[to_id] += 1

    # queue starts with nodes with indegree 0
    queue = sorted([nid for nid, d in indeg.items() if d == 0])
    ordered_ids: List[str] = []
    while queue:
        nid = queue.pop(0)
        ordered_ids.append(nid)
        node = id_to_node[nid]
        for to_id in node.get("connections", []) or []:
            if to_id in indeg:
                indeg[to_id] -= 1
                if indeg[to_id] == 0:
                    queue.append(to_id)
        queue.sort()

    if len(ordered_ids) != len(layers):
        # There is a cycle or a dangling connection
        # We'll append any missing nodes in a stable order to avoid hard failure.
        remaining = [nid for nid in id_to_node.keys() if nid not in ordered_ids]
        ordered_ids.extend(sorted(remaining))

    return [id_to_node[i] for i in ordered_ids]

def ensure_list(x):
    if x is None:
        return []
    if isinstance(x, list):
        return x
    return [x]

def safe_get(d: dict, *keys, default=None):
    cur = d
    for k in keys:
        if isinstance(cur, dict) and k in cur:
            cur = cur[k]
        else:
            return default
    return cur
