"""Detailed kernel-to-kernel formatter for graph matching results.
Shows individual kernel pairs in position order (default) or grouped by operation type.
"""
from collections import Counter, defaultdict
from typing import Any
def format_graph_comparison_detailed(
    result: dict[str, Any],
    show_all: bool = False,
    group_by_op: bool = False,
    max_graphs: int = 3,
) -> str:
    """Format graph matching results with kernel-to-kernel details.
    Args:
        result: Results from graph_matcher.match_traces()
        show_all: Show all kernel pairs without truncation
        group_by_op: Group kernels by operation type instead of position order
        max_graphs: Maximum number of graph pairs to show in detail (default: 3)
    Returns:
        Formatted text report with kernel-to-kernel matching
    """
    lines = []
    summary = result['summary']
    graph_pairs = result['graph_pairs']
    # Header
    lines.append("=" * 80)
    lines.append("TRACE COMPARISON - AMD vs NVIDIA")
    lines.append("=" * 80)
    lines.append("")
    # Section 1: Overview
    lines.append("┏" + "━" * 78 + "┓")
    lines.append("┃ SECTION 1: OVERVIEW" + " " * 58 + "┃")
    lines.append("┣" + "━" * 78 + "┫")
    total_graphs = summary['num_graph_pairs']
    amd_total_kernels = sum(len(pair['amd_kernels']) for pair in graph_pairs)
    nv_total_kernels = sum(len(pair['nv_kernels']) for pair in graph_pairs)
    lines.append(f"┃ Transformer layer graphs:  AMD: {total_graphs}  NVIDIA: {total_graphs}" + " " * 27 + "┃")
    lines.append(f"┃ Graph pairs to compare:    {total_graphs}" + " " * 50 + "┃")
    lines.append(f"┃ Total kernels in graphs:   AMD: {amd_total_kernels}  NVIDIA: {nv_total_kernels}" + " " * 23 + "┃")
    lines.append("┗" + "━" * 78 + "┛")
    lines.append("")
    # Section 2: Non-graph kernels (placeholder - always 0 for transformer layers)
    lines.append("┏" + "━" * 78 + "┓")
    lines.append("┃ SECTION 2: NON-GRAPH KERNELS" + " " * 48 + "┃")
    lines.append("┣" + "━" * 78 + "┫")
    lines.append("┃ ✓ All kernels are in CUDA graphs" + " " * 44 + "┃")
    lines.append("┗" + "━" * 78 + "┛")
    lines.append("")
    # Section 3: Unique patterns count
    amd_patterns = _group_by_pattern(graph_pairs, 'amd')
    nv_patterns = _group_by_pattern(graph_pairs, 'nv')
    lines.append("┏" + "━" * 78 + "┓")
    lines.append("┃ SECTION 3: CUDA GRAPH PATTERNS (Transformer Layers)" + " " * 25 + "┃")
    lines.append("┣" + "━" * 78 + "┫")
    lines.append(f"┃ Unique patterns:  AMD: {len(amd_patterns)}, NVIDIA: {len(nv_patterns)}" + " " * 42 + "┃")
    lines.append(f"┃ Total executions: AMD: {total_graphs}, NVIDIA: {total_graphs}" + " " * 32 + "┃")
    lines.append("┗" + "━" * 78 + "┛")
    lines.append("")
    # Show representative patterns
    lines.append("=" * 80)
    lines.append(f"UNIQUE GRAPH PATTERNS (showing up to {max_graphs})")
    lines.append("=" * 80)
    lines.append("")
    num_patterns_to_show = min(max_graphs, len(amd_patterns)) if not show_all else len(amd_patterns)
    for pattern_idx, (amd_pattern, amd_executions) in enumerate(list(amd_patterns.items())[:num_patterns_to_show], 1):
        lines.append(f"┌─ Pattern {pattern_idx} " + "─" * (73 - len(f"Pattern {pattern_idx}")) + "┐")

        nv_pattern_idx = min(pattern_idx - 1, len(nv_patterns) - 1)
        nv_executions = list(nv_patterns.values())[nv_pattern_idx]
        amd_kernels_per = len(amd_executions[0]['amd_kernels'])
        nv_kernels_per = len(nv_executions[0]['nv_kernels'])
        lines.append(f"│ AMD:    {len(amd_executions)} executions × {amd_kernels_per} kernels each" + " " * 35 + "│")
        lines.append(f"│ NVIDIA: {len(nv_executions)} executions × {nv_kernels_per} kernels each" + " " * 35 + "│")
        lines.append("└" + "─" * 78 + "┘")
        lines.append("")
        # Show match quality for this pattern
        matches = amd_executions[0]['matches']
        matched = sum(1 for m in matches if m['status'] == 'MATCH')
        amd_only = sum(1 for m in matches if m['status'] == 'AMD_ONLY')
        nv_only = sum(1 for m in matches if m['status'] == 'NV_ONLY')
        mismatch = sum(1 for m in matches if m['status'] == 'MISMATCH')
        match_rate = (matched / len(matches) * 100) if matches else 0
        lines.append(f"Match Quality: {match_rate:.1f}%")
        lines.append(f"  ✓ Matched:     {matched}")
        lines.append(f"  ⚠ AMD only:    {amd_only} (fusion differences)")
        lines.append(f"  ⚠ NVIDIA only: {nv_only} (fusion differences)")
        if mismatch > 0:
            lines.append(f"  ✗ Mismatched:  {mismatch}")
        lines.append("")
        # Show kernel details
        if group_by_op:
            lines.extend(_format_kernels_grouped_by_op(matches, show_all))
        else:
            lines.extend(_format_kernels_in_order(matches, show_all))
        lines.append("")
    return "\n".join(lines)
