"""Thread-safe synchronization clock runtime.

Provides a pause-aware elapsed-time counter and a shared speed multiplier
that animation streams read every frame.
"""

import threading
import time


class SyncClockRuntime:
    """In-memory clock instance used by animation streams for synchronized timing.

    ``get_time()`` returns pause-aware **real** elapsed seconds (NOT
    speed-scaled).  Streams combine this with ``speed`` themselves so that
    per-frame physics effects (fire cooling, sparkle density) also scale
    with clock speed uniformly.
    """

    __slots__ = ("_speed", "_running", "_epoch", "_offset", "_lock")

    def __init__(self, speed: float = 1.0) -> None:
        self._speed: float = speed
        self._running: bool = True
        self._epoch: float = time.perf_counter()
        self._offset: float = 0.0
        self._lock = threading.Lock()

    # ── Speed ──────────────────────────────────────────────────────

    @property
    def speed(self) -> float:
        """Current speed multiplier (lock-free read; CPython float is atomic)."""
        return self._speed

    @speed.setter
    def speed(self, value: float) -> None:
        self._speed = value

    # ── Time ───────────────────────────────────────────────────────

    def get_time(self) -> float:
        """Pause-aware elapsed seconds since creation/last reset.

        Returns *real* (wall-clock) elapsed time, not speed-scaled.

        Lock-free: under CPython's GIL, reading individual attributes is
        atomic.  pause()/resume() update _offset and _epoch under a lock,
        so the reader sees a consistent pre- or post-update snapshot.
        The worst case is a one-frame stale value, which is imperceptible.
        """
        running = self._running
        offset = self._offset
        epoch = self._epoch
        if not running:
            return offset
        return offset + (time.perf_counter() - epoch)

    # ── Control ────────────────────────────────────────────────────

    def pause(self) -> None:
        with self._lock:
            if self._running:
                self._offset += time.perf_counter() - self._epoch
                self._running = False

    def resume(self) -> None:
        with self._lock:
            if not self._running:
                self._epoch = time.perf_counter()
                self._running = True

    def reset(self) -> None:
        with self._lock:
            self._offset = 0.0
            self._epoch = time.perf_counter()

    @property
    def is_running(self) -> bool:
        return self._running
