"""
Parameter System for Indicators and Strategies

Provides type-safe, declarative parameter definitions with validation
and UI metadata for frontend rendering.

Usage:
    from sixtysix_core import param

    class MyStrategy(Strategy):
        period = param.number(default=20, label='Period', min=1, max=500, step=1)
        color = param.color(default='#2962ff', label='Line Color')
        enabled = param.boolean(default=True, label='Enable Feature')
        mode = param.select(default='auto', label='Mode', options=[
            {'value': 'auto', 'label': 'Auto'},
            {'value': 'manual', 'label': 'Manual'}
        ])
"""

from __future__ import annotations
from dataclasses import dataclass, field
from typing import Dict, List, Literal, Optional, Union
from typing_extensions import TypedDict


# Type for parameter values (number, color string, boolean, or select string)
ParamValue = Union[int, float, str, bool]


class SelectOption(TypedDict):
    """Type for select dropdown options"""
    value: str
    label: str


@dataclass
class ConfigParam:
    """Defines a configurable parameter for an indicator/strategy"""
    name: str
    label: str
    type: Literal['number', 'color', 'boolean', 'select', 'strategy']
    value: ParamValue
    min_value: Optional[float] = None
    max_value: Optional[float] = None
    step: Optional[float] = None
    options: Optional[List[SelectOption]] = None
    # Additional fields for model/strategy params
    purpose: Optional[str] = None  # For model param: 'regime', 'filter', 'direction'
    category: Optional[str] = None  # For strategy param: filter by category


class InputParam:
    """
    Descriptor for indicator/strategy parameters with metadata.

    This is a Python descriptor that stores parameter values on instances
    while providing metadata for UI generation and validation.
    """

    def __init__(
        self,
        default: ParamValue,
        type: Literal['number', 'color', 'boolean', 'select', 'strategy'] = 'number',
        label: Optional[str] = None,
        min: Optional[float] = None,
        max: Optional[float] = None,
        step: Optional[float] = None,
        options: Optional[List[SelectOption]] = None,
        purpose: Optional[str] = None,  # For model params
        category: Optional[str] = None,  # For strategy params
    ):
        self.default = default
        self.param_type = type
        self.label = label
        self.min_value = min
        self.max_value = max
        self.step = step
        self.options = options
        self.purpose = purpose
        self.category = category
        self.name: Optional[str] = None

    def __set_name__(self, owner, name):
        """Called when the descriptor is assigned to a class attribute."""
        self.name = name
        if self.label is None:
            # Convert snake_case to Title Case
            self.label = name.replace('_', ' ').title()

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return getattr(obj, f'_param_{self.name}', self.default)

    def __set__(self, obj, value):
        setattr(obj, f'_param_{self.name}', value)

    def to_config_param(self) -> ConfigParam:
        """Convert to ConfigParam for API response"""
        return ConfigParam(
            name=self.name,
            label=self.label,
            type=self.param_type,
            value=self.default,
            min_value=self.min_value,
            max_value=self.max_value,
            step=self.step,
            options=self.options,
            purpose=self.purpose,
            category=self.category,
        )


