"""Base protocol decoder framework."""

from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Optional


@dataclass
class Transaction:
    """A decoded protocol transaction."""
    start: int
    end: int
    type: str = ""
    data: dict[str, Any] = field(default_factory=dict)

    def __getattr__(self, name):
        if name in self.__dict__.get("data", {}):
            return self.data[name]
        raise AttributeError(name)


@dataclass
class Violation:
    """A protocol violation."""
    type: str
    cycle: int
    message: str = ""


class Channel:
    """A protocol channel definition."""
    def __init__(self, name: str, **signals):
        self.name = name
        self.signals = signals


class Handshake:
    """A valid/ready handshake channel."""
    def __init__(self, valid: str, ready: str, data: list[str] = None,
                 clock: str = "clk"):
        self.valid = valid
        self.ready = ready
        self.data = data or []
        self.clock = clock


class Protocol:
    """Custom protocol decoder framework."""

    def __init__(self, name: str):
        self.name = name
        self.channels: list[Handshake | Channel] = []

    def add_channel(self, channel: Handshake | Channel):
        self.channels.append(channel)

    def bind(self, **signals) -> "BoundProtocol":
        return BoundProtocol(self, signals)


class BoundProtocol:
    """A protocol bound to actual trace signals."""

    def __init__(self, proto: Protocol, signal_map: dict):
        self.proto = proto
        self.signal_map = signal_map
        self._trace = None
        # Infer trace from signal refs
        for sig in signal_map.values():
            if hasattr(sig, "_trace"):
                self._trace = sig._trace
                break

    def transactions(self) -> list[Transaction]:
        """Extract all transactions by scanning handshake channels."""
        if self._trace is None:
            return []

        results = []
        t = self._trace

        for ch in self.proto.channels:
            if isinstance(ch, Handshake):
                valid_sig = self.signal_map.get(ch.valid)
                ready_sig = self.signal_map.get(ch.ready)
                clk_sig = self.signal_map.get(ch.clock)

                if valid_sig is None or ready_sig is None:
                    continue

                # Find handshake cycles: valid & ready both high
                hs_indices = t.find(valid_sig & ready_sig)
                for idx in hs_indices:
                    data = {}
                    old_idx = t._index
                    t._index = idx
                    t._virtual_cache.clear()
                    for dname in ch.data:
                        dsig = self.signal_map.get(dname)
                        if dsig is not None:
                            data[dname] = dsig.value
                    t._index = old_idx
                    results.append(Transaction(
                        start=idx, end=idx, type="handshake", data=data
                    ))

        return results
