"""Analysis Intent - 5-Step MAE/MFE Workflow Analysis.

Each intent represents a complete analysis workflow that generates
figures, statistics, and insights.

Intents:
    A. volatility_stability - 波動穩定性分析
    B. entry_timing - 進場時機分析
    C. exit_placement - 停損停利分析
    D. trailing_breakeven - 追蹤停損/損益兩平分析
    E. position_sizing - 加減碼分析
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING

import polars as pl

if TYPE_CHECKING:
    import plotly.graph_objects as go

    from ..mae_mfe.config import ChartConfig


def _import_plotly() -> tuple:
    """Lazy import Plotly."""
    try:
        import plotly.graph_objects as go
        from plotly.subplots import make_subplots

        return go, make_subplots
    except ImportError as e:
        raise ImportError(
            "Plotly is required for intent analysis. Install with: pip install plotly>=6.3.1"
        ) from e


@dataclass
class PreparedData:
    """Result of data preparation for analysis."""

    df: pl.DataFrame
    original_count: int
    filtered_count: int  # Number of rows filtered due to null volatility

    @property
    def was_filtered(self) -> bool:
        """Whether any rows were filtered."""
        return self.filtered_count > 0


def _prepare_df_for_analysis(
    df: pl.DataFrame,
    normalize: bool,
    columns: list[str] | None = None,
) -> PreparedData:
    """Prepare DataFrame with optional normalization.

    When normalize=True, divides specified columns by their respective
    volatility columns (e.g., mae/mae_volatility) if available.
    Only rows with null volatility for the specified columns are filtered.

    Args:
        df: Input DataFrame.
        normalize: Whether to normalize values.
        columns: List of columns to normalize (e.g., ["mae", "mfe"]).
                 If None, defaults to ["mae", "mfe", "g_mfe", "mhl"].

    Returns:
        PreparedData with filtered df and count of filtered rows.
    """
    original_count = len(df)

    if not normalize:
        return PreparedData(df=df, original_count=original_count, filtered_count=0)

    # Default columns if not specified
    if columns is None:
        columns = ["mae", "mfe", "g_mfe", "mhl", "mae_lv1"]

    # Build normalization pairs from specified columns
    volatility_cols_to_check = []
    exprs = []
    for metric in columns:
        volatility = f"{metric}_volatility"
        if metric in df.columns and volatility in df.columns:
            volatility_cols_to_check.append(volatility)
            # Create normalized column, replacing original
            exprs.append((pl.col(metric) / pl.col(volatility)).alias(metric))

    if not exprs:
        return PreparedData(df=df, original_count=original_count, filtered_count=0)

    # Filter out rows where any required volatility column is null
    filter_expr = pl.lit(True)
    for vol_col in volatility_cols_to_check:
        filter_expr = filter_expr & pl.col(vol_col).is_not_null()

    filtered_df = df.filter(filter_expr)
    filtered_count = original_count - len(filtered_df)

    # Apply normalization
    normalized_df = filtered_df.with_columns(exprs)

    return PreparedData(
        df=normalized_df, original_count=original_count, filtered_count=filtered_count
    )


@dataclass
class StepInsight:
    """Insight from analysis step."""

    observation: str  # What was observed
    recommendation: str  # What to do
    priority: str  # "high", "medium", "low"
    next_step: str | None = None  # Suggested next step


@dataclass
class IntentReport:
    """Report from an analysis intent.

    Contains figures, statistics, and insights from the analysis.
    """

    intent_name: str
    intent_description: str
    figures: list[go.Figure] = field(default_factory=list)
    stats: dict = field(default_factory=dict)
    insights: list[StepInsight] = field(default_factory=list)
    _config: "ChartConfig | None" = None

    def show(self) -> None:
        """Display all figures."""
        for fig in self.figures:
            fig.show()

    def summary(self) -> str:
        """Generate text summary of the analysis."""
        lines = [
            "=" * 60,
            f"Intent: {self.intent_name}",
            f"{self.intent_description}",
            "=" * 60,
        ]

        # Stats
        if self.stats:
            lines.append("\nStatistics:")
            for key, value in self.stats.items():
                if isinstance(value, float):
                    lines.append(f"  {key}: {value:.4f}")
                else:
                    lines.append(f"  {key}: {value}")

        # Insights
        if self.insights:
            lines.append("\nInsights:")
            for i, insight in enumerate(self.insights, 1):
                lines.append(f"  [{insight.priority.upper()}] {insight.observation}")
                lines.append(f"    → {insight.recommendation}")
                if insight.next_step:
                    lines.append(f"    Next: {insight.next_step}")

        lines.append("\n" + "=" * 60)
        return "\n".join(lines)

    def _repr_html_(self) -> str:
        """HTML representation for Jupyter notebooks."""
        insights_html = ""
        for insight in self.insights:
            color = {"high": "red", "medium": "orange", "low": "green"}.get(
                insight.priority, "gray"
            )
            insights_html += f"""
            <div style="margin: 10px 0; padding: 10px; border-left: 3px solid {color};">
                <strong>[{insight.priority.upper()}]</strong> {insight.observation}<br>
                <span style="color: #666;">→ {insight.recommendation}</span>
            </div>
            """

        stats_html = ""
        for key, value in self.stats.items():
            if isinstance(value, float):
                stats_html += f"<tr><td>{key}</td><td>{value:.4f}</td></tr>"
            else:
                stats_html += f"<tr><td>{key}</td><td>{value}</td></tr>"

        return f"""
        <div style="border: 1px solid #ccc; padding: 15px; border-radius: 5px;">
        <h3>{self.intent_name}</h3>
        <p style="color: #666;">{self.intent_description}</p>

        <h4>Statistics</h4>
        <table border="0">{stats_html}</table>

        <h4>Insights</h4>
        {insights_html}

        <p style="color: #999; font-size: 0.9em;">
        {len(self.figures)} figure(s) available. Call .show() to display.
        </p>
        </div>
        """


# =============================================================================
# Intent A: Volatility Stability Analysis (波動穩定性分析)
# =============================================================================


def volatility_stability(
    df: pl.DataFrame,
    config: "ChartConfig",
    *,
    normalize: bool = False,
    bins: int = 30,
) -> IntentReport:
    """A: Analyze volatility stability.

    Steps:
        A1. Observe MAE, MFE, MFE before MAE time series
        A2. Check if MAE is stable
        A3. If MAE and MFE scale together, consider segmenting analysis
        A4. Observe MFE vs MFE before MAE overlap
        A5. Observe MAE/MFE ratio over time
    """
    go, make_subplots = _import_plotly()

    figures = []
    stats = {}
    insights = []

    # Check required columns
    has_exit_time = "exit_time" in df.columns
    has_mae = "mae" in df.columns
    has_mfe = "mfe" in df.columns
    has_g_mfe = "g_mfe" in df.columns

    if not has_mae or not has_mfe:
        return IntentReport(
            intent_name="A. Volatility Stability",
            intent_description="分析策略在整個區間中波動是否穩定",
            stats={"error": "Missing mae or mfe columns"},
        )

    # Prepare data (normalize if requested)
    # Intent A uses: mae, mfe, g_mfe
    norm_cols = ["mae", "mfe"] + (["g_mfe"] if has_g_mfe else [])
    prepared = _prepare_df_for_analysis(df, normalize, columns=norm_cols)
    df = prepared.df

    # Track normalization info
    if prepared.was_filtered:
        stats["normalize_filtered"] = prepared.filtered_count
        stats["normalize_original"] = prepared.original_count

    # === A1: Time series of MAE, MFE, g_mfe ===
    if has_exit_time:
        sorted_df = df.sort("exit_time")
        times = sorted_df["exit_time"].to_list()

        fig1 = go.Figure()
        fig1.add_trace(
            go.Scatter(
                x=times,
                y=sorted_df["mae"].to_list(),
                mode="lines",
                name="MAE",
                line=dict(color=config.color_loser),
            )
        )
        fig1.add_trace(
            go.Scatter(
                x=times,
                y=sorted_df["mfe"].to_list(),
                mode="lines",
                name="MFE (before MAE)",
                line=dict(color=config.color_winner),
            )
        )
        if has_g_mfe:
            fig1.add_trace(
                go.Scatter(
                    x=times,
                    y=sorted_df["g_mfe"].to_list(),
                    mode="lines",
                    name="Global MFE",
                    line=dict(color=config.color_long, dash="dash"),
                )
            )

        fig1.update_layout(
            title="A1: MAE / MFE (before MAE) / Global MFE Time Series",
            xaxis_title="Exit Time",
            yaxis_title="Value",
            template=config.theme,
            width=config.width,
            height=config.height,
        )
        figures.append(fig1)

    # === A2: MAE stability (rolling std) ===
    mae_mean = df["mae"].mean()
    mae_std = df["mae"].std()
    mae_cv = mae_std / mae_mean if mae_mean > 0 else 0  # Coefficient of variation

    stats["mae_mean"] = mae_mean
    stats["mae_std"] = mae_std
    stats["mae_cv"] = mae_cv

    if mae_cv < 0.5:
        insights.append(
            StepInsight(
                observation=f"MAE is stable (CV={mae_cv:.2f} < 0.5)",
                recommendation="MAE stability is good. MFE variation is acceptable.",
                priority="low",
            )
        )
    else:
        insights.append(
            StepInsight(
                observation=f"MAE is unstable (CV={mae_cv:.2f} >= 0.5)",
                recommendation="Consider segmenting analysis by time periods.",
                priority="high",
                next_step="Check A3 for segmentation points",
            )
        )

    # === A3: MAE/MFE correlation (do they scale together?) ===
    if has_exit_time:
        window = min(20, len(df) // 5)
        if window > 1:
            sorted_df = df.sort("exit_time")
            mae_vals = sorted_df["mae"].to_list()
            mfe_vals = sorted_df["mfe"].to_list()

            # Rolling correlation
            rolling_corr = []
            for i in range(window, len(mae_vals)):
                mae_window = mae_vals[i - window : i]
                mfe_window = mfe_vals[i - window : i]
                # Simple correlation
                mae_mean_w = sum(mae_window) / len(mae_window)
                mfe_mean_w = sum(mfe_window) / len(mfe_window)
                cov = sum(
                    (m - mae_mean_w) * (f - mfe_mean_w) for m, f in zip(mae_window, mfe_window)
                ) / len(mae_window)
                mae_std_w = (
                    sum((m - mae_mean_w) ** 2 for m in mae_window) / len(mae_window)
                ) ** 0.5
                mfe_std_w = (
                    sum((f - mfe_mean_w) ** 2 for f in mfe_window) / len(mfe_window)
                ) ** 0.5
                if mae_std_w > 0 and mfe_std_w > 0:
                    rolling_corr.append(cov / (mae_std_w * mfe_std_w))
                else:
                    rolling_corr.append(0)

            fig2 = go.Figure()
            fig2.add_trace(
                go.Scatter(
                    x=list(range(window, len(mae_vals))),
                    y=rolling_corr,
                    mode="lines",
                    name="Rolling MAE/MFE Correlation",
                )
            )
            fig2.add_hline(y=0.7, line_dash="dash", line_color="red")
            fig2.add_hline(y=-0.7, line_dash="dash", line_color="red")
            fig2.update_layout(
                title=f"A3: Rolling MAE/MFE Correlation (window={window})",
                xaxis_title="Trade Index",
                yaxis_title="Correlation",
                template=config.theme,
                width=config.width,
                height=config.height // 2,
            )
            figures.append(fig2)

            # Check if they scale together
            avg_corr = sum(rolling_corr) / len(rolling_corr) if rolling_corr else 0
            stats["mae_mfe_correlation"] = avg_corr

            if avg_corr > 0.7:
                insights.append(
                    StepInsight(
                        observation=f"MAE and MFE scale together (corr={avg_corr:.2f})",
                        recommendation="Consider segmenting analysis at correlation drops.",
                        priority="medium",
                    )
                )

    # === A4: MFE vs g_mfe overlap ===
    if has_g_mfe:
        # Calculate overlap ratio
        overlap_ratios = df.select(
            pl.when(pl.col("mfe") > 0)
            .then(pl.col("g_mfe") / pl.col("mfe"))
            .otherwise(1.0)
            .alias("ratio")
        )["ratio"]
        overlap_score = min(overlap_ratios.mean(), 1.0)
        stats["mfe_g_mfe_overlap"] = overlap_score

        fig3 = make_subplots(
            rows=1, cols=2, subplot_titles=("MFE vs g_mfe Scatter", "Overlap Ratio Distribution")
        )

        fig3.add_trace(
            go.Scatter(
                x=df["g_mfe"].to_list(),
                y=df["mfe"].to_list(),
                mode="markers",
                marker=dict(size=5, opacity=0.5),
                name="g_mfe vs mfe",
            ),
            row=1,
            col=1,
        )
        # Diagonal line
        max_val = max(df["mfe"].max(), df["g_mfe"].max())
        fig3.add_trace(
            go.Scatter(
                x=[0, max_val],
                y=[0, max_val],
                mode="lines",
                line=dict(dash="dash", color="gray"),
                name="y=x",
            ),
            row=1,
            col=1,
        )

        fig3.add_trace(
            go.Histogram(x=overlap_ratios.to_list(), nbinsx=bins, name="Overlap Ratio"),
            row=1,
            col=2,
        )

        fig3.update_layout(
            title="A4: MFE (before MAE) vs Global MFE Overlap Analysis",
            template=config.theme,
            width=config.width,
            height=config.height,
        )
        figures.append(fig3)

        if overlap_score < 0.5:
            insights.append(
                StepInsight(
                    observation=f"Low MFE/g_mfe overlap ({overlap_score:.1%})",
                    recommendation="Prioritize trailing stop analysis (Step D).",
                    priority="high",
                    next_step="Go to trailing_breakeven()",
                )
            )
        else:
            insights.append(
                StepInsight(
                    observation=f"High MFE/g_mfe overlap ({overlap_score:.1%})",
                    recommendation="Prioritize SL/TP analysis (Step C).",
                    priority="medium",
                    next_step="Go to exit_placement()",
                )
            )

    # === A5: MAE/MFE ratio over time ===
    mfe_mean = df["mfe"].mean()
    mae_mfe_ratio = mae_mean / mfe_mean if mfe_mean > 0 else float("inf")
    stats["mae_mfe_ratio"] = mae_mfe_ratio

    if mae_mfe_ratio < 0.5:
        insights.append(
            StepInsight(
                observation=f"MFE dominates (MAE/MFE={mae_mfe_ratio:.2f})",
                recommendation="Focus on capturing potential profit. MAE analysis less critical.",
                priority="low",
            )
        )
    elif mae_mfe_ratio > 2.0:
        insights.append(
            StepInsight(
                observation=f"MAE dominates (MAE/MFE={mae_mfe_ratio:.2f})",
                recommendation="Focus on reducing risk. MFE analysis less critical.",
                priority="high",
            )
        )
    else:
        insights.append(
            StepInsight(
                observation=f"MAE and MFE balanced (ratio={mae_mfe_ratio:.2f})",
                recommendation="Both MAE and MFE analysis are important.",
                priority="medium",
            )
        )

    return IntentReport(
        intent_name="A. Volatility Stability",
        intent_description="分析策略在整個區間中波動是否穩定",
        figures=figures,
        stats=stats,
        insights=insights,
        _config=config,
    )


# =============================================================================
# Intent B: Entry Timing Analysis (進場時機分析)
# =============================================================================


def entry_timing(
    df: pl.DataFrame,
    config: "ChartConfig",
    *,
    normalize: bool = False,
    bins: int = 30,
) -> IntentReport:
    """B: Analyze entry timing.

    Steps:
        B1. Observe MAE/MFE distribution with equal scale
        B2. Analyze upper-right corner ratio
        B3. Determine delay price if needed
        B4. Check for L-shape distribution (inverse relationship)
        B5. Consider time-based delay
    """
    go, make_subplots = _import_plotly()

    figures = []
    stats = {}
    insights = []

    if "mae" not in df.columns or "mfe" not in df.columns:
        return IntentReport(
            intent_name="B. Entry Timing",
            intent_description="判斷能否重新選擇時機與價位",
            stats={"error": "Missing mae or mfe columns"},
        )

    # Prepare data (normalize if requested)
    # Intent B uses: mae, mfe
    prepared = _prepare_df_for_analysis(df, normalize, columns=["mae", "mfe"])
    df = prepared.df

    # Track normalization info
    if prepared.was_filtered:
        stats["normalize_filtered"] = prepared.filtered_count
        stats["normalize_original"] = prepared.original_count

    mae_vals = df["mae"].to_list()
    mfe_vals = df["mfe"].to_list()
    pnl_vals = df["pnl"].to_list() if "pnl" in df.columns else [0] * len(mae_vals)

    # Quantiles
    mae_q1 = df["mae"].quantile(0.25)
    mae_q2 = df["mae"].quantile(0.5)
    mae_q3 = df["mae"].quantile(0.75)
    mfe_q1 = df["mfe"].quantile(0.25)
    mfe_q2 = df["mfe"].quantile(0.5)
    mfe_q3 = df["mfe"].quantile(0.75)

    stats["mae_q1"] = mae_q1
    stats["mae_q2"] = mae_q2
    stats["mae_q3"] = mae_q3
    stats["mfe_q1"] = mfe_q1
    stats["mfe_q2"] = mfe_q2
    stats["mfe_q3"] = mfe_q3

    # === B1: MAE/MFE scatter with equal scale ===
    colors = [config.color_winner if p > 0 else config.color_loser for p in pnl_vals]
    max_val = max(max(mae_vals), max(mfe_vals)) * 1.1

    fig1 = go.Figure()
    fig1.add_trace(
        go.Scatter(
            x=mae_vals,
            y=mfe_vals,
            mode="markers",
            marker=dict(color=colors, size=6, opacity=0.6),
            name="Trades",
        )
    )

    # Diagonal line (MAE = MFE)
    fig1.add_trace(
        go.Scatter(
            x=[0, max_val],
            y=[0, max_val],
            mode="lines",
            line=dict(dash="dot", color="gray"),
            name="MAE=MFE",
        )
    )

    # Quantile lines
    fig1.add_vline(x=mae_q1, line_dash="dash", line_color="blue", annotation_text="MAE Q1")
    fig1.add_vline(x=mae_q3, line_dash="dash", line_color="blue", annotation_text="MAE Q3")
    fig1.add_hline(y=mfe_q1, line_dash="dash", line_color="green", annotation_text="MFE Q1")
    fig1.add_hline(y=mfe_q3, line_dash="dash", line_color="green", annotation_text="MFE Q3")

    fig1.update_layout(
        title="B1: MAE vs MFE Distribution (Equal Scale)",
        xaxis_title="MAE",
        yaxis_title="MFE",
        xaxis=dict(range=[0, max_val]),
        yaxis=dict(range=[0, max_val]),
        template=config.theme,
        width=config.width,
        height=config.width,  # Square
    )
    figures.append(fig1)

    # === B2: Upper-right corner analysis ===
    upper_right_ratio = mae_q3 / mfe_q3 if mfe_q3 > 0 else float("inf")
    stats["upper_right_mae_mfe_ratio"] = upper_right_ratio

    if upper_right_ratio > 2.0:
        insights.append(
            StepInsight(
                observation=f"Upper-right MAE >> MFE (ratio={upper_right_ratio:.2f})",
                recommendation="Strong delay recommended. Consider waiting for better entry.",
                priority="high",
                next_step="Use MAE Q1 as delay price",
            )
        )
        should_delay = True
    elif upper_right_ratio > 1.0:
        insights.append(
            StepInsight(
                observation=f"Upper-right MAE > MFE (ratio={upper_right_ratio:.2f})",
                recommendation="Moderate delay recommended.",
                priority="medium",
            )
        )
        should_delay = True
    else:
        insights.append(
            StepInsight(
                observation=f"Upper-right MFE >= MAE (ratio={upper_right_ratio:.2f})",
                recommendation="No delay needed. Enter immediately.",
                priority="low",
            )
        )
        should_delay = False

    # === B3: Delay price recommendation ===
    if should_delay:
        delay_price = mae_q1
        delay_price_conservative = (mae_q1 + mae_q2) / 2
        stats["suggested_delay_price"] = delay_price
        stats["conservative_delay_price"] = delay_price_conservative

        # Visualization with delay zone
        fig2 = go.Figure()
        fig2.add_trace(
            go.Scatter(
                x=mae_vals,
                y=mfe_vals,
                mode="markers",
                marker=dict(color=colors, size=6, opacity=0.6),
                name="Trades",
            )
        )
        # Delay zone
        fig2.add_vrect(
            x0=0,
            x1=delay_price,
            fillcolor="rgba(0, 255, 0, 0.1)",
            layer="below",
            line_width=0,
        )
        fig2.add_vline(
            x=delay_price,
            line_dash="solid",
            line_color="green",
            annotation_text=f"Delay: {delay_price:.4f}",
        )
        fig2.add_vline(
            x=delay_price_conservative,
            line_dash="dash",
            line_color="green",
            annotation_text=f"Conservative: {delay_price_conservative:.4f}",
        )

        fig2.update_layout(
            title="B3: Suggested Entry Delay Zone",
            xaxis_title="MAE",
            yaxis_title="MFE",
            template=config.theme,
            width=config.width,
            height=config.height,
        )
        figures.append(fig2)

        insights.append(
            StepInsight(
                observation=f"Delay price calculated: {delay_price:.4f}",
                recommendation=f"Wait for price to move {delay_price:.4f} against entry before entering.",
                priority="medium",
            )
        )

    # === B4: L-shape detection (inverse relationship) ===
    corr = df.select(pl.corr("mae", "mfe")).item()
    stats["mae_mfe_correlation"] = corr if corr is not None else 0

    if corr is not None and corr < -0.3:
        insights.append(
            StepInsight(
                observation=f"L-shape distribution detected (corr={corr:.2f})",
                recommendation="Consider trailing stop (Step D) due to inverse relationship.",
                priority="high",
                next_step="Go to trailing_breakeven()",
            )
        )

    # MAE distribution histogram
    fig3 = go.Figure()
    fig3.add_trace(
        go.Histogram(
            x=mae_vals,
            nbinsx=bins,
            marker_color=config.color_loser,
            opacity=0.7,
            name="MAE Distribution",
        )
    )
    if should_delay:
        fig3.add_vline(x=delay_price, line_dash="solid", line_color="green")

    fig3.update_layout(
        title="B3: MAE Distribution (Entry Delay Impact)",
        xaxis_title="MAE",
        yaxis_title="Count",
        template=config.theme,
        width=config.width,
        height=config.height // 2,
    )
    figures.append(fig3)

    return IntentReport(
        intent_name="B. Entry Timing",
        intent_description="判斷能否重新選擇時機與價位",
        figures=figures,
        stats=stats,
        insights=insights,
        _config=config,
    )


# =============================================================================
# Intent C: Exit Placement Analysis (停損停利分析)
# =============================================================================


def exit_placement(
    df: pl.DataFrame,
    config: "ChartConfig",
    *,
    normalize: bool = False,
    bins: int = 30,
) -> IntentReport:
    """C: Analyze stop loss and take profit placement.

    Steps:
        C1. Check if MAE/MFE shows cluster distribution
        C2. For wave trading (MFE before MAE), use g_mfe
        C3. For trend trading, use MFE
        C4. Analyze cluster corners for SL/TP
        C5. Consider tightening SL/TP
        C6. Note: Tightening consumes win rate
    """
    go, make_subplots = _import_plotly()

    figures = []
    stats = {}
    insights = []

    if "mae" not in df.columns or "mfe" not in df.columns:
        return IntentReport(
            intent_name="C. Exit Placement",
            intent_description="判斷應該放多少的停損與停利",
            stats={"error": "Missing mae or mfe columns"},
        )

    has_g_mfe = "g_mfe" in df.columns

    # Prepare data (normalize if requested)
    # Intent C uses: mae, mfe, g_mfe (if available)
    norm_cols = ["mae", "mfe"] + (["g_mfe"] if has_g_mfe else [])
    prepared = _prepare_df_for_analysis(df, normalize, columns=norm_cols)
    df = prepared.df

    # Track normalization info
    if prepared.was_filtered:
        stats["normalize_filtered"] = prepared.filtered_count
        stats["normalize_original"] = prepared.original_count
    mae_vals = df["mae"].to_list()
    mfe_vals = df["mfe"].to_list()
    pnl_vals = df["pnl"].to_list() if "pnl" in df.columns else [0] * len(mae_vals)

    # Quantiles
    mae_q1 = df["mae"].quantile(0.25)
    mae_q2 = df["mae"].quantile(0.5)
    mae_q3 = df["mae"].quantile(0.75)
    mfe_q1 = df["mfe"].quantile(0.25)
    mfe_q2 = df["mfe"].quantile(0.5)
    mfe_q3 = df["mfe"].quantile(0.75)

    # === C2/C3: Determine trading style ===
    if has_g_mfe:
        g_mfe_vals = df["g_mfe"].to_list()
        overlap = df.select(
            pl.when(pl.col("mfe") > 0).then(pl.col("g_mfe") / pl.col("mfe")).otherwise(1.0).mean()
        ).item()

        if overlap < 0.7:
            trading_style = "wave"
            tp_col = "g_mfe"
            tp_vals = g_mfe_vals
            insights.append(
                StepInsight(
                    observation=f"Wave trading detected (MFE before MAE, overlap={overlap:.1%})",
                    recommendation="Use MFE before MAE (g_mfe) for Take Profit analysis.",
                    priority="medium",
                )
            )
        else:
            trading_style = "trend"
            tp_col = "mfe"
            tp_vals = mfe_vals
            insights.append(
                StepInsight(
                    observation=f"Trend trading detected (overlap={overlap:.1%})",
                    recommendation="Use MFE for Take Profit analysis.",
                    priority="medium",
                )
            )
    else:
        trading_style = "trend"
        tp_col = "mfe"
        tp_vals = mfe_vals

    stats["trading_style"] = trading_style

    # === SL/TP Recommendations ===
    # SL from MAE Q3 (cover 75% of adverse moves)
    sl_raw = mae_q3
    # TP from MFE/g_mfe Q2 (median)
    tp_raw = df[tp_col].quantile(0.5)

    stats["suggested_sl"] = sl_raw
    stats["suggested_tp"] = tp_raw

    # Risk/Reward ratio
    rr_ratio = tp_raw / sl_raw if sl_raw > 0 else 0
    stats["risk_reward_ratio"] = rr_ratio

    # Expected win rate for breakeven
    breakeven_wr = sl_raw / (sl_raw + tp_raw) if (sl_raw + tp_raw) > 0 else 0.5
    stats["breakeven_win_rate"] = breakeven_wr

    # Current win rate
    current_wr = len(df.filter(pl.col("pnl") > 0)) / len(df) if len(df) > 0 else 0
    stats["current_win_rate"] = current_wr

    # === C1: Cluster visualization ===
    colors = [config.color_winner if p > 0 else config.color_loser for p in pnl_vals]

    fig1 = go.Figure()
    fig1.add_trace(
        go.Scatter(
            x=mae_vals,
            y=tp_vals,
            mode="markers",
            marker=dict(color=colors, size=6, opacity=0.6),
            name="Trades",
        )
    )

    # SL line
    fig1.add_vline(
        x=sl_raw,
        line_dash="solid",
        line_color=config.color_loser,
        annotation_text=f"SL: {sl_raw:.4f}",
    )

    # TP line
    fig1.add_hline(
        y=tp_raw,
        line_dash="solid",
        line_color=config.color_winner,
        annotation_text=f"TP: {tp_raw:.4f}",
    )

    # Cluster zones
    fig1.add_vrect(
        x0=mae_q3,
        x1=max(mae_vals) * 1.1,
        fillcolor="rgba(255,0,0,0.1)",
        layer="below",
        line_width=0,
    )
    fig1.add_hrect(
        y0=mfe_q3, y1=max(tp_vals) * 1.1, fillcolor="rgba(0,255,0,0.1)", layer="below", line_width=0
    )

    fig1.update_layout(
        title=f"C1: SL/TP Quadrant Analysis (R:R = {rr_ratio:.2f})",
        xaxis_title="MAE",
        yaxis_title=tp_col.upper(),
        template=config.theme,
        width=config.width,
        height=config.height,
    )
    figures.append(fig1)

    # === C4: Upper-right corner analysis ===
    if rr_ratio > 2:
        insights.append(
            StepInsight(
                observation=f"MFE >> MAE at upper-right (R:R={rr_ratio:.2f})",
                recommendation="Consider tightening TP for earlier profit taking.",
                priority="low",
            )
        )
    elif rr_ratio < 0.5:
        insights.append(
            StepInsight(
                observation=f"MAE >> MFE at upper-right (R:R={rr_ratio:.2f})",
                recommendation="Consider tightening SL to reduce losses.",
                priority="high",
            )
        )
    else:
        insights.append(
            StepInsight(
                observation=f"Balanced R:R ratio ({rr_ratio:.2f})",
                recommendation="SL/TP placement is reasonable.",
                priority="medium",
            )
        )

    # === C6: Win rate impact ===
    if current_wr > breakeven_wr:
        margin = current_wr - breakeven_wr
        insights.append(
            StepInsight(
                observation=f"Win rate ({current_wr:.1%}) > Breakeven ({breakeven_wr:.1%})",
                recommendation=f"You have {margin:.1%} margin to tighten SL/TP.",
                priority="low",
            )
        )
    else:
        insights.append(
            StepInsight(
                observation=f"Win rate ({current_wr:.1%}) < Breakeven ({breakeven_wr:.1%})",
                recommendation="Tightening SL/TP will likely hurt performance.",
                priority="high",
            )
        )

    # === SL/TP distribution histograms ===
    fig2 = make_subplots(
        rows=1,
        cols=2,
        subplot_titles=("MAE Distribution (SL)", f"{tp_col.upper()} Distribution (TP)"),
    )

    fig2.add_trace(
        go.Histogram(x=mae_vals, nbinsx=bins, marker_color=config.color_loser, opacity=0.7),
        row=1,
        col=1,
    )
    fig2.add_vline(x=sl_raw, line_dash="solid", line_color="red", row=1, col=1)

    fig2.add_trace(
        go.Histogram(x=tp_vals, nbinsx=bins, marker_color=config.color_winner, opacity=0.7),
        row=1,
        col=2,
    )
    fig2.add_vline(x=tp_raw, line_dash="solid", line_color="green", row=1, col=2)

    fig2.update_layout(
        title="C5: SL/TP Distribution Analysis",
        template=config.theme,
        width=config.width,
        height=config.height // 2,
    )
    figures.append(fig2)

    return IntentReport(
        intent_name="C. Exit Placement",
        intent_description="判斷應該放多少的停損與停利",
        figures=figures,
        stats=stats,
        insights=insights,
        _config=config,
    )


# =============================================================================
# Intent D: Trailing & Breakeven Analysis (追蹤停損/損益兩平分析)
# =============================================================================


def trailing_breakeven(
    df: pl.DataFrame,
    config: "ChartConfig",
    *,
    normalize: bool = False,
    bins: int = 30,
) -> IntentReport:
    """D: Analyze trailing stop and breakeven placement.

    Steps:
        D1. Can test trailing without tightening SL/TP first
        D2. Use lose trade MFE for breakeven trigger
        D3. Use MHL for trailing distance
    """
    go, make_subplots = _import_plotly()

    figures = []
    stats = {}
    insights = []

    if "mae" not in df.columns or "mfe" not in df.columns:
        return IntentReport(
            intent_name="D. Trailing & Breakeven",
            intent_description="判斷追蹤停損、損益兩平停損的合適位置",
            stats={"error": "Missing mae or mfe columns"},
        )

    has_mhl = "mhl" in df.columns

    # Prepare data (normalize if requested)
    # Intent D uses: mae, mfe, mhl (if available)
    norm_cols = ["mae", "mfe"] + (["mhl"] if has_mhl else [])
    prepared = _prepare_df_for_analysis(df, normalize, columns=norm_cols)
    df = prepared.df

    # Track normalization info
    if prepared.was_filtered:
        stats["normalize_filtered"] = prepared.filtered_count
        stats["normalize_original"] = prepared.original_count

    # Split by outcome
    winners = df.filter(pl.col("pnl") > 0)
    losers = df.filter(pl.col("pnl") <= 0)

    # === D1: Check for inverse relationship ===
    corr = df.select(pl.corr("mae", "mfe")).item()
    stats["mae_mfe_correlation"] = corr if corr is not None else 0

    if corr is not None and corr < -0.3:
        insights.append(
            StepInsight(
                observation=f"Inverse MAE/MFE relationship detected (corr={corr:.2f})",
                recommendation="Good candidate for trailing stop strategy.",
                priority="high",
            )
        )
    else:
        insights.append(
            StepInsight(
                observation=f"No strong inverse relationship (corr={corr:.2f})",
                recommendation="Trailing stop may not be as effective.",
                priority="medium",
            )
        )

    # === D2: Breakeven trigger from lose trade MFE ===
    if len(losers) > 0:
        lose_mfe_q1 = losers["mfe"].quantile(0.25)
        lose_mfe_q2 = losers["mfe"].quantile(0.5)
        be_trigger_conservative = (lose_mfe_q1 + lose_mfe_q2) / 2
        be_trigger = lose_mfe_q2

        stats["breakeven_trigger_conservative"] = be_trigger_conservative
        stats["breakeven_trigger"] = be_trigger

        fig1 = go.Figure()
        fig1.add_trace(
            go.Histogram(
                x=losers["mfe"].to_list(),
                nbinsx=bins,
                marker_color=config.color_loser,
                opacity=0.7,
                name="Lose Trade MFE",
            )
        )
        fig1.add_vline(
            x=be_trigger_conservative,
            line_dash="dash",
            line_color="orange",
            annotation_text=f"BE (Q1+Q2)/2: {be_trigger_conservative:.4f}",
        )
        fig1.add_vline(
            x=be_trigger,
            line_dash="solid",
            line_color="orange",
            annotation_text=f"BE Q2: {be_trigger:.4f}",
        )

        fig1.update_layout(
            title="D2: Lose Trade MFE (Breakeven Trigger)",
            xaxis_title="MFE",
            yaxis_title="Count",
            template=config.theme,
            width=config.width,
            height=config.height // 2,
        )
        figures.append(fig1)

        insights.append(
            StepInsight(
                observation=f"Breakeven trigger at {be_trigger:.4f} (Q2 of lose trade MFE)",
                recommendation="Move SL to breakeven when price moves favorably by this amount.",
                priority="medium",
            )
        )

    # === D3: Trailing distance from MHL ===
    if has_mhl:
        mhl_q2 = df["mhl"].quantile(0.5)
        mhl_q3 = df["mhl"].quantile(0.75)

        stats["trailing_distance_q2"] = mhl_q2
        stats["trailing_distance_q3"] = mhl_q3

        fig2 = go.Figure()
        fig2.add_trace(
            go.Histogram(
                x=df["mhl"].to_list(),
                nbinsx=bins,
                marker_color=config.color_neutral,
                opacity=0.7,
                name="MHL Distribution",
            )
        )
        fig2.add_vline(
            x=mhl_q2,
            line_dash="dash",
            line_color="purple",
            annotation_text=f"Q2: {mhl_q2:.4f}",
        )
        fig2.add_vline(
            x=mhl_q3,
            line_dash="solid",
            line_color="purple",
            annotation_text=f"Q3: {mhl_q3:.4f}",
        )

        fig2.update_layout(
            title="D3: MHL Distribution (Trailing Distance)",
            xaxis_title="MHL (Max High-Low)",
            yaxis_title="Count",
            template=config.theme,
            width=config.width,
            height=config.height // 2,
        )
        figures.append(fig2)

        insights.append(
            StepInsight(
                observation=f"Trailing distance: {mhl_q2:.4f} (Q2) to {mhl_q3:.4f} (Q3)",
                recommendation="Use MHL as trailing stop distance from high.",
                priority="medium",
            )
        )
    else:
        # Fallback to MAE for trailing
        mae_q2 = df["mae"].quantile(0.5)
        mae_q3 = df["mae"].quantile(0.75)
        stats["trailing_distance_q2"] = mae_q2
        stats["trailing_distance_q3"] = mae_q3

        insights.append(
            StepInsight(
                observation="MHL not available, using MAE as trailing distance.",
                recommendation=f"Trailing distance: {mae_q2:.4f} (MAE Q2).",
                priority="low",
            )
        )

    # === Combined visualization ===
    if len(losers) > 0:
        pnl_vals = df["pnl"].to_list()
        colors = [config.color_winner if p > 0 else config.color_loser for p in pnl_vals]

        fig3 = go.Figure()
        fig3.add_trace(
            go.Scatter(
                x=df["mae"].to_list(),
                y=df["mfe"].to_list(),
                mode="markers",
                marker=dict(color=colors, size=6, opacity=0.6),
                name="Trades",
            )
        )

        if len(losers) > 0:
            fig3.add_hline(
                y=be_trigger, line_dash="solid", line_color="orange", annotation_text="BE Trigger"
            )

        fig3.update_layout(
            title="D: MAE/MFE with Breakeven Trigger",
            xaxis_title="MAE",
            yaxis_title="MFE",
            template=config.theme,
            width=config.width,
            height=config.height,
        )
        figures.append(fig3)

    return IntentReport(
        intent_name="D. Trailing & Breakeven",
        intent_description="判斷追蹤停損、損益兩平停損的合適位置",
        figures=figures,
        stats=stats,
        insights=insights,
        _config=config,
    )


# =============================================================================
# Intent E: Position Sizing Analysis (加減碼分析)
# =============================================================================


def position_sizing(
    df: pl.DataFrame,
    config: "ChartConfig",
    *,
    normalize: bool = False,
    bins: int = 30,
) -> IntentReport:
    """E: Analyze position sizing (add/reduce).

    Steps:
        E1. Adverse direction add (high win rate, large loss strategy)
        E2. Favorable direction add (high MFE)
        E3. Reduce position between add zone and TP
    """
    go, make_subplots = _import_plotly()

    figures = []
    stats = {}
    insights = []

    if "mae" not in df.columns or "mfe" not in df.columns:
        return IntentReport(
            intent_name="E. Position Sizing",
            intent_description="判斷有利方向／不利方向加與減碼的合適位置",
            stats={"error": "Missing mae or mfe columns"},
        )

    # Prepare data (normalize if requested)
    # Intent E uses: mae, mfe
    prepared = _prepare_df_for_analysis(df, normalize, columns=["mae", "mfe"])
    df = prepared.df

    # Track normalization info
    if prepared.was_filtered:
        stats["normalize_filtered"] = prepared.filtered_count
        stats["normalize_original"] = prepared.original_count

    winners = df.filter(pl.col("pnl") > 0)
    losers = df.filter(pl.col("pnl") <= 0)

    win_rate = len(winners) / len(df) if len(df) > 0 else 0
    stats["win_rate"] = win_rate

    # === E1: Adverse direction add ===
    if len(winners) > 0:
        win_mae_q1 = winners["mae"].quantile(0.25)
        win_mae_q2 = winners["mae"].quantile(0.5)
        win_mae_q2_q3 = (winners["mae"].quantile(0.5) + winners["mae"].quantile(0.75)) / 2

        stats["adverse_add_zone_start"] = win_mae_q1
        stats["adverse_add_zone_end"] = win_mae_q2
        stats["adverse_reduce_zone"] = win_mae_q2_q3

        # Check if adverse add is suitable
        if win_rate > 0.6:
            insights.append(
                StepInsight(
                    observation=f"High win rate ({win_rate:.1%}) - adverse add may be suitable",
                    recommendation=f"Add position when price moves against you by {win_mae_q1:.4f} to {win_mae_q2:.4f}",
                    priority="medium",
                )
            )
        else:
            insights.append(
                StepInsight(
                    observation=f"Win rate ({win_rate:.1%}) not high enough for adverse add",
                    recommendation="Adverse direction add is risky with low win rate.",
                    priority="low",
                )
            )

        fig1 = go.Figure()
        fig1.add_trace(
            go.Histogram(
                x=winners["mae"].to_list(),
                nbinsx=bins,
                marker_color=config.color_winner,
                opacity=0.7,
                name="Win Trade MAE",
            )
        )
        fig1.add_vrect(
            x0=win_mae_q1,
            x1=win_mae_q2,
            fillcolor="rgba(0,255,0,0.2)",
            layer="below",
            line_width=0,
            annotation_text="Add Zone",
        )
        fig1.add_vline(
            x=win_mae_q2_q3, line_dash="dash", line_color="red", annotation_text="Reduce Zone"
        )

        fig1.update_layout(
            title="E1: Win Trade MAE (Adverse Add Zone)",
            xaxis_title="MAE",
            yaxis_title="Count",
            template=config.theme,
            width=config.width,
            height=config.height // 2,
        )
        figures.append(fig1)

    # === E2: Favorable direction add ===
    if len(winners) > 0:
        win_mfe_q2 = winners["mfe"].quantile(0.5)
        win_mfe_q3 = winners["mfe"].quantile(0.75)

        stats["favorable_add_zone_start"] = win_mfe_q2
        stats["favorable_add_zone_end"] = win_mfe_q3

        # Crossover point
        if len(losers) > 0:
            lose_mfe_median = losers["mfe"].median()
            crossover = (win_mfe_q2 + lose_mfe_median) / 2
            stats["favorable_crossover"] = crossover

            insights.append(
                StepInsight(
                    observation=f"Win/Lose MFE crossover at {crossover:.4f}",
                    recommendation="Consider adding position at crossover point.",
                    priority="medium",
                )
            )

        fig2 = make_subplots(rows=1, cols=1)
        fig2.add_trace(
            go.Histogram(
                x=winners["mfe"].to_list(),
                nbinsx=bins,
                marker_color=config.color_winner,
                opacity=0.5,
                name="Win Trade MFE",
            )
        )
        if len(losers) > 0:
            fig2.add_trace(
                go.Histogram(
                    x=losers["mfe"].to_list(),
                    nbinsx=bins,
                    marker_color=config.color_loser,
                    opacity=0.5,
                    name="Lose Trade MFE",
                )
            )

        fig2.add_vrect(
            x0=win_mfe_q2,
            x1=win_mfe_q3,
            fillcolor="rgba(0,0,255,0.1)",
            layer="below",
            line_width=0,
            annotation_text="Favorable Add",
        )

        fig2.update_layout(
            title="E2: Win/Lose Trade MFE (Favorable Add Zone)",
            xaxis_title="MFE",
            yaxis_title="Count",
            barmode="overlay",
            template=config.theme,
            width=config.width,
            height=config.height // 2,
        )
        figures.append(fig2)

        insights.append(
            StepInsight(
                observation=f"Favorable add zone: {win_mfe_q2:.4f} to {win_mfe_q3:.4f}",
                recommendation="Add position when price moves favorably to this range.",
                priority="medium",
            )
        )

    # === E3: Reduce position ===
    if len(winners) > 0 and "favorable_add_zone_end" in stats:
        # Simple rule: reduce at 1/3 between add zone end and estimated TP
        tp_estimate = winners["mfe"].quantile(0.75)
        reduce_zone = (
            stats["favorable_add_zone_end"] + (tp_estimate - stats["favorable_add_zone_end"]) / 3
        )
        stats["reduce_zone"] = reduce_zone

        insights.append(
            StepInsight(
                observation=f"Reduce zone at {reduce_zone:.4f}",
                recommendation="Take partial profit at this level after adding.",
                priority="low",
            )
        )

    return IntentReport(
        intent_name="E. Position Sizing",
        intent_description="判斷有利方向／不利方向加與減碼的合適位置",
        figures=figures,
        stats=stats,
        insights=insights,
        _config=config,
    )


# =============================================================================
# Full Workflow
# =============================================================================


@dataclass
class FullWorkflowReport:
    """Complete A-E workflow report."""

    step_a: IntentReport
    step_b: IntentReport
    step_c: IntentReport
    step_d: IntentReport
    step_e: IntentReport
    n_trades: int
    _config: "ChartConfig | None" = None

    def show(self) -> None:
        """Display all figures from all steps."""
        for step in [self.step_a, self.step_b, self.step_c, self.step_d, self.step_e]:
            step.show()

    def summary(self) -> str:
        """Generate combined summary."""
        lines = [
            "=" * 70,
            "MAE/MFE 5-Step Workflow Analysis",
            f"Total trades: {self.n_trades}",
            "=" * 70,
            "",
            self.step_a.summary(),
            "",
            self.step_b.summary(),
            "",
            self.step_c.summary(),
            "",
            self.step_d.summary(),
            "",
            self.step_e.summary(),
            "",
            "=" * 70,
            "Note: These are general guidelines. Adjust based on your strategy.",
            "=" * 70,
        ]
        return "\n".join(lines)

    def dashboard(self) -> "go.Figure":
        """Create combined dashboard with key charts."""
        go, make_subplots = _import_plotly()

        fig = make_subplots(
            rows=2,
            cols=3,
            subplot_titles=(
                "A: MAE/MFE Time Series",
                "B: Entry Timing",
                "C: SL/TP Quadrant",
                "D: Breakeven Trigger",
                "E: Position Sizing",
                "Summary",
            ),
            specs=[
                [{"type": "scatter"}, {"type": "scatter"}, {"type": "scatter"}],
                [{"type": "histogram"}, {"type": "histogram"}, {"type": "table"}],
            ],
        )

        # Add first figure from each step (if available)
        for i, step in enumerate([self.step_a, self.step_b, self.step_c]):
            if step.figures:
                for trace in step.figures[0].data:
                    fig.add_trace(trace, row=1, col=i + 1)

        for i, step in enumerate([self.step_d, self.step_e]):
            if step.figures:
                for trace in step.figures[0].data:
                    fig.add_trace(trace, row=2, col=i + 1)

        # Summary table
        all_stats = {}
        for step in [self.step_a, self.step_b, self.step_c, self.step_d, self.step_e]:
            all_stats.update(step.stats)

        summary_rows = [
            [k, f"{v:.4f}" if isinstance(v, float) else str(v)]
            for k, v in list(all_stats.items())[:8]
        ]

        if summary_rows:
            fig.add_trace(
                go.Table(
                    header=dict(values=["Metric", "Value"], fill_color="paleturquoise"),
                    cells=dict(
                        values=list(zip(*summary_rows)) if summary_rows else [[], []],
                        fill_color="lavender",
                    ),
                ),
                row=2,
                col=3,
            )

        config = self._config
        if config:
            fig.update_layout(
                title="MAE/MFE 5-Step Workflow Dashboard",
                template=config.theme,
                width=config.width * 1.5,
                height=config.height * 1.2,
                showlegend=False,
            )

        return fig


def full_workflow(
    df: pl.DataFrame,
    config: "ChartConfig",
    *,
    normalize: bool = False,
    bins: int = 30,
) -> FullWorkflowReport:
    """Execute complete A-E workflow analysis."""
    return FullWorkflowReport(
        step_a=volatility_stability(df, config, normalize=normalize, bins=bins),
        step_b=entry_timing(df, config, normalize=normalize, bins=bins),
        step_c=exit_placement(df, config, normalize=normalize, bins=bins),
        step_d=trailing_breakeven(df, config, normalize=normalize, bins=bins),
        step_e=position_sizing(df, config, normalize=normalize, bins=bins),
        n_trades=len(df),
        _config=config,
    )