class param:
    """
    Type-specific input parameter factories for indicators and strategies.

    Each method creates an InputParam descriptor with the appropriate type
    and only the parameters relevant to that type.

    Usage:
        class MyStrategy(Strategy):
            period = param.number(default=20, label='Period', min=1, max=500, step=1)
            color = param.color(default='#2962ff', label='Line Color')
            enabled = param.boolean(default=True, label='Enable Feature')
            mode = param.select(default='auto', label='Mode', options=[
                {'value': 'auto', 'label': 'Auto'},
                {'value': 'manual', 'label': 'Manual'}
            ])
    """

    @staticmethod
    def number(
        default: float,
        label: Optional[str] = None,
        min: Optional[float] = None,
        max: Optional[float] = None,
        step: Optional[float] = None
    ) -> InputParam:
        """
        Numeric input parameter.

        Args:
            default: Default value
            label: Display label (auto-generated from field name if not provided)
            min: Minimum allowed value
            max: Maximum allowed value
            step: Step increment for UI slider/input

        Example:
            period = param.number(default=20, label='Period', min=1, max=500, step=1)
        """
        return InputParam(
            default=default,
            type='number',
            label=label,
            min=min,
            max=max,
            step=step
        )

    @staticmethod
    def color(
        default: str,
        label: Optional[str] = None
    ) -> InputParam:
        """
        Color picker input parameter.

        Args:
            default: Default hex color (e.g., '#2962ff')
            label: Display label (auto-generated from field name if not provided)

        Example:
            line_color = param.color(default='#2962ff', label='Line Color')
        """
        return InputParam(
            default=default,
            type='color',
            label=label
        )

    @staticmethod
    def boolean(
        default: bool,
        label: Optional[str] = None
    ) -> InputParam:
        """
        Boolean toggle input parameter.

        Args:
            default: Default value (True/False)
            label: Display label (auto-generated from field name if not provided)

        Example:
            show_labels = param.boolean(default=True, label='Show Labels')
        """
        return InputParam(
            default=default,
            type='boolean',
            label=label
        )

    @staticmethod
    def select(
        default: str,
        options: List[SelectOption],
        label: Optional[str] = None
    ) -> InputParam:
        """
        Dropdown select input parameter.

        Args:
            default: Default selected value
            options: List of option dicts with 'value' and 'label' keys
            label: Display label (auto-generated from field name if not provided)

        Example:
            source = param.select(
                default='close',
                label='Price Source',
                options=[
                    {'value': 'close', 'label': 'Close'},
                    {'value': 'open', 'label': 'Open'},
                    {'value': 'hl2', 'label': 'HL/2'},
                    {'value': 'hlc3', 'label': 'HLC/3'},
                ]
            )
        """
        return InputParam(
            default=default,
            type='select',
            label=label,
            options=options
        )

    @staticmethod
    def timeframe(
        default: str = '1d',
        label: Optional[str] = None
    ) -> InputParam:
        """
        Timeframe selector parameter.

        Args:
            default: Default timeframe (e.g., '1d', '4h', '1w')
            label: Display label (auto-generated from field name if not provided)

        Example:
            htf = param.timeframe(default='1d', label='Higher Timeframe')
        """
        return InputParam(
            default=default,
            type='select',
            label=label,
            options=[
                {'value': '5m', 'label': '5 Minutes'},
                {'value': '15m', 'label': '15 Minutes'},
                {'value': '30m', 'label': '30 Minutes'},
                {'value': '1h', 'label': '1 Hour'},
                {'value': '4h', 'label': '4 Hours'},
                {'value': '1d', 'label': '1 Day'},
                {'value': '1w', 'label': '1 Week'},
            ]
        )

    @staticmethod
    def strategy(
        default: Optional[str] = None,
        label: Optional[str] = None,
        category: Optional[str] = None,
    ) -> InputParam:
        """
        Strategy selector parameter.

        Creates a dropdown that shows available strategies from the registry.
        Useful for meta-strategies that delegate to other strategies.

        Args:
            default: Default strategy name (e.g., 'sma_crossover')
            label: Display label (auto-generated from field name if not provided)
            category: Filter strategies by category (e.g., 'Trend', 'ML Enhanced')
                     If None, shows all strategies

        Example:
            # Select any trend strategy
            trend_strategy = param.strategy(
                default='hl_sma_exit',
                label='Trend Strategy',
                category='Best'
            )

            # Select any strategy
            sub_strategy = param.strategy(label='Sub Strategy')

        Usage in strategy:
            def on_start(self):
                from application.analysis.strategies import get_strategy
                if self.trend_strategy:
                    self._trend_strat = get_strategy(self.trend_strategy)

            def on_bar(self, df):
                # Delegate to sub-strategy
                return self._trend_strat.on_bar(df)
        """
        return InputParam(
            default=default or '',
            type='strategy',
            label=label,
            category=category,
        )


@dataclass
class StrategyConfig:
    """Configuration for a strategy"""
    name: str
    display_name: str
    description: str
    category: str = "General"
    render_components: "List[RenderComponent]" = field(default_factory=list)
    configurable_params: List[ConfigParam] = field(default_factory=list)


# Deferred import to avoid circular dependency
# RenderComponent is used only as a type annotation in StrategyConfig
from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from .indicator import RenderComponent


__all__ = [
    'ParamValue',
    'SelectOption',
    'ConfigParam',
    'InputParam',
    'param',
    'StrategyConfig',
]
