"""cocotb integration — reusable checkers for live and post-hoc analysis.

Write a checker once, use it both in cocotb (live simulation) and
eWAL (post-hoc waveform analysis).

Usage:
    from ewal.cocotb_compat import Checker

    class AXIChecker(Checker):
        def bind(self, signals):
            self.clk = signals["clk"]
            self.valid = signals["valid"]
            ...
        def check(self, t):
            @t.whenever(self.clk)
            def _():
                if self.valid[-1] and not self.valid.value:
                    if not self.ready[-1]:
                        self.fail("valid deasserted without handshake")

    checker = AXIChecker()
    checker.bind({"clk": t.clk, "valid": t.valid, ...})
    checker.check(t)
    t.run()
    print(checker.failures)
"""

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

if TYPE_CHECKING:
    from ewal.trace import Trace


@dataclass
class CheckerFailure:
    message: str
    index: int = -1


class Checker:
    """Base class for reusable signal checkers.

    Subclass and implement bind() and check().
    """

    def __init__(self):
        self.failures: list[CheckerFailure] = []
        self._trace = None

    def bind(self, signals: dict[str, Any]):
        """Bind checker to trace signals. Override in subclass."""
        raise NotImplementedError

    def check(self, t: "Trace"):
        """Register checking logic on the trace. Override in subclass."""
        raise NotImplementedError

    def fail(self, message: str):
        """Record a checker failure at the current trace index."""
        index = self._trace._index if self._trace else -1
        self.failures.append(CheckerFailure(message=message, index=index))

    @property
    def passed(self) -> bool:
        return len(self.failures) == 0

    def run(self, t: "Trace"):
        """Convenience: bind, check, and run in one call."""
        self._trace = t
        self.check(t)
        t.run()
        return self

    def report(self) -> str:
        if self.passed:
            return "PASS: No failures"
        lines = [f"FAIL: {len(self.failures)} failure(s)"]
        for f in self.failures:
            lines.append(f"  [{f.index}] {f.message}")
        return "\n".join(lines)
