"""Graph-based pattern-focused formatter for trace comparison.
Presents results grouped by CUDA graph execution patterns, reducing cognitive load
by showing 1-7 unique patterns instead of thousands of individual kernel executions.
"""
from collections import Counter, defaultdict
from typing import Any
def format_graph_comparison_text(result: dict[str, Any], show_all: bool = False) -> str:
    """Format graph matching results as pattern-focused text report.
    Args:
        result: Results from graph_matcher.match_traces()
        show_all: Show all patterns without truncation
    Returns:
        Formatted text report with pattern-focused UX
    """
    lines = []
    summary = result['summary']
    graph_pairs = result['graph_pairs']
    # Header
    lines.append("=" * 80)
    lines.append("TRACE COMPARISON - GRAPH-BASED ANALYSIS")
    lines.append("=" * 80)
    lines.append("")
    # Overview section
    lines.append("┌" + "─" * 78 + "┐")
    lines.append("│ OVERVIEW" + " " * 69 + "│")
    lines.append("├" + "─" * 78 + "┤")

    amd_total_ms = sum(
        sum(k.get('dur', 0) for k in pair['amd_kernels'])
        for pair in graph_pairs
    ) / 1000
    nv_total_ms = sum(
        sum(k.get('dur', 0) for k in pair['nv_kernels'])
        for pair in graph_pairs
    ) / 1000
    amd_kernels = summary['total_kernel_pairs']  # Actually shows unique positions
    nv_kernels = summary['total_kernel_pairs']
    lines.append(f"│ AMD:     {amd_kernels:>6,} kernel positions  {amd_total_ms:>8.1f}ms" + " " * 25 + "│")
    lines.append(f"│ NVIDIA:  {nv_kernels:>6,} kernel positions  {nv_total_ms:>8.1f}ms" + " " * 25 + "│")
    lines.append(f"│" + " " * 78 + "│")
    lines.append(f"│ Match rate: {summary['match_rate']:.1f}%  "
                 f"({summary['matched']:,} matched, "
                 f"{summary['amd_only']:,} AMD-only, "
                 f"{summary['nv_only']:,} NV-only)" + " " * 5 + "│")
    lines.append("└" + "─" * 78 + "┘")
    lines.append("")
    # CUDA Graph Patterns section
    lines.append("┌" + "─" * 78 + "┐")
    lines.append("│ CUDA GRAPH PATTERNS (Transformer Layers)" + " " * 36 + "│")
    lines.append("├" + "─" * 78 + "┤")
    lines.append(f"│ Total graph executions:  {summary['num_graph_pairs']:,}" + " " * 50 + "│")

    amd_patterns = _group_by_pattern(graph_pairs, 'amd')
    nv_patterns = _group_by_pattern(graph_pairs, 'nv')
    lines.append(f"│ Unique AMD patterns:     {len(amd_patterns)}" + " " * 50 + "│")
    lines.append(f"│ Unique NVIDIA patterns:  {len(nv_patterns)}" + " " * 50 + "│")
    lines.append("└" + "─" * 78 + "┘")
    lines.append("")
    # Pattern Details
    lines.append("=" * 80)
    lines.append("PATTERN DETAILS")
    lines.append("=" * 80)
    lines.append("")
    # Show AMD patterns
    lines.append("AMD Patterns:")
    max_patterns = len(amd_patterns) if show_all else min(5, len(amd_patterns))
    for i, (pattern, executions) in enumerate(list(amd_patterns.items())[:max_patterns], 1):
        kernel_count = len(executions[0]['amd_kernels'])
        lines.append(f"  Pattern {i}: {len(executions):>4} executions ({kernel_count} kernels each)")
        if i == 1:  # Show detail for main pattern
            lines.append(f"    First few kernels:")
            first_kernels = sorted(executions[0]['amd_kernels'], key=lambda x: x.get('ts', 0))[:5]
            for k in first_kernels:
                name = k.get('name', '')[:60]
                lines.append(f"      - {name}")
    if not show_all and len(amd_patterns) > 5:
        lines.append(f"  ... ({len(amd_patterns) - 5} more patterns)")
    lines.append("")
    # Show NVIDIA patterns
    lines.append("NVIDIA Patterns:")
    max_patterns = len(nv_patterns) if show_all else min(5, len(nv_patterns))
    for i, (pattern, executions) in enumerate(list(nv_patterns.items())[:max_patterns], 1):
        kernel_count = len(executions[0]['nv_kernels'])
        lines.append(f"  Pattern {i}: {len(executions):>4} executions ({kernel_count} kernels each)")
        if i == 1:  # Show detail for main pattern
            lines.append(f"    First few kernels:")
            first_kernels = sorted(executions[0]['nv_kernels'], key=lambda x: x.get('ts', 0))[:5]
            for k in first_kernels:
                name = k.get('name', '')[:60]
                lines.append(f"      - {name}")
    if not show_all and len(nv_patterns) > 5:
        lines.append(f"  ... ({len(nv_patterns) - 5} more patterns)")
    lines.append("")
    # Drilling down into main pattern
    lines.append("=" * 80)
    lines.append("MAIN PATTERN COMPARISON (Pattern 1)")
    lines.append("=" * 80)
    lines.append("")
    if amd_patterns and nv_patterns:

        amd_main_executions = list(amd_patterns.values())[0]
        nv_main_executions = list(nv_patterns.values())[0]
        amd_main = amd_main_executions[0]
        nv_main = nv_main_executions[0]

        amd_types = Counter()
        nv_types = Counter()
        for match in amd_main['matches']:
            if match['status'] in ['MATCH', 'AMD_ONLY']:
                amd_types[match['amd_type']] += 1
            if match['status'] in ['MATCH', 'NV_ONLY']:
                nv_types[match['nv_type']] += 1
        lines.append("Kernel Type Distribution (per execution):")
        lines.append(f"{'Type':<20} {'AMD':>8} {'NVIDIA':>8} {'Diff':>8}")
        lines.append("-" * 50)
        all_types = sorted(set(amd_types.keys()) | set(nv_types.keys()))
        differences = []
        for ktype in all_types:
            amd_count = amd_types.get(ktype, 0)
            nv_count = nv_types.get(ktype, 0)
            diff = amd_count - nv_count
            diff_str = f"+{diff}" if diff > 0 else str(diff) if diff < 0 else "="
            lines.append(f"{ktype:<20} {amd_count:>8} {nv_count:>8} {diff_str:>8}")
            if diff != 0:
                differences.append((ktype, diff))
        lines.append("")
        lines.append("-" * 80)
        lines.append("Key Findings:")
        if differences:

            differences.sort(key=lambda x: abs(x[1]), reverse=True)
            for ktype, diff in differences[:3]:
                total_extra = abs(diff) * len(amd_main_executions)
                if diff > 0:
                    lines.append(f"  • AMD runs {diff:+d} extra {ktype} per execution")
                    lines.append(f"    → {total_extra:,} extra operations across all executions")
                else:
                    lines.append(f"  • NVIDIA runs {abs(diff)} extra {ktype} per execution")
                    lines.append(f"    → {total_extra:,} extra operations across all executions")
        else:
            lines.append("  • Perfect match - kernel types align exactly!")
    lines.append("")
    # Aggregate Statistics
    lines.append("=" * 80)
    lines.append("AGGREGATE STATISTICS")
    lines.append("=" * 80)
    lines.append("")
    if amd_patterns and nv_patterns:
        amd_main_executions = list(amd_patterns.values())[0]
        nv_main_executions = list(nv_patterns.values())[0]
        amd_main_count = len(amd_main_executions)
        nv_main_count = len(nv_main_executions)
        lines.append(f"Main Pattern (appears {min(amd_main_count, nv_main_count)}x on both platforms):")
        amd_kernels_per = len(amd_main_executions[0]['amd_kernels'])
        nv_kernels_per = len(nv_main_executions[0]['nv_kernels'])
        lines.append(f"  AMD:    {amd_kernels_per} kernels × {amd_main_count} executions = {amd_kernels_per * amd_main_count:,} total kernels")
        lines.append(f"  NVIDIA: {nv_kernels_per} kernels × {nv_main_count} executions = {nv_kernels_per * nv_main_count:,} total kernels")

        amd_time = sum(
            sum(k.get('dur', 0) for k in exec['amd_kernels'])
            for exec in amd_main_executions
        )
        nv_time = sum(
            sum(k.get('dur', 0) for k in exec['nv_kernels'])
            for exec in nv_main_executions
        )
        lines.append(f"")
        lines.append(f"  Total time in main pattern:")
        lines.append(f"    AMD:    {amd_time/1000:.1f}ms ({amd_time/amd_total_ms/10:.1f}% of total)")
        lines.append(f"    NVIDIA: {nv_time/1000:.1f}ms ({nv_time/nv_total_ms/10:.1f}% of total)")
    lines.append("")
    return "\n".join(lines)
def _group_by_pattern(
    graph_pairs: list[dict[str, Any]],
    platform: str
) -> dict[tuple, list[dict[str, Any]]]:
    """Group graph executions by their kernel sequence pattern.
    Args:
        graph_pairs: List of graph pair dictionaries
        platform: 'amd' or 'nv'
    Returns:
        Dictionary mapping pattern signatures to list of executions
    """
    patterns: dict[tuple, list[dict[str, Any]]] = defaultdict(list)
    kernels_key = f'{platform}_kernels'
    for pair in graph_pairs:
        kernels = pair[kernels_key]
        sorted_kernels = sorted(kernels, key=lambda x: x.get('ts', 0))
        # Pattern signature: tuple of kernel names in order
        signature = tuple(k.get('name', '') for k in sorted_kernels)
        patterns[signature].append(pair)

    sorted_patterns = dict(sorted(
        patterns.items(),
        key=lambda x: len(x[1]),
        reverse=True
    ))
    return sorted_patterns
def format_graph_comparison_json(result: dict[str, Any]) -> str:
    """Format graph matching results as JSON.
    Args:
        result: Results from graph_matcher.match_traces()
    Returns:
        JSON string
    """
    import json
    return json.dumps(result, indent=2)
