"""
Factory Functions for Optuna Components.

Provides builder functions that construct Optuna samplers, pruners,
and callbacks based on configuration strings. Centralizes the
instantiation logic and provides clear error messages for invalid
configurations.

Functions:
    - build_sampler: Create Optuna sampler from type string
    - build_pruner: Create Optuna pruner from config
    - build_callbacks: Construct optimization callbacks list
"""

import logging
from typing import List, cast

import optuna
from optuna.pruners import HyperbandPruner, MedianPruner, NopPruner, PercentilePruner

from orchard.core import LOGGER_NAME, Config

from ..early_stopping import get_early_stopping_callback
from .config import PRUNER_REGISTRY, SAMPLER_REGISTRY

logger = logging.getLogger(LOGGER_NAME)


# CONFIGURATION BUILDERS
def build_sampler(cfg: Config) -> optuna.samplers.BaseSampler:
    """
    Create Optuna sampler from configuration.

    Args:
        cfg: Global configuration with optuna.sampler_type

    Returns:
        Configured Optuna sampler instance

    Raises:
        ValueError: If sampler_type is not in SAMPLER_REGISTRY
    """
    sampler_cls = SAMPLER_REGISTRY.get(cfg.optuna.sampler_type)
    if sampler_cls is None:
        raise ValueError(
            f"Unknown sampler: {cfg.optuna.sampler_type}. "
            f"Valid options: {list(SAMPLER_REGISTRY.keys())}"
        )
    return sampler_cls()


def build_pruner(cfg: Config) -> MedianPruner | PercentilePruner | HyperbandPruner | NopPruner:
    """
    Create Optuna pruner from configuration.

    Args:
        cfg: Global configuration with optuna.enable_pruning and optuna.pruner_type

    Returns:
        Configured Optuna pruner instance (NopPruner if disabled)

    Raises:
        ValueError: If pruner_type is not in PRUNER_REGISTRY
    """
    if not cfg.optuna.enable_pruning:
        return NopPruner()

    pruner_factory = PRUNER_REGISTRY.get(cfg.optuna.pruner_type)
    if pruner_factory is None:
        raise ValueError(
            f"Unknown pruner: {cfg.optuna.pruner_type}. "
            f"Valid options: {list(PRUNER_REGISTRY.keys())}"
        )
    # Type narrowing: PRUNER_REGISTRY values are concrete pruner factories
    return cast(MedianPruner | PercentilePruner | HyperbandPruner | NopPruner, pruner_factory())


def build_callbacks(cfg: Config) -> List:
    """
    Construct list of optimization callbacks from configuration.

    Currently supports:
        - Early stopping callback (based on metric threshold)

    Args:
        cfg: Global configuration with optuna section

    Returns:
        List of Optuna callback objects (may be empty)

    Example:
        >>> callbacks = build_callbacks(cfg)
        >>> len(callbacks)  # 0 or 1 depending on early_stopping config
    """
    early_stop_callback = get_early_stopping_callback(
        direction=cfg.optuna.direction,
        threshold=cfg.optuna.early_stopping_threshold,
        patience=cfg.optuna.early_stopping_patience,
        enabled=cfg.optuna.enable_early_stopping,
        metric_name=cfg.optuna.metric_name,
    )

    return [early_stop_callback] if early_stop_callback else []
