from __future__ import annotations

import polars as pl
from typing import Iterable, Mapping, Callable, Any

from .iv import build_iv_features
from .skew_smile import build_skew_smile_features
from .term_structure import build_term_structure_features
from .liquidity import build_liquidity_features
from .ohlcv import build_ohlcv_features


# Feature registry
FEATURE_BUILDERS: dict[str, Callable[..., pl.DataFrame]] = {
    # order matters
    "ohlcv": build_ohlcv_features,
    "iv": build_iv_features,
    "skew_smile": build_skew_smile_features,
    "term_structure": build_term_structure_features,
    "liquidity": build_liquidity_features,
}

# Orchestrator
def build_features(
    df: pl.DataFrame,
    *,
    include: Iterable[str] | None = None,
    exclude: Iterable[str] | None = None,
    config: Mapping[str, Mapping[str, Any]] | None = None,
) -> pl.DataFrame:
    """
    Builds a model-ready feature table.
    """

    if include is None:
        selected = list(FEATURE_BUILDERS.keys())
    else:
        selected = list(include)

    if exclude is not None:
        selected = [s for s in selected if s not in set(exclude)]

    unknown = set(selected) - FEATURE_BUILDERS.keys()
    if unknown:
        raise ValueError(f"Unknown feature blocks: {sorted(unknown)}")

    cfg = config or {}
    result = df.clone()

    # Apply feature builders in deterministic order
    for name in FEATURE_BUILDERS:
        if name not in selected:
            continue

        builder = FEATURE_BUILDERS[name]
        kwargs = cfg.get(name, {})

        before_cols = set(result.columns)
        result = builder(result, **kwargs)
        after_cols = set(result.columns)

        # sanity check: no column loss
        dropped = before_cols - after_cols
        if dropped:
            raise RuntimeError(
                f"Feature block '{name}' dropped columns: {sorted(dropped)}"
            )

    return result