"""Watchpoints and conditional breakpoints for interactive debugging.

Provides an interactive debug session where you can set watchpoints,
step by clock edges, reverse-step, and inspect signal neighborhoods.

Usage:
    dbg = t.debug()
    dbg.watch(t.tb.dut.error, condition=lambda v: v != 0)
    hit = dbg.continue_()
    dbg.context(window=10)
    dbg.step_back(1)
"""

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

if TYPE_CHECKING:
    from ewal.trace import Trace
    from ewal.signal import SignalRef


@dataclass
class Watchpoint:
    signal: Any  # SignalRef
    condition: Optional[Callable] = None
    on_change: bool = False
    _prev_value: Any = None


@dataclass
class WatchpointHit:
    signal: str
    value: Any
    index: int


class DebugSession:
    """Interactive debug session over a trace."""

    def __init__(self, trace: "Trace"):
        self._trace = trace
        self._watchpoints: list[Watchpoint] = []
        self._index = trace._index

    @property
    def index(self) -> int:
        return self._index

    def watch(self, signal, condition: Callable = None, on_change: bool = False):
        """Set a watchpoint on a signal.

        Args:
            signal: SignalRef to watch
            condition: Optional callable(value) -> bool, triggers when True
            on_change: If True, triggers on any value change
        """
        self._watchpoints.append(Watchpoint(
            signal=signal, condition=condition, on_change=on_change,
        ))

    def continue_(self) -> Optional[WatchpointHit]:
        """Step forward until a watchpoint triggers.

        Returns the WatchpointHit, or None if end of trace reached.
        """
        t = self._trace

        while self._index <= t.max_index:
            self._index += 1
            if self._index > t.max_index:
                break

            t._index = self._index
            t._virtual_cache.clear()

            for wp in self._watchpoints:
                val = wp.signal.value
                sig_name = wp.signal._path if hasattr(wp.signal, "_path") else str(wp.signal)

                triggered = False
                if wp.on_change:
                    if wp._prev_value is not None and val != wp._prev_value:
                        triggered = True
                elif wp.condition:
                    if wp.condition(val):
                        triggered = True
                else:
                    if val:
                        triggered = True

                wp._prev_value = val

                if triggered:
                    return WatchpointHit(signal=sig_name, value=val, index=self._index)

        return None

    def continue_until(self, condition) -> Optional[WatchpointHit]:
        """Step forward until a condition is met.

        Args:
            condition: SignalExpr, SignalRef, or callable
        """
        from ewal.signal import SignalExpr, SignalRef, _compile_expr

        t = self._trace

        if isinstance(condition, (SignalExpr, SignalRef)):
            compiled = _compile_expr(condition, t)
            while self._index < t.max_index:
                self._index += 1
                if t._core.eval_expr_bool(compiled, self._index):
                    t._index = self._index
                    t._virtual_cache.clear()
                    return WatchpointHit(signal="condition", value=True, index=self._index)
        elif callable(condition):
            while self._index < t.max_index:
                self._index += 1
                t._index = self._index
                t._virtual_cache.clear()
                if condition():
                    return WatchpointHit(signal="condition", value=True, index=self._index)

        return None

    def step_clock(self, clock, n: int = 1):
        """Advance n rising clock edges."""
        from ewal.signal import SignalRef, _compile_expr

        t = self._trace
        if isinstance(clock, SignalRef):
            compiled = _compile_expr(clock.rising(), t)
            count = 0
            while self._index < t.max_index and count < n:
                self._index += 1
                if t._core.eval_expr_bool(compiled, self._index):
                    count += 1

            t._index = self._index
            t._virtual_cache.clear()

    def step_back(self, n: int = 1):
        """Step backward n indices (reverse stepping)."""
        self._index = max(0, self._index - n)
        self._trace._index = self._index
        self._trace._virtual_cache.clear()

    def context(self, window: int = 10, signals: list[str] = None) -> str:
        """Print signal values in a window around the current index.

        Returns the context as a formatted string.
        """
        t = self._trace
        if signals is None:
            signals = list(t._core.signal_names())[:8]

        start = max(0, self._index - window)
        end = min(t.max_index, self._index + window)

        # Header
        short_names = [s.split(".")[-1][:12] for s in signals]
        header = f"{'idx':>6} {'ts':>8} | " + " | ".join(f"{n:>12}" for n in short_names)
        separator = "-" * len(header)

        lines = [header, separator]

        for idx in range(start, end + 1):
            marker = " >>>" if idx == self._index else "    "
            ts = t._core.timestamp(idx)
            vals = []
            for sig in signals:
                v = t._get_signal_value(sig, idx)
                if isinstance(v, int):
                    vals.append(f"{v:>12}")
                else:
                    vals.append(f"{str(v):>12}")

            line = f"{idx:>6} {ts:>8}{marker} | " + " | ".join(vals)
            lines.append(line)

        result = "\n".join(lines)
        print(result)
        return result


def _patch_debug():
    from ewal.trace import Trace

    def _debug(self) -> DebugSession:
        return DebugSession(self)

    Trace.debug = _debug


_patch_debug()
