"""Scope exploration for TradesAnalyzer.

This module provides tools to explore available data and build analysis scopes
for MAE/MFE workflow analysis.

Example:
    >>> from tradepose_client.analysis import TradesAnalyzer
    >>>
    >>> analyzer = TradesAnalyzer(trades_df)
    >>>
    >>> # Step 1: Explore available options
    >>> blueprints = analyzer.available_blueprints()
    >>> blueprints.to_dataframe()
    >>>
    >>> # Step 2: Build scope with selection
    >>> scope = analyzer.build_scope(blueprints.options[0])
    >>>
    >>> # Step 3: Run analysis intents
    >>> scope.volatility_stability()
    >>> scope.full_workflow()
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING

import polars as pl

from .types import (
    ENTRY_REASON_LABELS,
    EXIT_REASON_LABELS,
)

if TYPE_CHECKING:
    import plotly.graph_objects as go

    from . import intent as intent_module
    from .analyzer import TradesAnalyzer


@dataclass
class OptionSummary:
    """Summary of an available option with statistics."""

    value: str | int
    label: str
    count: int
    pct: float
    total_pnl: float
    avg_pnl: float
    total_pnl_pct: float
    avg_pnl_pct: float
    win_rate: float

    def __str__(self) -> str:
        return (
            f"{self.label}: {self.count} trades ({self.pct:.1%}), "
            f"PnL={self.total_pnl:+,.2f} ({self.total_pnl_pct:+.2%}), WR={self.win_rate:.1%}"
        )


@dataclass
class AvailableOptions:
    """Collection of available options for a category."""

    category: str
    options: list[OptionSummary]
    total_trades: int

    def __str__(self) -> str:
        lines = [
            f"=== {self.category} ({len(self.options)} options, {self.total_trades} total trades) ==="
        ]
        for opt in self.options:
            lines.append(f"  {opt}")
        return "\n".join(lines)

    def to_dataframe(self) -> pl.DataFrame:
        """Convert options to a Polars DataFrame.

        Returns:
            DataFrame with columns: value, label, count, pct, total_pnl, avg_pnl,
            total_pnl_pct, avg_pnl_pct, win_rate

        Example:
            >>> blueprints = analyzer.available_blueprints()
            >>> blueprints.to_dataframe()
        """
        if not self.options:
            return pl.DataFrame()

        return pl.DataFrame(
            [
                {
                    "value": opt.value,
                    "label": opt.label,
                    "count": opt.count,
                    "pct": opt.pct,
                    "total_pnl": opt.total_pnl,
                    "avg_pnl": opt.avg_pnl,
                    "total_pnl_pct": opt.total_pnl_pct,
                    "avg_pnl_pct": opt.avg_pnl_pct,
                    "win_rate": opt.win_rate,
                }
                for opt in self.options
            ]
        )


@dataclass
class ScopeConfig:
    """Configuration for scope filtering.

    Scope filters select DATA RANGE only:
    - strategy, blueprint, period

    Analysis intents (exit_reason, direction, etc.) are handled
    by intent methods on ScopeResult.
    """

    strategies: list[str] | None = None
    blueprints: list[str] | None = None
    periods: list[str] | None = None


def _parse_selection(selection: OptionSummary) -> ScopeConfig:
    """Parse OptionSummary to ScopeConfig.

    Parses the value format:
    - "strategy_name" -> strategy filter
    - "strategy_name / blueprint_name" -> strategy + blueprint filter
    - "strategy_name / blueprint_name / period" -> all three filters
    """
    config = ScopeConfig()
    value = str(selection.value)
    parts = [p.strip() for p in value.split("/")]

    if len(parts) >= 1 and parts[0]:
        config.strategies = [parts[0]]
    if len(parts) >= 2 and parts[1]:
        config.blueprints = [parts[1]]
    if len(parts) >= 3 and parts[2]:
        config.periods = [parts[2]]

    return config


@dataclass
class ScopeResult:
    """Result of scope building with analysis capabilities.

    Attributes:
        trades: Filtered DataFrame.
        config: The scope configuration used.
        n_trades: Number of trades in scope.
        metric_col: Default metric column for performance/plot methods.
        normalize: Default normalization setting for scatter plots.
    """

    _analyzer: "TradesAnalyzer"
    _config: ScopeConfig
    _metric_col: str = "pnl"
    _normalize: bool = True
    _filtered_df: pl.DataFrame = field(init=False)

    def __post_init__(self) -> None:
        """Apply filters to create filtered DataFrame."""
        self._filtered_df = self._apply_filters()

    def _apply_filters(self) -> pl.DataFrame:
        """Apply all configured filters to the DataFrame.

        Only scope filters (strategy, blueprint, period) are applied here.
        Analysis intents are handled by intent methods.
        """
        df = self._analyzer._trades

        # Strategy filter
        if self._config.strategies:
            if "strategy_name" not in df.columns:
                raise ValueError("strategy_name column required for strategy filter")
            df = df.filter(pl.col("strategy_name").is_in(self._config.strategies))

        # Blueprint filter
        if self._config.blueprints:
            if "blueprint_name" not in df.columns:
                raise ValueError("blueprint_name column required for blueprint filter")
            df = df.filter(pl.col("blueprint_name").is_in(self._config.blueprints))

        # Period filter (direct string matching on period column)
        if self._config.periods:
            if "period" not in df.columns:
                raise ValueError("period column required for period filter")
            df = df.filter(pl.col("period").is_in(self._config.periods))

        return df

    @property
    def trades(self) -> pl.DataFrame:
        """Get the filtered trades DataFrame."""
        return self._filtered_df

    @property
    def n_trades(self) -> int:
        """Get the number of trades in scope."""
        return len(self._filtered_df)

    @property
    def config(self) -> ScopeConfig:
        """Get the scope configuration."""
        return self._config

    def _repr_html_(self) -> str:
        """HTML representation for Jupyter notebooks."""
        df = self._filtered_df
        original_count = len(self._analyzer._trades)

        # Build filters HTML
        filters_html = ""
        if self._config.strategies:
            filters_html += f"<li>Strategies: {', '.join(self._config.strategies)}</li>"
        if self._config.blueprints:
            filters_html += f"<li>Blueprints: {', '.join(self._config.blueprints)}</li>"
        if self._config.periods:
            filters_html += f"<li>Periods: {', '.join(self._config.periods)}</li>"

        if not filters_html:
            filters_html = "<li>(No filters - using all trades)</li>"

        # Stats
        if self.n_trades > 0:
            total_pnl = df["pnl"].sum()
            win_count = len(df.filter(pl.col("pnl") > 0))
            win_rate = win_count / self.n_trades
            pnl_color = "green" if total_pnl > 0 else "red"

            stats_html = f"""
            <tr><td>Trades</td><td>{self.n_trades:,} / {original_count:,} ({self.n_trades / original_count:.1%})</td></tr>
            <tr><td>Total PnL</td><td style="color: {pnl_color}">{total_pnl:+,.2f}</td></tr>
            <tr><td>Win Rate</td><td>{win_rate:.1%} ({win_count}W / {self.n_trades - win_count}L)</td></tr>
            """
        else:
            stats_html = "<tr><td colspan='2'>No trades in scope</td></tr>"

        return f"""
        <div style="border: 1px solid #ccc; padding: 10px; border-radius: 5px; max-width: 500px;">
        <h4>Scope Summary</h4>
        <p><strong>Scope Filters:</strong></p>
        <ul>{filters_html}</ul>
        <table border="0" style="margin-top: 10px;">
        {stats_html}
        </table>
        <p style="margin-top: 10px; color: #666; font-size: 0.9em;">
        Call intent methods to analyze: .volatility_stability(), .entry_timing(), etc.
        </p>
        </div>
        """

    def head(self, n: int = 5) -> pl.DataFrame:
        """Preview first n trades in scope.

        Args:
            n: Number of trades to show.

        Returns:
            DataFrame with first n trades.
        """
        return self._filtered_df.head(n)

    def performance(self, metric_col: str | None = None) -> pl.DataFrame:
        """Calculate performance metrics for scoped trades.

        Args:
            metric_col: Column to use for calculations ("pnl" or "pnl_pct").
                        If None, uses the default set in build_scope().

        Returns:
            Single-row DataFrame with performance metrics.

        Example:
            >>> scope = analyzer.build_scope(opt, metric_col="pnl_pct")
            >>> scope.performance()  # uses pnl_pct
            >>> scope.performance(metric_col="pnl")  # override
        """
        from .analyzer import calculate_performance

        col = metric_col if metric_col is not None else self._metric_col
        return calculate_performance(self._filtered_df, col)

    def plot_scatter(
        self,
        *,
        x: "str | pl.Expr" = "mae",
        y: "str | pl.Expr" = "mfe",
        normalize: bool | None = None,
        color_by: str = "outcome",
        hover_cols: list[str] | None = None,
        title: str | None = None,
    ) -> "go.Figure":
        """Create scatter plot for scoped trades.

        Args:
            x: X-axis column name or Polars expression.
            y: Y-axis column name or Polars expression.
            normalize: If True and x/y are strings, normalize by volatility.
                       e.g., "mae" → pl.col("mae") / pl.col("mae_volatility").
                       If None, uses the default set in build_scope().
            color_by: Coloring option ("outcome", "direction", etc.).
            hover_cols: Additional columns to show on hover.
            title: Optional chart title.

        Returns:
            Plotly Figure.

        Example:
            >>> # Use default normalize setting from build_scope()
            >>> scope = analyzer.build_scope(opt, normalize=True)
            >>> scope.plot_scatter(x="mae", y="mfe")  # normalized
            >>>
            >>> # Override for raw values
            >>> scope.plot_scatter(x="mae", y="mfe", normalize=False)
            >>>
            >>> # Custom expression (normalize ignored)
            >>> scope.plot_scatter(
            ...     x=pl.col("mae") / pl.col("entry_price"),
            ...     y=pl.col("mfe") / pl.col("entry_price"),
            ... )
        """
        from ..mae_mfe.config import DEFAULT_CONFIG
        from .components import scatter_component

        # Use default normalize setting if not specified
        do_normalize = normalize if normalize is not None else self._normalize

        # Apply normalization if enabled and using string column names
        x_expr = x
        y_expr = y
        if do_normalize:
            if isinstance(x, str) and f"{x}_volatility" in self._filtered_df.columns:
                x_expr = (pl.col(x) / pl.col(f"{x}_volatility")).alias(f"{x} (norm)")
            if isinstance(y, str) and f"{y}_volatility" in self._filtered_df.columns:
                y_expr = (pl.col(y) / pl.col(f"{y}_volatility")).alias(f"{y} (norm)")

        config = self._analyzer._config or DEFAULT_CONFIG
        return scatter_component(
            self._filtered_df,
            x=x_expr,
            y=y_expr,
            color_by=color_by,
            hover_cols=hover_cols,
            config=config,
            title=title,
        )

    def plot_equity(
        self,
        *,
        metric_col: str | None = None,
        ohlcv: pl.DataFrame | None = None,
        title: str | None = None,
    ) -> "go.Figure":
        """Create equity curve chart with optional benchmark comparison.

        Plots cumulative PnL or return over time. If OHLCV data is provided,
        also plots benchmark performance for comparison.

        Args:
            metric_col: Column to use for cumsum ("pnl" or "pnl_pct").
                        If None, uses the default set in build_scope().
            ohlcv: Optional OHLCV DataFrame with 'ts' (datetime) and 'open' columns.
                   When provided, benchmark is calculated from open prices.
            title: Optional chart title.

        Returns:
            Plotly Figure.

        Example:
            >>> scope = analyzer.build_scope(opt, metric_col="pnl_pct")
            >>> scope.plot_equity()  # uses pnl_pct
            >>> scope.plot_equity(metric_col="pnl")  # override
            >>> scope.plot_equity(ohlcv=ohlcv_df)  # with benchmark
        """
        from ..mae_mfe.config import DEFAULT_CONFIG
        from .components import equity_curve_component

        config = self._analyzer._config or DEFAULT_CONFIG
        col = metric_col if metric_col is not None else self._metric_col
        return equity_curve_component(
            self._filtered_df,
            metric_col=col,
            ohlcv=ohlcv,
            config=config,
            title=title,
        )

    def plot_box(
        self,
        *,
        group_by: str = "exit_reason",
        metric: str | pl.Expr = "pnl",
        show_outcome: bool = True,
        title: str | None = None,
    ) -> "go.Figure":
        """Create box plot of metric grouped by category.

        Args:
            group_by: Column to group by. Options:
                - "exit_reason": Group by exit reason (SL, TP, etc.)
                - "entry_reason": Group by entry reason
                - "blueprint_name": Group by blueprint
                - "strategy_name": Group by strategy
                - "direction": Group by Long/Short
            metric: Metric column or Polars expression.
            show_outcome: If True, split by Win/Lose within each group.
            title: Optional chart title.

        Returns:
            Plotly Figure.

        Example:
            >>> scope.plot_box(group_by="exit_reason", metric="mae")
            >>> scope.plot_box(group_by="blueprint_name", metric="pnl")
            >>> scope.plot_box(group_by="direction", metric="pnl_pct")
        """
        from ..mae_mfe.config import DEFAULT_CONFIG
        from .components import box_component

        config = self._analyzer._config or DEFAULT_CONFIG
        return box_component(
            self._filtered_df,
            metric=metric,
            group_by=group_by,
            show_outcome=show_outcome,
            config=config,
            title=title,
        )

    def plot_distribution(
        self,
        metric: str | pl.Expr = "pnl",
        *,
        by_outcome: bool = True,
        bins: int = 50,
        title: str | None = None,
    ) -> "go.Figure":
        """Create distribution histogram of a metric.

        Args:
            metric: Metric column or Polars expression.
            by_outcome: If True, show separate histograms for Win/Lose.
            bins: Number of histogram bins.
            title: Optional chart title.

        Returns:
            Plotly Figure.

        Example:
            >>> scope.plot_distribution("pnl")
            >>> scope.plot_distribution("mae", by_outcome=True)
            >>> scope.plot_distribution(pl.col("mfe") / pl.col("mfe_volatility"))
        """
        from ..mae_mfe.config import DEFAULT_CONFIG
        from .components import distribution_component

        config = self._analyzer._config or DEFAULT_CONFIG
        return distribution_component(
            self._filtered_df,
            metric=metric,
            by_outcome=by_outcome,
            bins=bins,
            config=config,
            title=title,
        )

    # ==========================================================================
    # Intent Methods - 5-Step MAE/MFE Workflow Analysis
    # ==========================================================================

    def volatility_stability(
        self,
        *,
        normalize: bool = False,
        bins: int = 30,
    ) -> "intent_module.IntentReport":
        """A: Analyze volatility stability.

        Steps:
            A1. Observe MAE, MFE, Global MFE time series
            A2. Check if MAE is stable
            A3. If MAE and MFE scale together, consider segmenting
            A4. Observe MFE vs Global MFE overlap
            A5. Observe MAE/MFE ratio over time

        Args:
            normalize: If True, use mae/mae_volatility instead of raw mae.
            bins: Number of bins for histograms (default 30).

        Returns:
            IntentReport with figures, stats, and insights.

        Example:
            >>> scope.volatility_stability().show()
            >>> scope.volatility_stability(normalize=True, bins=50).show()
        """
        from ..mae_mfe.config import DEFAULT_CONFIG
        from . import intent as intent_module

        config = self._analyzer._config or DEFAULT_CONFIG
        return intent_module.volatility_stability(
            self._filtered_df, config, normalize=normalize, bins=bins
        )

    def entry_timing(
        self,
        *,
        normalize: bool = False,
        bins: int = 30,
    ) -> "intent_module.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
            B5. Consider time-based delay

        Args:
            normalize: If True, use mae/mae_volatility instead of raw mae.
            bins: Number of bins for histograms (default 30).

        Returns:
            IntentReport with figures, stats, and insights.

        Example:
            >>> scope.entry_timing().show()
            >>> scope.entry_timing(normalize=True, bins=50).show()
        """
        from ..mae_mfe.config import DEFAULT_CONFIG
        from . import intent as intent_module

        config = self._analyzer._config or DEFAULT_CONFIG
        return intent_module.entry_timing(self._filtered_df, config, normalize=normalize, bins=bins)

    def exit_placement(
        self,
        *,
        normalize: bool = False,
        bins: int = 30,
    ) -> "intent_module.IntentReport":
        """C: Analyze stop loss and take profit placement.

        Steps:
            C1. Check if MAE/MFE shows cluster distribution
            C2. For wave trading, use Global 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

        Args:
            normalize: If True, use mae/mae_volatility instead of raw mae.
            bins: Number of bins for histograms (default 30).

        Returns:
            IntentReport with figures, stats, and insights.

        Example:
            >>> scope.exit_placement().show()
            >>> scope.exit_placement(normalize=True, bins=50).show()
        """
        from ..mae_mfe.config import DEFAULT_CONFIG
        from . import intent as intent_module

        config = self._analyzer._config or DEFAULT_CONFIG
        return intent_module.exit_placement(
            self._filtered_df, config, normalize=normalize, bins=bins
        )

    def trailing_breakeven(
        self,
        *,
        normalize: bool = False,
        bins: int = 30,
    ) -> "intent_module.IntentReport":
        """D: Analyze trailing stop and breakeven placement.

        Steps:
            D1. Check for inverse MAE/MFE relationship
            D2. Use lose trade MFE for breakeven trigger
            D3. Use MHL for trailing distance

        Args:
            normalize: If True, use mae/mae_volatility instead of raw mae.
            bins: Number of bins for histograms (default 30).

        Returns:
            IntentReport with figures, stats, and insights.

        Example:
            >>> scope.trailing_breakeven().show()
            >>> scope.trailing_breakeven(normalize=True, bins=50).show()
        """
        from ..mae_mfe.config import DEFAULT_CONFIG
        from . import intent as intent_module

        config = self._analyzer._config or DEFAULT_CONFIG
        return intent_module.trailing_breakeven(
            self._filtered_df, config, normalize=normalize, bins=bins
        )

    def position_sizing(
        self,
        *,
        normalize: bool = False,
        bins: int = 30,
    ) -> "intent_module.IntentReport":
        """E: Analyze position sizing (add/reduce).

        Steps:
            E1. Adverse direction add (high win rate)
            E2. Favorable direction add (high MFE)
            E3. Reduce position between add zone and TP

        Args:
            normalize: If True, use mae/mae_volatility instead of raw mae.
            bins: Number of bins for histograms (default 30).

        Returns:
            IntentReport with figures, stats, and insights.

        Example:
            >>> scope.position_sizing().show()
            >>> scope.position_sizing(normalize=True, bins=50).show()
        """
        from ..mae_mfe.config import DEFAULT_CONFIG
        from . import intent as intent_module

        config = self._analyzer._config or DEFAULT_CONFIG
        return intent_module.position_sizing(
            self._filtered_df, config, normalize=normalize, bins=bins
        )

    def full_workflow(
        self,
        *,
        normalize: bool = False,
        bins: int = 30,
    ) -> "intent_module.FullWorkflowReport":
        """Execute complete A-E workflow analysis.

        Runs all 5 steps in sequence:
            A. Volatility Stability
            B. Entry Timing
            C. Exit Placement
            D. Trailing & Breakeven
            E. Position Sizing

        Args:
            normalize: If True, use mae/mae_volatility instead of raw mae.
            bins: Number of bins for histograms (default 30).

        Returns:
            FullWorkflowReport with all 5 step reports.

        Example:
            >>> report = scope.full_workflow()
            >>> report.summary()
            >>> report.full_workflow(normalize=True, bins=50).dashboard().show()
        """
        from ..mae_mfe.config import DEFAULT_CONFIG
        from . import intent as intent_module

        config = self._analyzer._config or DEFAULT_CONFIG
        return intent_module.full_workflow(
            self._filtered_df, config, normalize=normalize, bins=bins
        )


