# -*- coding: utf-8 -*-
"""
Blocking Transparency Window - Enhanced implementation for Sprint S-5
Provides data-driven transparency for blocking strategy decisions.
"""

# ── Standard library imports ──────────────────────────────────────────────────
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import time
import csv
from typing import Dict, Any, Optional
import logging
import webbrowser

# ── Third-party imports ───────────────────────────────────────────────────────
import pandas as pd

# Conditional imports with fallback
try:
    import psutil

    PSUTIL_AVAILABLE = True
except ImportError:
    PSUTIL_AVAILABLE = False
    logging.warning("psutil not available - memory metrics will be estimated")

try:
    import matplotlib

    matplotlib.use("TkAgg")
    import matplotlib.pyplot as plt
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    from matplotlib.figure import Figure

    MATPLOTLIB_AVAILABLE = True
except ImportError:
    MATPLOTLIB_AVAILABLE = False
    logging.warning("matplotlib not available - charts will be disabled")

try:
    import multiprocessing as mp

    CPU_COUNT = mp.cpu_count() or 1
except:
    CPU_COUNT = 1

# ── Local imports ─────────────────────────────────────────────────────────────
# Import the actual calculators from blocking_metrics instead of duplicating
try:
    from fmatch.core.blocking_metrics import (
        ImpactMetricsCalculator as CoreImpactCalculator,
        DistributionAnalyzer as CoreDistributionAnalyzer,
        calculate_raw_signals,
        normalize_signals,
        quality_score,
        _calculate_gini,
        _calculate_entropy,
    )

    BLOCKING_METRICS_AVAILABLE = True
except ImportError:
    BLOCKING_METRICS_AVAILABLE = False
    logging.warning(
        "blocking_metrics module not available - using fallback calculations"
    )

# Constants from engine.py
_BYTES_PER_PAIR = 100  # From blocking_metrics
DEFAULT_ALGORITHM_KEY = "WRatio"


# ── Helper classes for missing tooltip ────────────────────────────────────────
class ToolTip:
    """Simple tooltip implementation if not available globally."""

    def __init__(self, widget, text):
        self.widget = widget
        self.text = text
        self.tooltip = None
        self.widget.bind("<Enter>", self.on_enter)
        self.widget.bind("<Leave>", self.on_leave)

    def on_enter(self, event=None):
        x, y, _, _ = self.widget.bbox("insert")
        x += self.widget.winfo_rootx() + 25
        y += self.widget.winfo_rooty() + 25

        self.tooltip = tk.Toplevel(self.widget)
        self.tooltip.wm_overrideredirect(True)
        self.tooltip.wm_geometry(f"+{x}+{y}")

        label = tk.Label(
            self.tooltip,
            text=self.text,
            background="lightyellow",
            relief=tk.SOLID,
            borderwidth=1,
        )
        label.pack()

    def on_leave(self, event=None):
        if self.tooltip:
            self.tooltip.destroy()
            self.tooltip = None