def _format_kernels_in_order(matches: list[dict[str, Any]], show_all: bool) -> list[str]:
    """Format kernels in position order."""
    lines = []
    lines.append("Kernel-to-Kernel Comparison (representative execution):")
    lines.append("")
    lines.append(f"{'Pos':<4} {'AMD Kernel':<45} {'NVIDIA Kernel':<45} {'Status':<8}")
    lines.append("-" * 110)
    max_pairs = len(matches) if show_all else min(20, len(matches))
    for idx, match in enumerate(matches[:max_pairs], 1):
        status_icon = {
            'MATCH': '✓',
            'AMD_ONLY': '⚠ AMD',
            'NV_ONLY': '⚠ NV',
            'MISMATCH': '✗',
        }.get(match['status'], '?')
        if idx == 1 or (idx > 1 and match['amd_type'] != matches[idx-2]['amd_type']):
            op_type = match['amd_type'] if match['amd_type'] != '-' else match['nv_type']
            lines.append("")
            lines.append(f"[{op_type}]")
        amd_name = match['amd_name'][:44] if match['amd_name'] != '-' else '-'
        nv_name = match['nv_name'][:44] if match['nv_name'] != '-' else '-'
        lines.append(f"{idx:<4} {amd_name:<45} {nv_name:<45} {status_icon:<8}")
    if not show_all and len(matches) > 20:
        lines.append(f"     ... ({len(matches) - 20} more kernel pairs)")
    return lines
def _format_kernels_grouped_by_op(matches: list[dict[str, Any]], show_all: bool) -> list[str]:
    """Format kernels grouped by operation type."""
    lines = []
    lines.append("Kernel-to-Kernel Comparison (representative execution):")
    lines.append("")
    by_op: dict[str, list[tuple[int, dict[str, Any]]]] = defaultdict(list)
    for idx, match in enumerate(matches, 1):
        op_type = match['amd_type'] if match['amd_type'] != '-' else match['nv_type']
        by_op[op_type].append((idx, match))
    sorted_ops = sorted(by_op.items(), key=lambda x: x[1][0][0])
    for op_type, op_matches in sorted_ops:
        lines.append(f"── {op_type} ({len(op_matches)} kernel pairs) " + "─" * (80 - len(f"── {op_type} ({len(op_matches)} kernel pairs) ")))
        lines.append(f"{'Pos':<4} {'AMD Kernel':<45} {'NVIDIA Kernel':<45} {'Status':<8}")
        lines.append("-" * 110)
        max_to_show = len(op_matches) if show_all else min(3, len(op_matches))
        for idx, match in op_matches[:max_to_show]:
            status_icon = {
                'MATCH': '✓',
                'AMD_ONLY': '⚠ AMD',
                'NV_ONLY': '⚠ NV',
                'MISMATCH': '✗',
            }.get(match['status'], '?')
            amd_name = match['amd_name'][:44] if match['amd_name'] != '-' else '-'
            nv_name = match['nv_name'][:44] if match['nv_name'] != '-' else '-'
            lines.append(f"{idx:<4} {amd_name:<45} {nv_name:<45} {status_icon:<8}")
        if not show_all and len(op_matches) > 3:
            lines.append(f"     ... ({len(op_matches) - 3} more {op_type} pairs)")
        lines.append("")
    return 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."""
    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
