"""Transform protocol and chain model.

Transforms are non-destructive operations on Channels. Each transform
takes a Channel (or channels) and returns a new Channel with modified data.
TransformChain composes multiple transforms into a reproducible pipeline.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Protocol, runtime_checkable

from impakt.channel.model import Channel


@runtime_checkable
class Transform(Protocol):
    """Protocol for channel transforms."""

    @property
    def name(self) -> str:
        """Transform identifier (e.g., 'cfc_filter', 'x_align')."""
        ...

    @property
    def params(self) -> dict[str, Any]:
        """Transform parameters for serialization."""
        ...

    def apply(self, channel: Channel) -> Channel:
        """Apply this transform to a channel, returning a new Channel."""
        ...


@dataclass
class TransformChain:
    """An ordered sequence of transforms.

    Immutable once created — append returns a new chain.
    Serializable to/from dict for template storage.
    """

    steps: tuple[Transform, ...] = ()

    def apply(self, channel: Channel) -> Channel:
        """Apply all transforms in sequence."""
        result = channel
        for step in self.steps:
            result = step.apply(result)
        return result

    def append(self, transform: Transform) -> TransformChain:
        """Return a new chain with the transform appended."""
        return TransformChain(steps=(*self.steps, transform))

    def prepend(self, transform: Transform) -> TransformChain:
        """Return a new chain with the transform prepended."""
        return TransformChain(steps=(transform, *self.steps))

    def without(self, index: int) -> TransformChain:
        """Return a new chain with the transform at index removed."""
        steps = list(self.steps)
        steps.pop(index)
        return TransformChain(steps=tuple(steps))

    def serialize(self) -> list[dict[str, Any]]:
        """Serialize to a list of dicts for YAML/JSON storage."""
        return [{"type": step.name, **step.params} for step in self.steps]

    @classmethod
    def deserialize(cls, data: list[dict[str, Any]]) -> TransformChain:
        """Deserialize from a list of dicts.

        Uses the transform registry to resolve types.
        """
        from impakt.transform import _registry

        steps: list[Transform] = []
        for item in data:
            type_name = item.pop("type", "")
            transform = _registry.create(type_name, item)
            if transform is not None:
                steps.append(transform)
        return cls(steps=tuple(steps))

    def __len__(self) -> int:
        return len(self.steps)

    def __repr__(self) -> str:
        if not self.steps:
            return "TransformChain(empty)"
        names = " -> ".join(s.name for s in self.steps)
        return f"TransformChain({names})"


class TransformRegistry:
    """Registry for transform types, enabling deserialization by name."""

    def __init__(self) -> None:
        self._factories: dict[str, type] = {}

    def register(self, name: str, cls: type) -> None:
        self._factories[name] = cls

    def create(self, name: str, params: dict[str, Any]) -> Transform | None:
        cls = self._factories.get(name)
        if cls is None:
            return None
        return cls(**params)

    @property
    def names(self) -> list[str]:
        return list(self._factories.keys())


# Module-level registry (populated by transform submodules)
_registry = TransformRegistry()
