# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-must-an-instance-bound-an-evergreen-auto-renewal-window-shorter-than-the-counterparty-evaluation-cycle.md
# Remnant Warrant:    https://corpusfetch.com/c/how-must-an-instance-bound-an-evergreen-auto-renewal-window-shorter-than-the-counterparty-evaluation-cycle.warrant.json
# Markdown SHA-256:   02428989e5e9fe6f9b223aa5cccb586fba50a9f7edb317cb06752fda4c91e1eb
# Code AST SHA-256:   c934894b58848cbc340710f071ae7b773510c4d4f42dbf4efe11eb743260a6bb
# Verified Classes:   EvergreenTrapError, RenewalWindowGate
# Defensive Guards:   1 fail-closed invariants (zero external deps)

from dataclasses import dataclass


class EvergreenTrapError(RuntimeError):
    """Raised when an auto-renewal window cannot accommodate the counterparty's evaluation cycle."""


@dataclass(frozen=True)
class RenewalWindowGate:
    notice_window_days: int
    evaluation_cycle_days: int
    delivery_lag_days: int
    decision_margin_days: int = 5
    opt_in: bool = False

    def verify(self) -> bool:
        if self.opt_in:
            return True
        required = self.evaluation_cycle_days + self.delivery_lag_days + self.decision_margin_days
        if self.notice_window_days < required:
            raise EvergreenTrapError(
                f"window {self.notice_window_days}d < required {required}d: "
                "counterparty cannot evaluate and notice before renewal fires"
            )
        return True


def dispatch_renewal_mode(gate: RenewalWindowGate) -> str:
    try:
        gate.verify()
    except EvergreenTrapError:
        return "convert-to-opt-in"
    return "evergreen"
