from __future__ import annotations

from typing import Any, Callable, Mapping, Protocol, runtime_checkable

from .contracts import ExperimentRow


@runtime_checkable
class ExperimentRowMapperProtocol(Protocol):
    """Protocol for row mappers that can inject domain-specific contract logic."""

    def map_row(self, raw_row: Mapping[str, Any]) -> Mapping[str, Any]:
        ...


ExperimentRowMapper = Callable[[Mapping[str, Any]], Mapping[str, Any]] | ExperimentRowMapperProtocol



def map_experiment_row(
    raw_row: Mapping[str, Any],
    *,
    mapper: ExperimentRowMapper | None = None,
) -> ExperimentRow:
    """
    Map a raw row through an optional injected mapper, then enforce ExperimentRow contract.
    """
    if mapper is None:
        mapped = raw_row
    elif callable(mapper):
        mapped = mapper(raw_row)
    elif isinstance(mapper, ExperimentRowMapperProtocol):
        mapped = mapper.map_row(raw_row)
    else:
        raise TypeError(
            "mapper must be callable or implement ExperimentRowMapperProtocol.map_row(raw_row)"
        )

    if not isinstance(mapped, Mapping):
        raise TypeError(f"mapped row must be mapping-like, got {type(mapped)}")

    return ExperimentRow.from_mapping(mapped)


__all__ = [
    "ExperimentRowMapperProtocol",
    "ExperimentRowMapper",
    "map_experiment_row",
]