def _calculate_option_summary(
    df: pl.DataFrame,
    group_col: str,
    total_trades: int,
    label_map: dict[int, str] | None = None,
) -> list[OptionSummary]:
    """Calculate summary statistics for each option in a column."""
    if group_col not in df.columns:
        return []

    # Check if pnl_pct column exists
    has_pnl_pct = "pnl_pct" in df.columns

    agg_exprs = [
        pl.len().alias("count"),
        pl.col("pnl").sum().alias("total_pnl"),
        pl.col("pnl").mean().alias("avg_pnl"),
        (pl.col("pnl") > 0).sum().alias("wins"),
    ]
    if has_pnl_pct:
        agg_exprs.extend(
            [
                pl.col("pnl_pct").sum().alias("total_pnl_pct"),
                pl.col("pnl_pct").mean().alias("avg_pnl_pct"),
            ]
        )

    grouped = (
        df.group_by(group_col)
        .agg(*agg_exprs)
        .with_columns(
            (pl.col("count") / total_trades).alias("pct"),
            (pl.col("wins") / pl.col("count")).alias("win_rate"),
        )
        .sort("count", descending=True)
    )

    options = []
    for row in grouped.iter_rows(named=True):
        value = row[group_col]
        if label_map and isinstance(value, int):
            label = label_map.get(value, f"Unknown({value})")
        else:
            label = str(value)

        options.append(
            OptionSummary(
                value=value,
                label=label,
                count=row["count"],
                pct=row["pct"],
                total_pnl=row["total_pnl"],
                avg_pnl=row["avg_pnl"],
                total_pnl_pct=row.get("total_pnl_pct", 0.0),
                avg_pnl_pct=row.get("avg_pnl_pct", 0.0),
                win_rate=row["win_rate"],
            )
        )

    return options


