"""MIR control flow graph analysis utilities.

Provides programmatic access to MIR CFG analysis for debugging openvaf_jax.
Uses networkx to analyze the control flow graph generated by openvaf-viz.

For CLI usage, see scripts/analyze_mir_cfg.py
"""

import subprocess
import tempfile
from pathlib import Path
from typing import Optional

# Optional imports - only needed if using these functions
try:
    import networkx as nx
    import pydot

    HAS_GRAPH_DEPS = True
except ImportError:
    HAS_GRAPH_DEPS = False


def _check_deps():
    """Check that networkx and pydot are available."""
    if not HAS_GRAPH_DEPS:
        raise ImportError(
            "MIR analysis requires networkx and pydot. Install with: uv add networkx pydot"
        )


def generate_mir_dot(
    va_path: Path,
    func: str = "eval",
    output_dir: Optional[Path] = None,
) -> Path:
    """Generate MIR DOT file from Verilog-A using openvaf-viz.

    Args:
        va_path: Path to Verilog-A file
        func: Function to visualize ('init' or 'eval')
        output_dir: Output directory (default: system temp)

    Returns:
        Path to generated DOT file

    Raises:
        FileNotFoundError: If openvaf-viz not found
        subprocess.CalledProcessError: If openvaf-viz fails
    """
    if output_dir is None:
        output_dir = Path(tempfile.gettempdir())

    model_name = va_path.stem
    dot_path = output_dir / f"{model_name}_{func}.dot"

    # Find openvaf-viz
    openvaf_viz = Path("vendor/OpenVAF/target/release/openvaf-viz")
    if not openvaf_viz.exists():
        openvaf_viz = Path("openvaf-viz")  # Try PATH

    # Build command with correct syntax
    cmd = [str(openvaf_viz), str(va_path), "--output", str(dot_path), "--format", "dot"]
    if func == "eval":
        cmd.append("--eval-only")
    elif func == "init":
        cmd.append("--init-only")

    try:
        subprocess.run(cmd, capture_output=True, text=True, check=True)
        return dot_path
    except FileNotFoundError:
        raise FileNotFoundError(
            "openvaf-viz not found. Build with: "
            "cd vendor/OpenVAF && cargo build --release -p openvaf-viz"
        )


def load_mir_cfg(dot_path: Path) -> "nx.DiGraph":
    """Load MIR CFG from DOT file into networkx DiGraph.

    Args:
        dot_path: Path to DOT file from openvaf-viz

    Returns:
        NetworkX DiGraph with node labels containing MIR instructions
    """
    _check_deps()

    graphs = pydot.graph_from_dot_file(str(dot_path))
    if not graphs:
        raise ValueError(f"No graphs found in {dot_path}")

    dot_graph = graphs[0]
    G = nx.DiGraph()

    # Add nodes with labels
    for node in dot_graph.get_nodes():
        name = node.get_name().strip('"')
        if name and name not in ("node", "edge", "graph", "\\n"):
            label = node.get_label()
            if label:
                # Handle double-escaped newlines from pydot
                label = label.strip('"')
                label = label.replace("\\\\l", "\n").replace("\\\\n", "\n")
                label = label.replace("\\l", "\n").replace("\\n", "\n")
            G.add_node(name, label=label or "")

    # Add edges with branch labels
    for edge in dot_graph.get_edges():
        src = edge.get_source().strip('"')
        dst = edge.get_destination().strip('"')
        label = edge.get_label()
        if label:
            label = label.strip('"')
        G.add_edge(src, dst, label=label)

    return G


def find_phi_blocks(G: "nx.DiGraph") -> dict[str, list[str]]:
    """Find all blocks containing PHI instructions.

    Args:
        G: MIR CFG graph

    Returns:
        Dict mapping block name -> list of PHI instruction lines
    """
    _check_deps()

    phis = {}
    for node, data in G.nodes(data=True):
        label = data.get("label", "")
        lines = label.split("\n")
        phi_lines = []
        for line in lines:
            line_stripped = line.strip().lower()
            if (
                line_stripped == "phi"
                or line_stripped.startswith("phi(")
                or line_stripped.startswith("phi ")
            ):
                phi_lines.append(line.strip())
        if phi_lines:
            phis[node] = phi_lines
    return phis


