"""Typed event-stream signals for the MXAccess Gateway Python client."""

from __future__ import annotations

from dataclasses import dataclass

from .generated import mxaccess_gateway_pb2 as pb


@dataclass(frozen=True, slots=True)
class ReplayGap:
    """Reconnect-replay gap signal surfaced on a resumed event stream.

    The gateway emits this at the head of a stream resumed with
    :meth:`Session.stream_events`'s ``after_worker_sequence`` cursor when the
    requested sequence predates the oldest event still retained in the gateway's
    replay ring. That means the events between ``requested_after_sequence`` and
    ``oldest_available_sequence`` were dropped from the ring and can no longer be
    replayed — the client has an unrecoverable hole in its event history.

    ``ReplayGap`` is a *non-terminal, observable* signal: the stream keeps
    delivering normal :class:`~zb_mom_ww_mxgateway.generated.mxaccess_gateway_pb2.MxEvent`
    values after it. :meth:`Session.stream_events` yields it as a distinct type
    (never as an ``MxEvent``) so a consumer can branch on
    ``isinstance(item, ReplayGap)`` and never mistake a gap for a real MXAccess
    event. The client neither synthesizes nor swallows the gateway's sentinel —
    it only makes that sentinel typed and observable.

    On seeing a gap the consumer must discard any locally cached tag/alarm state
    and re-snapshot (for example via :meth:`Session.read_bulk` or
    :meth:`~zb_mom_ww_mxgateway.GatewayClient.query_active_alarms`). To resume
    the stream without provoking another gap, reconnect with
    ``after_worker_sequence = gap.resume_after_worker_sequence`` (that is,
    ``oldest_available_sequence - 1``) so the next replayed event is the oldest
    the gateway still retains.

    The gateway sets this only on ``StreamEvents`` results — never on a normal
    (non-resumed) stream and never on a ``DrainEvents`` reply.
    """

    requested_after_sequence: int
    """The ``after_worker_sequence`` cursor the resumed stream was opened with."""

    oldest_available_sequence: int
    """Oldest worker sequence the gateway can still replay."""

    @classmethod
    def from_proto(cls, gap: pb.ReplayGap) -> "ReplayGap":
        """Build a :class:`ReplayGap` from the generated ``ReplayGap`` message."""
        return cls(
            requested_after_sequence=gap.requested_after_sequence,
            oldest_available_sequence=gap.oldest_available_sequence,
        )

    @property
    def resume_after_worker_sequence(self) -> int:
        """``after_worker_sequence`` to resume the stream without another gap.

        Equal to ``oldest_available_sequence - 1`` so the next event the gateway
        replays is ``oldest_available_sequence`` — the oldest it still retains.
        Clamped at ``0`` so it is never negative.
        """
        return max(self.oldest_available_sequence - 1, 0)