def available_strategies(df: pl.DataFrame) -> AvailableOptions:
    """Get available strategies with statistics."""
    total = len(df)
    options = _calculate_option_summary(df, "strategy_name", total)
    return AvailableOptions("Strategies", options, total)


def available_blueprints(df: pl.DataFrame) -> AvailableOptions:
    """Get available strategy + blueprint combinations with statistics.

    Key format: "strategy_name / blueprint_name"
    """
    total = len(df)

    if "strategy_name" not in df.columns or "blueprint_name" not in df.columns:
        return AvailableOptions("Blueprints", [], total)

    # Check if pnl_pct column exists
    has_pnl_pct = "pnl_pct" in df.columns

    agg_exprs = [
        pl.len().alias("count"),
        pl.col("pnl").sum().alias("total_pnl"),
        pl.col("pnl").mean().alias("avg_pnl"),
        (pl.col("pnl") > 0).sum().alias("wins"),
    ]
    if has_pnl_pct:
        agg_exprs.extend(
            [
                pl.col("pnl_pct").sum().alias("total_pnl_pct"),
                pl.col("pnl_pct").mean().alias("avg_pnl_pct"),
            ]
        )

    # Group by strategy + blueprint combination
    grouped = (
        df.group_by(["strategy_name", "blueprint_name"])
        .agg(*agg_exprs)
        .with_columns(
            (pl.col("count") / total).alias("pct"),
            (pl.col("wins") / pl.col("count")).alias("win_rate"),
        )
        .sort("count", descending=True)
    )

    options = []
    for row in grouped.iter_rows(named=True):
        key = f"{row['strategy_name']} / {row['blueprint_name']}"
        options.append(
            OptionSummary(
                value=key,
                label=key,
                count=row["count"],
                pct=row["pct"],
                total_pnl=row["total_pnl"],
                avg_pnl=row["avg_pnl"],
                total_pnl_pct=row.get("total_pnl_pct", 0.0),
                avg_pnl_pct=row.get("avg_pnl_pct", 0.0),
                win_rate=row["win_rate"],
            )
        )

    return AvailableOptions("Blueprints (Strategy / Blueprint)", options, total)


