
import re, numpy as np

def parse_ops(circuit):
    """
    Works with BOTH circuit._ops (list of tuples) AND circuit.gate_history (list of strings).
    Returns a clean list of (gate_name, qubits, params) tuples.
    NaN-safe: any NaN param is replaced with 0.0.
    """
    ops = []

    # ── Source 1: structured _ops tuples ─────────────────────────
    if hasattr(circuit, "_ops") and circuit._ops:
        for entry in circuit._ops:
            if isinstance(entry, (list, tuple)) and len(entry) >= 2:
                name   = str(entry[0]).upper()
                qubits = list(entry[1]) if hasattr(entry[1], "__iter__") else [entry[1]]
                params = list(entry[2]) if len(entry) > 2 and entry[2] is not None else []
                params = [0.0 if (p is None or (isinstance(p, float) and np.isnan(p))) else float(p)
                          for p in params]
                ops.append((name, qubits, params))
        return ops

    # ── Source 2: gate_history strings ───────────────────────────
    history = getattr(circuit, "gate_history", [])
    for raw in history:
        e  = raw.strip()
        el = e.lower()

        # Toffoli
        m = re.match(r"toffoli\s+control1\s+(\d+)\s+control2\s+(\d+)\s+target\s+(\d+)", el)
        if m:
            ops.append(("TOFFOLI", [int(m.group(1)), int(m.group(2)), int(m.group(3))], []))
            continue

        # Fredkin
        m = re.match(r"fredkin\s+control\s+(\d+)\s+target1\s+(\d+)\s+target2\s+(\d+)", el)
        if m:
            ops.append(("FREDKIN", [int(m.group(1)), int(m.group(2)), int(m.group(3))], []))
            continue

        # CNOT / CZ / CY
        m = re.match(r"(cnot|cz|cy)\s+control\s+(\d+)\s+target\s+(\d+)", el)
        if m:
            ops.append((m.group(1).upper(), [int(m.group(2)), int(m.group(3))], []))
            continue

        # CRx / CRy / CRz
        m = re.match(r"(crx|cry|crz)\(([^)]+)\)\s+control\s+(\d+)\s+target\s+(\d+)", el)
        if m:
            p = _safe_float(m.group(2))
            ops.append((m.group(1).upper(), [int(m.group(3)), int(m.group(4))], [p]))
            continue

        # SWAP
        m = re.match(r"swap\s+qubits\s+(\d+)\s+and\s+(\d+)", el)
        if m:
            ops.append(("SWAP", [int(m.group(1)), int(m.group(2))], []))
            continue

        # Rx / Ry / Rz / P with param
        m = re.match(r"(rx|ry|rz|p)\(([^)]+)\)\s+on\s+qubit\s+(\d+)", el)
        if m:
            p = _safe_float(m.group(2))
            ops.append((m.group(1).upper(), [int(m.group(3))], [p]))
            continue

        # U3
        m = re.match(r"u\(([^)]+)\)\s+on\s+qubit\s+(\d+)", el)
        if m:
            params = [_safe_float(x) for x in m.group(1).split(",")]
            ops.append(("U", [int(m.group(2))], params))
            continue

        # Plain single-qubit: "H on qubit 0"
        m = re.match(r"([a-z]+)\s+on\s+qubit\s+(\d+)", el)
        if m:
            ops.append((m.group(1).upper(), [int(m.group(2))], []))
            continue

        # Measurement
        m = re.match(r"measure(?:ment)?\s+qubit[s]?\s+([\d,\s]+)", el)
        if m:
            qubits = [int(x) for x in re.findall(r"\d+", m.group(1))]
            ops.append(("M", qubits, []))
            continue

    return ops


def _safe_float(s):
    try:
        v = float(s)
        return 0.0 if np.isnan(v) else v
    except (ValueError, TypeError):
        return 0.0