class BlockingTransparencyWindow(tk.Toplevel):
    """Enhanced transparency window for blocking decisions with rich metrics."""

    def __init__(
        self,
        parent,
        blocking_result: Dict[str, Any],
        source_df: Optional[pd.DataFrame] = None,
        ref_df: Optional[pd.DataFrame] = None,
        mode: str = "match",
    ):
        """Initialize the blocking transparency window."""
        super().__init__(parent)
        self.title("🔍 Blocking Strategy Analysis")
        self.geometry("900x700")
        self.minsize(800, 600)

        self.transient(parent)
        self.parent = parent
        self.result = blocking_result
        self.source_df = source_df
        self.ref_df = ref_df
        self.mode = mode

        # Initialize components with proper data
        self.impact_calculator = CoreImpactCalculator(
            blocking_result, source_df, ref_df, mode
        )
        self.distribution_analyzer = CoreDistributionAnalyzer(
            blocking_result, source_df, ref_df, mode
        )
        self.what_if_simulator = WhatIfSimulator(
            blocking_result, source_df, ref_df, mode
        )
        self.visualization_manager = VisualizationManager()

        # State
        self.advanced_mode = tk.BooleanVar(value=False)
        self.current_view = "impact"  # Start with impact view to show value first

        # Set up styles
        self._setup_styles()

        self.setup_ui()
        self.bind("<Escape>", lambda e: self.destroy())
        self.center_on_parent(parent)
        self.grab_set()

        # Calculate initial metrics
        self.update_all_metrics()

    def _setup_styles(self):
        """Set up ttk styles with enhanced styling."""
        style = ttk.Style()

        # KEEP your existing button styles
        style.configure("Primary.TButton", background="#0078D7", foreground="white")
        style.map("Primary.TButton", background=[("active", "#005a9e")])

        # ADD these new styles
        style.configure("BTTitle.TLabel", font=("TkDefaultFont", 14, "bold"))
        style.configure("BTCert.TLabel", font=("TkDefaultFont", 11))

    def center_on_parent(self, parent):
        """Center the window on the parent window."""
        self.update_idletasks()
        x = parent.winfo_x() + (parent.winfo_width() // 2) - (self.winfo_width() // 2)
        y = parent.winfo_y() + (parent.winfo_height() // 2) - (self.winfo_height() // 2)
        self.geometry(f"+{x}+{y}")

    def setup_ui(self):
        """Build the comprehensive UI with progressive disclosure."""
        # Main container
        main_frame = ttk.Frame(self, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)

        # Header with confidence badge and mode toggle
        self.create_header(main_frame)

        # Navigation tabs
        self.create_navigation(main_frame)

        # Content area (changes based on selected tab)
        self.content_frame = ttk.Frame(main_frame)
        self.content_frame.pack(fill=tk.BOTH, expand=True, pady=(10, 0))

        # Show initial view
        self.show_impact_view()

        # Bottom action bar
        self.create_action_bar(main_frame)

    def create_header(self, parent):
        """Create header with plain-English certainty label, progress bar & info link."""
        header_frame = ttk.Frame(parent)
        header_frame.pack(fill=tk.X, pady=(0, 10))

        # Configure grid
        header_frame.columnconfigure(1, weight=1)  # Allow middle column to expand

        # Title
        title = ttk.Label(
            header_frame, text="Blocking Strategy Analysis", style="BTTitle.TLabel"
        )
        title.grid(row=0, column=0, sticky="w")

        # Certainty score with visual indicator
        certainty = self._calc_certainty()
        pct_text = f"{certainty:.0%}"
        bar = self._ascii_bar(certainty)

        cert_lbl = ttk.Label(
            header_frame,
            text=f"Model certainty: {pct_text}  {bar}",
            style="BTCert.TLabel",
        )
        cert_lbl.grid(row=0, column=1, sticky="e", padx=(20, 0))

        # Color-code but stay neutral (avoid alarming red)
        if certainty < 0.30:
            cert_lbl.configure(foreground="#C77F00")  # amber
        elif certainty < 0.75:
            cert_lbl.configure(foreground="#3E7BB6")  # blue-grey
        else:
            cert_lbl.configure(foreground="#22863A")  # green

        # Info icon that opens docs or "Why low?" popup
        info = ttk.Label(header_frame, text="  ℹ", cursor="hand2")
        info.grid(row=0, column=2, sticky="w")
        info.bind("<Button-1>", lambda *_: self._on_info_click(certainty))

        # View mode toggle
        toggle_frame = ttk.Frame(header_frame)
        toggle_frame.grid(row=0, column=3, sticky="e", padx=(20, 0))

        ttk.Label(toggle_frame, text="View:").pack(side=tk.LEFT, padx=(0, 5))
        ttk.Radiobutton(
            toggle_frame,
            text="Basic",
            variable=self.advanced_mode,
            value=False,
            command=self.refresh_view,
        ).pack(side=tk.LEFT)
        ttk.Radiobutton(
            toggle_frame,
            text="Advanced",
            variable=self.advanced_mode,
            value=True,
            command=self.refresh_view,
        ).pack(side=tk.LEFT)

    def _calc_certainty(self) -> float:
        """Translate engine's quality_score to intuitive 0-1 certainty."""
        qs = self.result.get("quality_score", 0.0)
        # Simple monotonic mapping; adjust curve as needed
        return max(0.0, min((qs - 0.25) * 2.0, 1.0))

    @staticmethod
    def _ascii_bar(value: float, segs: int = 10) -> str:
        """Create ASCII progress bar."""
        filled = "█" * int(round(value * segs))
        empty = "░" * (segs - len(filled))
        return f"[{filled}{empty}]"

    def _on_info_click(self, certainty: float) -> None:
        """Open docs; if low certainty, preface with a short explainer."""
        if certainty < 0.30:
            proceed = messagebox.askyesno(
                "Low certainty – what it means",
                "Low certainty simply reflects that most domains in the two lists "
                "do not overlap – the optimizer therefore weighs this column down. "
                "It does NOT indicate an error.\n\n"
                "The blocking strategy is still saving significant processing time.\n\n"
                "Open the documentation to learn more?",
                parent=self,
            )
            if not proceed:
                return

        # Open documentation
        try:
            webbrowser.open_new_tab("https://docs.fuzzylab.com/blocking-certainty")
        except Exception:
            messagebox.showinfo(
                "Documentation",
                "Please visit: https://docs.fuzzylab.com/blocking-certainty",
                parent=self,
            )

    def create_navigation(self, parent):
        """Create navigation buttons for different views."""
        nav_frame = ttk.Frame(parent)
        nav_frame.pack(fill=tk.X, pady=(0, 10))

        # Navigation buttons styled as tabs
        self.nav_buttons = {}

        views = [
            ("impact", "💡 Impact", "See time and resource savings"),
            ("distribution", "📊 Distribution", "Analyze block sizes"),
            ("whatif", "🔮 What-If", "Try alternative strategies"),
        ]

        for view_id, label, tooltip in views:
            btn = ttk.Button(
                nav_frame, text=label, command=lambda v=view_id: self.switch_view(v)
            )
            btn.pack(side=tk.LEFT, padx=(0, 5))
            self.nav_buttons[view_id] = btn
            ToolTip(btn, tooltip)

        # Highlight current view
        self.update_nav_buttons()

    def switch_view(self, view_name):
        """Switch between different analysis views."""
        self.current_view = view_name
        self.update_nav_buttons()

        # Clear content frame
        for widget in self.content_frame.winfo_children():
            widget.destroy()

        # Show appropriate view
        if view_name == "impact":
            self.show_impact_view()
        elif view_name == "distribution":
            self.show_distribution_view()
        elif view_name == "whatif":
            self.show_whatif_view()

    def update_nav_buttons(self):
        """Update navigation button styles."""
        for view_id, btn in self.nav_buttons.items():
            if view_id == self.current_view:
                # This style should make it look different/active
                btn.configure(style="Primary.TButton")
            else:
                # This is the default button style
                btn.configure(style="TButton")

    def show_impact_view(self):
        """Show real-world impact metrics."""
        # Calculate metrics
        impact_metrics = self.impact_calculator.calculate()

        # Strategy summary
        summary_frame = ttk.LabelFrame(
            self.content_frame, text="Selected Strategy", padding="10"
        )
        summary_frame.pack(fill=tk.X, pady=(0, 10))

        self.create_strategy_summary(summary_frame)

        # Impact visualization
        impact_frame = ttk.LabelFrame(
            self.content_frame, text="Real-World Impact", padding="10"
        )
        impact_frame.pack(fill=tk.BOTH, expand=True)

        # Create impact cards
        cards_frame = ttk.Frame(impact_frame)
        cards_frame.pack(fill=tk.X, pady=(0, 10))

        # Time savings card
        self.create_impact_card(
            cards_frame,
            "⏱️ Time Savings",
            f"{impact_metrics['time_saved_str']}",
            f"From {impact_metrics['runtime_without_str']} -> {impact_metrics['runtime_with_str']}",
            column=0,
        )

        # Comparison reduction card
        self.create_impact_card(
            cards_frame,
            "🔍 Comparisons",
            f"{impact_metrics['reduction_percentage']:.1f}% fewer",
            f"{impact_metrics['comparisons_without_str']} -> {impact_metrics['comparisons_with_str']}",
            column=1,
        )

        # Memory usage card
        self.create_impact_card(
            cards_frame,
            "💾 Memory Usage",
            f"{impact_metrics['memory_usage_mb']} MB",
            f"Peak usage ({impact_metrics['memory_percentage']:.0f}% of available)",
            column=2,
        )

        # Advanced metrics (only in advanced mode)
        if self.advanced_mode.get():
            self.add_advanced_impact_metrics(impact_frame, impact_metrics)

        # Add sample preview to Impact view as well
        if self.source_df is not None:
            self.show_sample_matches_preview()

    def create_impact_card(self, parent, title, main_text, sub_text, column):
        """Create an impact metric card."""
        card = ttk.Frame(parent, relief="ridge", borderwidth=1)
        card.grid(row=0, column=column, padx=5, sticky="nsew")
        parent.columnconfigure(column, weight=1)

        inner = ttk.Frame(card, padding="15")
        inner.pack(fill=tk.BOTH, expand=True)

        ttk.Label(inner, text=title, font=("TkDefaultFont", 10, "bold")).pack(
            anchor=tk.W
        )

        # Add visual emphasis for good metrics
        main_label = ttk.Label(
            inner, text=main_text, font=("TkDefaultFont", 16, "bold")
        )

        # Color-code based on performance, not confidence
        if "99" in main_text or "8h" in main_text:  # Excellent performance
            main_label.configure(foreground="green")

        main_label.pack(anchor=tk.W, pady=(5, 0))
        ttk.Label(
            inner, text=sub_text, font=("TkDefaultFont", 9), foreground="gray"
        ).pack(anchor=tk.W)

    def add_advanced_impact_metrics(self, parent, metrics):
        """Add detailed metrics table for advanced users."""
        adv_frame = ttk.LabelFrame(parent, text="Detailed Metrics", padding="10")
        adv_frame.pack(fill=tk.BOTH, expand=True, pady=(10, 0))

        # Create metrics tree
        tree = ttk.Treeview(
            adv_frame, columns=("value", "description"), show="tree headings", height=8
        )
        tree.heading("#0", text="Metric")
        tree.heading("value", text="Value")
        tree.heading("description", text="Details")

        tree.column("#0", width=200)
        tree.column("value", width=150)
        tree.column("description", width=300)

        # Add metric categories
        perf = tree.insert("", "end", text="Performance", open=True)
        tree.insert(
            perf,
            "end",
            text="CPU Cores",
            values=(metrics["cpu_cores_utilized"], "Parallel processing capability"),
        )
        tree.insert(
            perf,
            "end",
            text="Throughput",
            values=(
                f"{metrics['comparisons_per_second']:,.0f}/sec",
                "Estimated processing speed",
            ),
        )

        quality = tree.insert("", "end", text="Quality", open=True)
        tree.insert(
            quality,
            "end",
            text="Entropy",
            values=(
                f"{self.result.get('raw_metrics', {}).get('H', 0):.3f}",
                "Key diversity (higher is better)",
            ),
        )
        tree.insert(
            quality,
            "end",
            text="Balance",
            values=(
                f"{1 - self.result.get('raw_metrics', {}).get('G', 0):.3f}",
                "Block size uniformity",
            ),
        )

        tree.pack(fill=tk.BOTH, expand=True)

    def show_distribution_view(self):
        """Show block distribution analysis with histogram."""
        dist_data = self.distribution_analyzer.analyze()

        # Summary stats
        stats_frame = ttk.LabelFrame(
            self.content_frame, text="Distribution Statistics", padding="10"
        )
        stats_frame.pack(fill=tk.X, pady=(0, 10))

        stats_text = f"""Total Blocks: {dist_data['total_blocks']:,}
Largest Block: {dist_data['largest_block_size']:,} records ({dist_data['largest_block_percentage']:.1f}%)
Median Block Size: {dist_data['percentiles']['p50']} records
90th Percentile: {dist_data['percentiles']['p90']} records"""

        ttk.Label(stats_frame, text=stats_text, justify=tk.LEFT).pack(anchor=tk.W)

        # Distribution health indicator
        health_frame = ttk.Frame(stats_frame)
        health_frame.pack(fill=tk.X, pady=(10, 0))

        health_color = {"balanced": "green", "skewed": "orange", "extreme": "red"}.get(
            dist_data["distribution_health"], "black"
        )

        ttk.Label(
            health_frame,
            text=f"Distribution Health: {dist_data['distribution_health'].title()}",
            font=("TkDefaultFont", 11, "bold"),
            foreground=health_color,
        ).pack(side=tk.LEFT)

        # Histogram
        if MATPLOTLIB_AVAILABLE and dist_data.get("block_histogram"):
            hist_frame = ttk.LabelFrame(
                self.content_frame, text="Block Size Distribution", padding="10"
            )
            hist_frame.pack(fill=tk.BOTH, expand=True)

            # Create matplotlib figure
            fig = self.visualization_manager.create_histogram(
                dist_data["block_histogram"], dist_data["percentiles"]
            )

            # Embed in tkinter
            canvas = FigureCanvasTkAgg(fig, master=hist_frame)
            canvas.draw()
            canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
        else:
            # Text-based fallback
            ttk.Label(
                self.content_frame,
                text="Chart visualization not available (matplotlib not installed)",
                foreground="gray",
            ).pack(pady=20)

        # Problem blocks warning
        if dist_data.get("problem_blocks"):
            self.show_problem_blocks_warning(
                self.content_frame, dist_data["problem_blocks"]
            )

        # Sample matches preview
        self.show_sample_matches_preview()

    def show_problem_blocks_warning(self, parent, problem_blocks):
        """Show warning about problematic blocks."""
        warning_frame = ttk.Frame(parent)
        warning_frame.pack(fill=tk.X, pady=(10, 0))

        ttk.Label(
            warning_frame,
            text="⚠️ Problem Blocks Detected:",
            font=("TkDefaultFont", 10, "bold"),
            foreground="orange",
        ).pack(anchor=tk.W)

        for block in problem_blocks[:3]:  # Show top 3
            ttk.Label(
                warning_frame,
                text=f"  • Key '{block['key']}': {block['size']:,} records ({block['percentage']:.1f}%)",
                foreground="gray",
            ).pack(anchor=tk.W)

    def show_sample_matches_preview(self):
        """Show a few example matches to build user confidence."""
        preview_frame = ttk.LabelFrame(
            self.content_frame,
            text="Sample Match Preview (first 5 blocks)",
            padding="10",
        )
        preview_frame.pack(fill=tk.X, pady=(10, 0))

        # Get actual data if available
        if self.source_df is not None and "source_block_col" in self.result:
            block_col = self.result["source_block_col"]

            if block_col in self.source_df.columns:
                try:
                    # Create blocking keys
                    block_key_len = self.result.get("block_key_len", 3)
                    preproc = self.result.get("preproc", "alnum")

                    # Get sample of actual blocking keys
                    sample_df = self.source_df.head(
                        5000
                    ).copy()  # Larger sample to find business domains

                    # Check if this is a domain column and B2C filtering is likely
                    is_domain_column = any(
                        term in block_col.lower()
                        for term in ["domain", "website", "url", "email"]
                    )

                    if is_domain_column:
                        # Filter out common B2C domains before creating blocks
                        b2c_patterns = [
                            "gmail.com",
                            "yahoo.com",
                            "hotmail.com",
                            "outlook.com",
                            "aol.com",
                            "msn.com",
                            "live.com",
                            "icloud.com",
                            "protonmail.com",
                            "proton.me",
                            "ymail.com",
                        ]

                        # Create a mask for non-B2C domains
                        mask = ~sample_df[block_col].astype(
                            str
                        ).str.lower().str.contains("|".join(b2c_patterns), na=False)
                        sample_df = sample_df[mask]

                        # Add note about B2C filtering
                        filter_note = ttk.Label(
                            preview_frame,
                            text="Note: B2C email domains (gmail, yahoo, etc.) are excluded from matching",
                            font=("TkDefaultFont", 9, "italic"),
                            foreground="gray",
                        )
                        filter_note.pack(anchor=tk.W, pady=(0, 5))

                    # Apply preprocessing to get actual block keys
                    if preproc == "alnum":
                        sample_df["_block_key"] = (
                            sample_df[block_col]
                            .astype(str)
                            .str[:block_key_len]
                            .str.lower()
                        )
                    else:
                        # Handle other preprocessing types
                        sample_df["_block_key"] = (
                            sample_df[block_col].astype(str).str[:block_key_len]
                        )

                    # Remove empty or nan block keys
                    sample_df = sample_df[
                        sample_df["_block_key"].notna()
                        & (sample_df["_block_key"] != "")
                    ]

                    # Get unique blocks and their counts
                    block_counts = sample_df["_block_key"].value_counts().head(5)

                    if len(block_counts) == 0:
                        # No business domains found, show message
                        ttk.Label(
                            preview_frame,
                            text="No business domain blocks found in sample. The system will still process all non-B2C domains.",
                            foreground="gray",
                        ).pack(pady=10)
                    else:
                        # Create tree widget for better display
                        tree = ttk.Treeview(
                            preview_frame,
                            columns=("count", "examples"),
                            show="tree headings",
                            height=6,
                        )
                        tree.heading("#0", text="Block Key")
                        tree.heading("count", text="Records")
                        tree.heading("examples", text="Sample Business Domains")

                        tree.column("#0", width=100)
                        tree.column("count", width=80)
                        tree.column("examples", width=400)

                        # Add actual data
                        for block_key, count in block_counts.items():
                            # Get sample values for this block
                            block_samples = (
                                sample_df[sample_df["_block_key"] == block_key][
                                    block_col
                                ]
                                .head(3)
                                .tolist()
                            )

                            # Format examples (show full domain for business context)
                            example_text = " | ".join([str(ex) for ex in block_samples])

                            tree.insert(
                                "", "end", text=block_key, values=(count, example_text)
                            )

                        tree.pack(fill=tk.X, pady=(5, 0))

                        # Add explanation
                        explanation = ttk.Label(
                            preview_frame,
                            text=f"These blocks show business domains from your data. Records in the same block "
                            f"(same first {block_key_len} characters) will be compared for matching.",
                            wraplength=600,
                            foreground="gray",
                        )
                        explanation.pack(anchor=tk.W, pady=(5, 0))

                except Exception as e:
                    # Log the error and show placeholder
                    import logging

                    logging.error(f"Error creating sample preview: {e}")
                    self._show_placeholder_preview(preview_frame)
            else:
                # Fallback to placeholder
                self._show_placeholder_preview(preview_frame)
        else:
            # No data available
            self._show_placeholder_preview(preview_frame)

    def show_whatif_view(self):
        """Show interactive what-if scenario explorer."""
        # Controls frame
        controls_frame = ttk.LabelFrame(
            self.content_frame, text="Adjust Parameters", padding="10"
        )
        controls_frame.pack(fill=tk.X, pady=(0, 10))

        # Key length slider
        length_frame = ttk.Frame(controls_frame)
        length_frame.pack(fill=tk.X, pady=(0, 10))

        ttk.Label(length_frame, text="Block Key Length:").pack(side=tk.LEFT)

        self.length_var = tk.IntVar(value=self.result.get("block_key_len", 3))
        self.length_slider = ttk.Scale(
            length_frame,
            from_=1,
            to=10,
            orient=tk.HORIZONTAL,
            variable=self.length_var,
            command=self.on_whatif_change,
        )
        self.length_slider.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(10, 10))

        self.length_label = ttk.Label(length_frame, text=str(self.length_var.get()))
        self.length_label.pack(side=tk.LEFT)

        # Composite keys option
        self.composite_var = tk.BooleanVar(value=False)
        ttk.Checkbutton(
            controls_frame,
            text="Try Composite Keys (combine multiple columns)",
            variable=self.composite_var,
            command=self.on_whatif_change,
        ).pack(anchor=tk.W)

        # Preprocessing variant
        variant_frame = ttk.Frame(controls_frame)
        variant_frame.pack(fill=tk.X, pady=(10, 0))

        ttk.Label(variant_frame, text="Preprocessing:").pack(side=tk.LEFT)

        self.variant_var = tk.StringVar(value=self.result.get("preproc", "alnum"))
        variant_combo = ttk.Combobox(
            variant_frame,
            textvariable=self.variant_var,
            values=["alnum", "soundex", "first_1_word", "first_2_words"],
            state="readonly",
            width=15,
        )
        variant_combo.pack(side=tk.LEFT, padx=(10, 0))
        variant_combo.bind("<<ComboboxSelected>>", lambda e: self.on_whatif_change())

        # Results comparison
        self.whatif_results_frame = ttk.LabelFrame(
            self.content_frame, text="Comparison", padding="10"
        )
        self.whatif_results_frame.pack(fill=tk.BOTH, expand=True)

        # Initial comparison
        self.update_whatif_comparison()

    def on_whatif_change(self, *args):
        """Handle what-if parameter changes."""
        self.length_label.config(text=str(self.length_var.get()))

        # Debounce updates
        if hasattr(self, "_whatif_timer"):
            self.after_cancel(self._whatif_timer)

        self._whatif_timer = self.after(300, self.update_whatif_comparison)

    def update_whatif_comparison(self):
        """Update what-if scenario comparison."""
        # Clear previous results
        for widget in self.whatif_results_frame.winfo_children():
            widget.destroy()

        # Show loading
        loading_label = ttk.Label(
            self.whatif_results_frame,
            text="Calculating...",
            font=("TkDefaultFont", 10, "italic"),
        )
        loading_label.pack()

        # Schedule actual calculation
        self.after(100, self._perform_whatif_calculation, loading_label)

    def _perform_whatif_calculation(self, loading_label):
        """Perform what-if calculation and update display."""
        # Get current parameters
        params = {
            "key_length": self.length_var.get(),
            "composite": self.composite_var.get(),
            "variant": self.variant_var.get(),
        }

        # Simulate alternative strategy
        alt_metrics = self.what_if_simulator.simulate(params)

        # Remove loading label
        loading_label.destroy()

        # Create comparison display
        self.create_whatif_comparison(alt_metrics)

    def create_whatif_comparison(self, alt_metrics):
        """Create side-by-side comparison of strategies."""
        # Headers
        header_frame = ttk.Frame(self.whatif_results_frame)
        header_frame.pack(fill=tk.X, pady=(0, 10))
        header_frame.columnconfigure(1, weight=1)
        header_frame.columnconfigure(2, weight=1)

        ttk.Label(header_frame, text="", width=20).grid(row=0, column=0)
        ttk.Label(
            header_frame, text="Current Strategy", font=("TkDefaultFont", 11, "bold")
        ).grid(row=0, column=1)
        ttk.Label(
            header_frame,
            text="Alternative Strategy",
            font=("TkDefaultFont", 11, "bold"),
        ).grid(row=0, column=2)

        # Comparison rows
        comparisons = [
            (
                "Key Length",
                self.result.get("block_key_len", "N/A"),
                alt_metrics["key_length"],
            ),
            (
                "Estimated Reduction",
                f"{self.result.get('raw_metrics', {}).get('RR', 0) * 100:.1f}%",
                f"{alt_metrics['reduction_pct']:.1f}%",
            ),
            (
                "Largest Block",
                f"{self.result.get('raw_metrics', {}).get('P1', 0) * 100:.1f}%",
                f"{alt_metrics['largest_block_pct']:.1f}%",
            ),
            (
                "Quality Score",
                f"{self.result.get('quality_score', 0):.3f}",
                f"{alt_metrics['quality_score']:.3f}",
            ),
            (
                "Estimated Runtime",
                self.impact_calculator.calculate()["runtime_with_str"],
                alt_metrics["runtime_str"],
            ),
        ]

        for i, (label, current, alternative) in enumerate(comparisons, start=1):
            ttk.Label(
                header_frame, text=label + ":", font=("TkDefaultFont", 10, "bold")
            ).grid(row=i, column=0, sticky=tk.W, pady=2)

            ttk.Label(header_frame, text=str(current)).grid(row=i, column=1, pady=2)

            # Color code the alternative based on improvement
            alt_label = ttk.Label(header_frame, text=str(alternative))
            alt_label.grid(row=i, column=2, pady=2)

            # Simple comparison logic (would be more sophisticated in production)
            if "Score" in label and float(alt_metrics.get("quality_score", 0)) > float(
                self.result.get("quality_score", 0)
            ):
                alt_label.config(foreground="green")
            elif "Runtime" in label:
                # Compare runtimes (simplified)
                alt_label.config(
                    foreground="green"
                    if "faster" in str(alternative).lower()
                    else "red"
                )

        # Recommendation
        rec_frame = ttk.Frame(self.whatif_results_frame)
        rec_frame.pack(fill=tk.X, pady=(20, 0))

        recommendation = self._generate_whatif_recommendation(alt_metrics)
        rec_label = ttk.Label(
            rec_frame,
            text=recommendation,
            wraplength=600,
            justify=tk.LEFT,
            font=("TkDefaultFont", 10),
        )
        rec_label.pack(anchor=tk.W)

    def _generate_whatif_recommendation(self, alt_metrics):
        """Generate recommendation based on what-if results."""
        current_score = self.result.get("quality_score", 0)
        alt_score = alt_metrics.get("quality_score", 0)

        if alt_score > current_score * 1.1:  # 10% improvement
            return "✅ This alternative strategy shows significant improvement. Consider using these settings."
        elif alt_score > current_score:
            return "↗️ This alternative shows marginal improvement. The current strategy is already well-optimized."
        else:
            return "↘️ The current strategy appears to be optimal for your data."

    def create_strategy_summary(self, parent):
        """Create strategy summary section."""
        source_col = self.result.get("source_block_col", "N/A")
        ref_col = self.result.get("ref_block_col", source_col)

        # Basic info
        info_frame = ttk.Frame(parent)
        info_frame.pack(fill=tk.X)

        # Check if ensemble scoring is enabled
        is_ensemble = (
            self.result.get("suggested_algorithm_display_name") == "Ensemble Score"
        )

        if is_ensemble:
            algo_text = "Ensemble Score (Multi-Algorithm)"
        else:
            algo_text = self.result.get(
                "suggested_algorithm_display_name", DEFAULT_ALGORITHM_KEY
            )

        info_text = f"""Column Mapping: {source_col} -> {ref_col}
    Key Length: {self.result.get('block_key_len', 'N/A')} characters
    Preprocessing: {self.result.get('preproc', 'N/A')}
    Algorithm: {algo_text}"""

        ttk.Label(info_frame, text=info_text, justify=tk.LEFT).pack(
            side=tk.LEFT, anchor=tk.NW
        )

        # If ensemble, show algorithm breakdown
        if is_ensemble and "algorithm_scores" in self.result:
            ensemble_frame = ttk.LabelFrame(
                parent, text="Ensemble Algorithm Selection", padding="10"
            )
            ensemble_frame.pack(fill=tk.X, pady=(10, 0))

            algo_scores = self.result["algorithm_scores"]
            for field, scores in list(algo_scores.items())[:3]:  # Show top 3 fields
                field_text = f"{field}: {scores[0][0]} (score: {scores[0][1]:.2f})"
                ttk.Label(
                    ensemble_frame, text=field_text, font=("TkDefaultFont", 9)
                ).pack(anchor="w")

    def create_quality_indicator(self, parent, score):
        """Create visual quality score indicator."""
        ttk.Label(
            parent, text="Quality Score:", font=("TkDefaultFont", 10, "bold")
        ).pack()

        # Progress bar
        progress_frame = ttk.Frame(parent)
        progress_frame.pack(pady=(5, 0))

        progress = ttk.Progressbar(
            progress_frame, length=150, mode="determinate", value=score * 100
        )
        progress.pack(side=tk.LEFT)

        # Label with color
        quality_text, color = self._get_quality_assessment(score)
        ttk.Label(
            progress_frame,
            text=f" {quality_text}",
            foreground=color,
            font=("TkDefaultFont", 10, "bold"),
        ).pack(side=tk.LEFT, padx=(5, 0))

    def _get_quality_assessment(self, score):
        """Get quality assessment text and color."""
        if score >= 0.7:
            return "Excellent", "green"
        elif score >= 0.5:
            return "Good", "darkorange"
        elif score >= 0.3:
            return "Acceptable", "orange"
        else:
            return "Poor", "red"

    def create_action_bar(self, parent):
        """Create bottom action bar."""
        action_frame = ttk.Frame(parent)
        action_frame.pack(fill=tk.X, pady=(10, 0))

        # Export button
        ttk.Button(action_frame, text="Export Report", command=self.export_report).pack(
            side=tk.LEFT, padx=(0, 5)
        )

        # Help button
        ttk.Button(action_frame, text="Help", command=self.show_help).pack(side=tk.LEFT)

        # Close button
        ttk.Button(action_frame, text="Close", command=self.destroy).pack(side=tk.RIGHT)

    def update_all_metrics(self):
        """Update all calculated metrics."""
        # This would be called initially and after any what-if changes
        pass

    def refresh_view(self):
        """Refresh current view when mode changes."""
        self.switch_view(self.current_view)

    def export_report(self):
        """Export comprehensive analysis report."""
        try:
            filename = filedialog.asksaveasfilename(
                defaultextension=".csv",
                filetypes=[("CSV files", "*.csv"), ("Text files", "*.txt")],
            )

            if filename:
                if filename.endswith(".csv"):
                    self._export_csv_report(filename)
                else:
                    self._export_text_report(filename)

                messagebox.showinfo("Export Complete", f"Report saved to:\n{filename}")

        except Exception as e:
            messagebox.showerror("Export Error", f"Failed to export report: {e}")

    def _export_text_report(self, filename):
        """Export analysis as text report."""
        content = f"""Blocking Strategy Analysis Report
Generated: {time.strftime('%Y-%m-%d %H:%M:%S')}

Strategy Summary:
- Columns: {self.result.get('source_block_col')} -> {self.result.get('ref_block_col')}
- Key Length: {self.result.get('block_key_len')} characters
- Quality Score: {self.result.get('quality_score', 0):.3f}

Impact Analysis:
{self._format_impact_metrics()}

Distribution Analysis:
{self._format_distribution_metrics()}

Recommendations:
{self._format_recommendations()}
"""

        with open(filename, "w") as f:
            f.write(content)

    def _export_csv_report(self, filename):
        """Export metrics as CSV."""
        metrics = self._gather_all_metrics()

        with open(filename, "w", newline="") as f:
            writer = csv.writer(f)
            writer.writerow(["Metric", "Value", "Description"])

            for metric, value, description in metrics:
                writer.writerow([metric, value, description])

    def _format_impact_metrics(self):
        """Format impact metrics for export."""
        metrics = self.impact_calculator.calculate()
        return f"""- Time Savings: {metrics['time_saved_str']}
- Comparison Reduction: {metrics['reduction_percentage']:.1f}%
- Memory Usage: {metrics['memory_usage_mb']} MB"""

    def _format_distribution_metrics(self):
        """Format distribution metrics for export."""
        dist = self.distribution_analyzer.analyze()
        return f"""- Total Blocks: {dist['total_blocks']:,}
- Largest Block: {dist['largest_block_size']:,} records
- Distribution Health: {dist['distribution_health']}"""

    def _format_recommendations(self):
        """Format recommendations for export."""
        recs = []

        raw = self.result.get("raw_metrics", {})
        if raw.get("P1", 0) > 0.3:
            recs.append("- Consider using composite keys to reduce largest block size")
        if raw.get("H", 0) < 0.3:
            recs.append("- Low entropy detected - try different preprocessing")
        if self.result.get("is_fallback"):
            recs.append("- Using fallback strategy - verify field mappings")

        return "\n".join(recs) if recs else "- Current strategy is well-optimized"

    def _gather_all_metrics(self):
        """Gather all metrics for CSV export."""
        metrics = []

        # Add all metrics with descriptions
        raw = self.result.get("raw_metrics", {})
        metrics.extend(
            [
                (
                    "Reduction Ratio",
                    f"{raw.get('RR', 0)*100:.1f}%",
                    "Percentage of comparisons eliminated",
                ),
                ("Entropy", f"{raw.get('H', 0):.3f}", "Diversity of blocking keys"),
                (
                    "Quality Score",
                    f"{self.result.get('quality_score', 0):.3f}",
                    "Overall strategy quality",
                ),
                # Add more metrics...
            ]
        )

        return metrics

    def show_help(self):
        """Show contextual help."""
        help_text = """Understanding Blocking Strategy Analysis:

**Impact View**: Shows real-world benefits of the blocking strategy including time savings and resource usage.

**Distribution View**: Visualizes how records are distributed across blocks. A balanced distribution is ideal.

**What-If View**: Experiment with different parameters to find optimal settings for your data.

**Quality Score**:
- Excellent (>0.7): Optimal strategy
- Good (0.5-0.7): Effective with minor improvements possible
- Acceptable (0.3-0.5): Works but consider optimization
- Poor (<0.3): Significant improvements needed

For more information, see the documentation."""

        messagebox.showinfo("Blocking Analysis Help", help_text)

    def _format_number(self, num):
        """Format large numbers with suffixes."""
        if num >= 1e9:
            return f"{num/1e9:.1f}B"
        elif num >= 1e6:
            return f"{num/1e6:.1f}M"
        elif num >= 1e3:
            return f"{num/1e3:.0f}K"
        else:
            return str(int(num))

    def _format_time(self, seconds):
        """Format time duration."""
        if seconds < 60:
            return f"{int(seconds)}s"
        elif seconds < 3600:
            return f"{int(seconds/60)}m {int(seconds%60)}s"
        else:
            hours = int(seconds / 3600)
            minutes = int((seconds % 3600) / 60)
            return f"{hours}h {minutes}m"


class WhatIfSimulator:
    """Simulates alternative blocking strategies using actual engine functions."""

    def __init__(self, blocking_result, source_df, ref_df, mode):
        self.result = blocking_result
        self.source_df = source_df
        self.ref_df = ref_df
        self.mode = mode

    def simulate(self, params):
        """Simulate alternative strategy with given parameters."""
        key_length = params["key_length"]
        composite = params["composite"]
        variant = params["variant"]

        if self.source_df is None:
            # Return simple heuristic-based simulation
            return self._heuristic_simulation(params)

        try:
            # Import required functions from engine
            from fmatch.core.engine import (
                build_key,
                calculate_raw_signals,
                normalize_signals,
                quality_score,
                BlockingMode,
            )

            # Get the blocking column
            block_col = self.result.get("source_block_col", self.source_df.columns[0])

            # Build new keys with alternative parameters
            new_keys, eff_k = build_key(
                self.source_df[block_col],
                variant,
                key_length,
                self.result.get("ruleset_name", "default"),
            )

            # Calculate raw signals
            mode_enum = (
                BlockingMode.DEDUPE if self.mode == "duplicate" else BlockingMode.MATCH
            )

            ref_keys = None
            if self.mode == "match" and self.ref_df is not None:
                ref_col = self.result.get("ref_block_col", block_col)
                if ref_col in self.ref_df.columns:
                    ref_keys, _ = build_key(
                        self.ref_df[ref_col],
                        variant,
                        key_length,
                        self.result.get("ruleset_name", "default"),
                    )

            raw_signals = calculate_raw_signals(new_keys, eff_k, mode_enum, ref_keys)

            if raw_signals is None:
                return self._heuristic_simulation(params)

            # Normalize and calculate quality score
            normalized = normalize_signals(raw_signals, variant)
            new_score = quality_score(normalized)

            # Extract metrics
            reduction_pct = raw_signals.get("RR", 0) * 100
            largest_block_pct = raw_signals.get("P1", 0) * 100

            # Runtime estimation
            runtime_factor = 1.0 / (reduction_pct / 100) if reduction_pct > 0 else 1.0
            base_runtime = self._get_base_runtime()
            new_runtime = base_runtime * runtime_factor

            return {
                "key_length": key_length,
                "composite": composite,
                "variant": variant,
                "quality_score": new_score,
                "reduction_pct": reduction_pct,
                "largest_block_pct": largest_block_pct,
                "runtime": new_runtime,
                "runtime_str": self._format_time(new_runtime),
            }

        except ImportError:
            # Fallback if engine functions not available
            return self._heuristic_simulation(params)

    def _heuristic_simulation(self, params):
        """Fallback heuristic-based simulation."""
        key_length = params["key_length"]
        composite = params["composite"]
        variant = params["variant"]

        # Simulate quality score change based on parameters
        base_score = self.result.get("quality_score", 0.5)

        # Key length affects score
        if key_length < 3:
            length_factor = 0.8  # Too short
        elif key_length > 6:
            length_factor = 0.9  # Too long
        else:
            length_factor = 1.0

        # Composite keys generally improve distribution
        composite_factor = 1.1 if composite else 1.0

        # Preprocessing variant effect
        variant_factors = {
            "alnum": 1.0,
            "soundex": 0.95,
            "first_1_word": 1.05,
            "first_2_words": 0.9,
        }
        variant_factor = variant_factors.get(variant, 1.0)

        # Calculate new metrics
        new_score = base_score * length_factor * composite_factor * variant_factor
        new_score = min(1.0, max(0.0, new_score))

        # Simulate other metrics
        reduction_pct = 90 + (new_score * 9)
        largest_block_pct = 30 * (1 - new_score)

        # Runtime estimation
        runtime_factor = 1.0 / (reduction_pct / 100)
        base_runtime = self._get_base_runtime()
        new_runtime = base_runtime * runtime_factor

        return {
            "key_length": key_length,
            "composite": composite,
            "variant": variant,
            "quality_score": new_score,
            "reduction_pct": reduction_pct,
            "largest_block_pct": largest_block_pct,
            "runtime": new_runtime,
            "runtime_str": self._format_time(new_runtime),
        }

    def _get_base_runtime(self):
        """Get base runtime from impact calculator."""
        impact_calc = CoreImpactCalculator(
            self.result, self.source_df, self.ref_df, self.mode
        )
        metrics = impact_calc.calculate()
        return metrics["runtime_with"]

    def _format_time(self, seconds):
        """Format time duration."""
        if seconds < 60:
            return f"{int(seconds)}s"
        elif seconds < 3600:
            return f"{int(seconds/60)}m"
        else:
            return f"{int(seconds/3600)}h"


class VisualizationManager:
    """Manages matplotlib visualizations."""

    def create_histogram(self, histogram_data, percentiles):
        """Create block size distribution histogram."""
        if not MATPLOTLIB_AVAILABLE:
            return None

        # Create figure
        fig = Figure(figsize=(8, 4), dpi=100)
        ax = fig.add_subplot(111)

        if not histogram_data:
            ax.text(
                0.5,
                0.5,
                "No distribution data available",
                ha="center",
                va="center",
                transform=ax.transAxes,
            )
            return fig

        # Extract data
        sizes = [d[0] for d in histogram_data]
        counts = [d[1] for d in histogram_data]

        # Create bar chart
        bars = ax.bar(range(len(sizes)), counts, color="steelblue", alpha=0.7)

        # Highlight percentiles
        p90_idx = self._find_nearest_idx(sizes, percentiles["p90"])
        p99_idx = self._find_nearest_idx(sizes, percentiles["p99"])

        if p90_idx < len(bars):
            bars[p90_idx].set_color("orange")
            bars[p90_idx].set_label("90th percentile")

        if p99_idx < len(bars):
            bars[p99_idx].set_color("red")
            bars[p99_idx].set_label("99th percentile")

        # Labels and title
        ax.set_xlabel("Block Size")
        ax.set_ylabel("Count")
        ax.set_title("Block Size Distribution")

        # Set x-axis labels
        step = max(1, len(sizes) // 10)
        ax.set_xticks(range(0, len(sizes), step))
        ax.set_xticklabels(
            [str(sizes[i]) for i in range(0, len(sizes), step)], rotation=45
        )

        # Add grid
        ax.grid(axis="y", alpha=0.3)

        # Legend
        if p90_idx < len(bars) or p99_idx < len(bars):
            ax.legend()

        # Tight layout
        fig.tight_layout()

        return fig

    def _find_nearest_idx(self, array, value):
        """Find index of nearest value in array."""
        if not array:
            return 0
        return min(range(len(array)), key=lambda i: abs(array[i] - value))