def available_exit_reasons(df: pl.DataFrame) -> AvailableOptions:
    """Get available exit reasons with statistics."""
    total = len(df)
    options = _calculate_option_summary(df, "exit_reason", total, EXIT_REASON_LABELS)
    return AvailableOptions("Exit Reasons", options, total)


def available_entry_reasons(df: pl.DataFrame) -> AvailableOptions:
    """Get available entry reasons with statistics."""
    total = len(df)
    options = _calculate_option_summary(df, "entry_reason", total, ENTRY_REASON_LABELS)
    return AvailableOptions("Entry Reasons", options, total)


def available_directions(df: pl.DataFrame) -> AvailableOptions:
    """Get available directions with statistics."""
    total = len(df)
    direction_labels = {1: "Long", -1: "Short"}
    options = _calculate_option_summary(df, "direction", total, direction_labels)
    return AvailableOptions("Directions", options, total)


def available_periods(df: pl.DataFrame) -> AvailableOptions:
    """Get available strategy + blueprint + period combinations with statistics.

    Key format: "strategy_name / blueprint_name / period"

    If no period column exists, uses exit_time/entry_time to derive year-month.
    """
    total = len(df)

    if "strategy_name" not in df.columns or "blueprint_name" not in df.columns:
        return AvailableOptions("Periods", [], total)

    # Check if period column exists, otherwise derive from time
    if "period" in df.columns:
        work_df = df
    else:
        # Find time column
        time_col = None
        for col in ["exit_time", "entry_time"]:
            if col in df.columns:
                time_col = col
                break

        if time_col is None:
            return AvailableOptions("Periods", [], total)

        work_df = df.with_columns(pl.col(time_col).dt.strftime("%Y-%m").alias("period"))

    # Check if pnl_pct column exists
    has_pnl_pct = "pnl_pct" in df.columns

    agg_exprs = [
        pl.len().alias("count"),
        pl.col("pnl").sum().alias("total_pnl"),
        pl.col("pnl").mean().alias("avg_pnl"),
        (pl.col("pnl") > 0).sum().alias("wins"),
    ]
    if has_pnl_pct:
        agg_exprs.extend(
            [
                pl.col("pnl_pct").sum().alias("total_pnl_pct"),
                pl.col("pnl_pct").mean().alias("avg_pnl_pct"),
            ]
        )

    # Group by strategy + blueprint + period combination
    grouped = (
        work_df.group_by(["strategy_name", "blueprint_name", "period"])
        .agg(*agg_exprs)
        .with_columns(
            (pl.col("count") / total).alias("pct"),
            (pl.col("wins") / pl.col("count")).alias("win_rate"),
        )
        .sort(["strategy_name", "blueprint_name", "period"])
    )

    options = []
    for row in grouped.iter_rows(named=True):
        key = f"{row['strategy_name']} / {row['blueprint_name']} / {row['period']}"
        options.append(
            OptionSummary(
                value=key,
                label=key,
                count=row["count"],
                pct=row["pct"],
                total_pnl=row["total_pnl"],
                avg_pnl=row["avg_pnl"],
                total_pnl_pct=row.get("total_pnl_pct", 0.0),
                avg_pnl_pct=row.get("avg_pnl_pct", 0.0),
                win_rate=row["win_rate"],
            )
        )

    return AvailableOptions("Periods (Strategy / Blueprint / Period)", options, total)
