"""Loop primitives: SIGALRM time-box, shutdown handlers, interruptible sleep,
and a tiny log_event.

Self-contained — no dependency on the old vendored senpi_runtime_helpers package.
"""

import json
import signal
import sys
import threading
import time
from contextlib import suppress


class _TickTimeout(BaseException):
    """Raised internally when a single tick exceeds its wall-clock budget."""


def log_event(event: str, **fields) -> None:
    """Write a JSON lifecycle event to stderr with a [scaffold] prefix."""
    record = {"event": event, "ts": time.time(), **fields}
    print(f"[scaffold] {json.dumps(record)}", file=sys.stderr, flush=True)


def _install_shutdown_handlers(stop_event: threading.Event) -> None:
    """Install SIGTERM and SIGINT handlers that set stop_event gracefully."""
    def handler(signum: int, _frame: object) -> None:
        if not stop_event.is_set():
            log_event("scaffold_signal", signum=signum, action="graceful_shutdown_requested")
            stop_event.set()

    with suppress(ValueError):  # only installable on the main thread
        signal.signal(signal.SIGTERM, handler)
        signal.signal(signal.SIGINT, handler)


def _arm_tick_alarm(seconds: float) -> bool:
    """Arm SIGALRM to enforce per-tick wall-clock budget. Returns True if armed."""
    if not hasattr(signal, "SIGALRM"):
        return False

    def alarm_handler(_signum: int, _frame: object) -> None:
        raise _TickTimeout(f"tick exceeded {seconds:g}s")

    try:
        signal.signal(signal.SIGALRM, alarm_handler)
        signal.setitimer(signal.ITIMER_REAL, seconds)
        return True
    except (ValueError, OSError):
        return False


def _disarm_tick_alarm() -> None:
    """Disarm any pending SIGALRM timer."""
    if hasattr(signal, "SIGALRM"):
        with suppress(ValueError, OSError):
            signal.setitimer(signal.ITIMER_REAL, 0)


def _interruptible_sleep(seconds: float, stop_event: threading.Event) -> bool:
    """Sleep up to `seconds`, returning early if stop_event fires.

    Returns True if the full sleep elapsed, False if woken early.
    """
    return not stop_event.wait(seconds)