def find_branch_points(G: "nx.DiGraph") -> list[tuple[str, list[tuple[str, str]]]]:
    """Find all conditional branch points (T/F branches).

    Args:
        G: MIR CFG graph

    Returns:
        List of (block, [(successor, label), ...]) for blocks with T/F branches
    """
    _check_deps()

    branches = []
    for node in G.nodes():
        succs = list(G.successors(node))
        if len(succs) == 2:
            labels = [G.edges[node, s].get("label", "") for s in succs]
            if "T" in labels or "F" in labels:
                branches.append((node, list(zip(succs, labels))))
    return branches


def get_cfg_summary(G: "nx.DiGraph") -> dict:
    """Get summary statistics for a MIR CFG.

    Args:
        G: MIR CFG graph

    Returns:
        Dict with node_count, edge_count, phi_block_count, branch_count,
        entry_blocks, exit_blocks
    """
    _check_deps()

    phi_blocks = find_phi_blocks(G)
    branches = find_branch_points(G)
    entries = [n for n in G.nodes() if G.in_degree(n) == 0]
    exits = [n for n in G.nodes() if G.out_degree(n) == 0]

    return {
        "node_count": G.number_of_nodes(),
        "edge_count": G.number_of_edges(),
        "phi_block_count": len(phi_blocks),
        "branch_count": len(branches),
        "entry_blocks": entries,
        "exit_blocks": exits,
    }


def find_paths_to_block(
    G: "nx.DiGraph",
    target: str,
    max_depth: int = 10,
) -> dict:
    """Find paths from entry blocks to a target block.

    Args:
        G: MIR CFG graph
        target: Target block name
        max_depth: Maximum path length to search

    Returns:
        Dict with predecessors, successors, and paths with edge labels
    """
    _check_deps()

    if target not in G:
        return {"error": f"Block {target} not found"}

    result = {
        "target": target,
        "predecessors": list(G.predecessors(target)),
        "successors": list(G.successors(target)),
        "paths": [],
    }

    # Find entry blocks
    entries = [n for n in G.nodes() if G.in_degree(n) == 0]

    # Find paths
    for entry in entries[:3]:
        try:
            paths = list(nx.all_simple_paths(G, entry, target, cutoff=max_depth))
            for path in paths[:5]:
                path_with_labels = []
                for i in range(len(path) - 1):
                    edge_label = G.edges[path[i], path[i + 1]].get("label", "")
                    path_with_labels.append((path[i], edge_label))
                path_with_labels.append((path[-1], ""))
                result["paths"].append(path_with_labels)
        except nx.NetworkXNoPath:
            pass

    return result


def analyze_phi_block(G: "nx.DiGraph", block: str) -> dict:
    """Detailed analysis of a block containing PHI nodes.

    Shows which predecessor provides which value for each PHI.

    Args:
        G: MIR CFG graph
        block: Block name to analyze

    Returns:
        Dict with predecessors, edge labels, and PHI source analysis
    """
    _check_deps()

    if block not in G:
        return {"error": f"Block {block} not found"}

    label = G.nodes[block].get("label", "")
    preds = list(G.predecessors(block))

    result = {
        "block": block,
        "predecessors": preds,
        "predecessor_edges": {p: G.edges[p, block].get("label", "jump") for p in preds},
        "phis": [],
    }

    # Parse PHI nodes from label
    for line in label.split("\n"):
        line = line.strip()
        if "phi(" in line.lower():
            phi_info = {"instruction": line, "sources": {}}

            if "=" in line:
                phi_info["result"] = line.split("=")[0].strip()

            # Parse phi(v1 from block1, v2 from block2, ...)
            if "(" in line and ")" in line:
                phi_content = line[line.find("(") + 1 : line.rfind(")")]
                for part in phi_content.split(","):
                    part = part.strip()
                    if " from " in part:
                        val, blk = part.split(" from ")
                        phi_info["sources"][blk.strip()] = val.strip()

            result["phis"].append(phi_info)

    return result
