"""Ref-counted pool of SyncClockRuntime instances.

Runtimes are created lazily when a stream first acquires a clock and
destroyed when the last consumer releases it.
"""

import threading
from typing import Dict, Optional

from wled_controller.core.processing.sync_clock_runtime import SyncClockRuntime
from wled_controller.storage.sync_clock_store import SyncClockStore
from wled_controller.utils import get_logger

logger = get_logger(__name__)


class SyncClockManager:
    """Manages SyncClockRuntime instances with reference counting."""

    def __init__(self, store: SyncClockStore) -> None:
        self._store = store
        self._lock = threading.Lock()
        self._runtimes: Dict[str, SyncClockRuntime] = {}
        self._ref_counts: Dict[str, int] = {}

    # ── Acquire / release (used by stream manager) ────────────────

    def acquire(self, clock_id: str) -> SyncClockRuntime:
        """Get or create a runtime for *clock_id* (ref-counted)."""
        with self._lock:
            if clock_id in self._runtimes:
                self._ref_counts[clock_id] += 1
                logger.debug(f"SyncClock {clock_id} ref++ → {self._ref_counts[clock_id]}")
                return self._runtimes[clock_id]

            clock_cfg = self._store.get_clock(clock_id)  # raises ValueError if missing
            rt = SyncClockRuntime(speed=clock_cfg.speed)
            self._runtimes[clock_id] = rt
            self._ref_counts[clock_id] = 1
            logger.info(f"SyncClock runtime created: {clock_id} (speed={clock_cfg.speed})")
            return rt

    def release(self, clock_id: str) -> None:
        """Decrement ref count; destroy runtime when it reaches zero."""
        with self._lock:
            if clock_id not in self._ref_counts:
                return
            self._ref_counts[clock_id] -= 1
            logger.debug(f"SyncClock {clock_id} ref-- → {self._ref_counts[clock_id]}")
            if self._ref_counts[clock_id] <= 0:
                del self._runtimes[clock_id]
                del self._ref_counts[clock_id]
                logger.info(f"SyncClock runtime destroyed: {clock_id}")

    def release_all_for(self, clock_id: str) -> None:
        """Force-release all references to *clock_id* (used on delete)."""
        with self._lock:
            self._runtimes.pop(clock_id, None)
            self._ref_counts.pop(clock_id, None)

    def release_all(self) -> None:
        """Destroy all runtimes (shutdown)."""
        with self._lock:
            self._runtimes.clear()
            self._ref_counts.clear()

    # ── Lookup (no ref counting) ──────────────────────────────────

    def get_runtime(self, clock_id: str) -> Optional[SyncClockRuntime]:
        """Return an existing runtime or *None* (does not create one)."""
        with self._lock:
            return self._runtimes.get(clock_id)

    def _ensure_runtime(self, clock_id: str) -> SyncClockRuntime:
        """Return existing runtime or create a zero-ref one for API control."""
        with self._lock:
            rt = self._runtimes.get(clock_id)
            if rt:
                return rt
            clock_cfg = self._store.get_clock(clock_id)
            rt = SyncClockRuntime(speed=clock_cfg.speed)
            self._runtimes[clock_id] = rt
            self._ref_counts[clock_id] = 0
            logger.info(f"SyncClock runtime created (API): {clock_id} (speed={clock_cfg.speed})")
            return rt

    # ── Delegated control ─────────────────────────────────────────

    def update_speed(self, clock_id: str, speed: float) -> None:
        rt = self._ensure_runtime(clock_id)
        rt.speed = speed
        logger.info(f"SyncClock {clock_id} speed → {speed}")

    def pause(self, clock_id: str) -> None:
        rt = self._ensure_runtime(clock_id)
        rt.pause()
        logger.info(f"SyncClock {clock_id} paused")

    def resume(self, clock_id: str) -> None:
        rt = self._ensure_runtime(clock_id)
        rt.resume()
        logger.info(f"SyncClock {clock_id} resumed")

    def reset(self, clock_id: str) -> None:
        rt = self._ensure_runtime(clock_id)
        rt.reset()
        logger.info(f"SyncClock {clock_id} reset")
