"""Watchdog-based live session discovery for ~/.claude/projects/."""

from __future__ import annotations

import threading
import time
from collections.abc import Callable
from pathlib import Path
from typing import TYPE_CHECKING, Any

from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer

if TYPE_CHECKING:
    from watchdog.events import FileSystemEvent

    from blackbox.models import SessionInfo

LIVE_THRESHOLD_S = 30.0


class SessionWatcher(FileSystemEventHandler):
    """Watches the Claude Code projects directory for transcript changes.

    Tracks which session files have been recently modified so the dashboard
    can mark them as "live" in the sidebar.  Also caches the session list
    and invalidates it when any transcript file changes.
    """

    def __init__(self, projects_dir: Path) -> None:
        self._projects_dir = projects_dir
        self._lock = threading.Lock()
        self._last_modified: dict[str, float] = {}
        self._observer: Any = None
        self._cached_sessions: list[SessionInfo] | None = None

    def start(self) -> None:
        if not self._projects_dir.is_dir():
            return
        self._observer = Observer()
        self._observer.schedule(self, str(self._projects_dir), recursive=True)
        self._observer.daemon = True
        self._observer.start()

    def stop(self) -> None:
        if self._observer is not None:
            self._observer.stop()
            self._observer.join(timeout=2.0)
            self._observer = None

    def on_modified(self, event: FileSystemEvent) -> None:
        if event.is_directory:
            return
        path = Path(str(event.src_path))
        if path.suffix != ".jsonl":
            return
        session_id = path.stem
        with self._lock:
            self._last_modified[session_id] = time.time()
            self._cached_sessions = None

    def on_created(self, event: FileSystemEvent) -> None:
        self.on_modified(event)

    def on_deleted(self, event: FileSystemEvent) -> None:
        if not event.is_directory and Path(str(event.src_path)).suffix == ".jsonl":
            with self._lock:
                self._cached_sessions = None

    def live_session_ids(self) -> set[str]:
        now = time.time()
        with self._lock:
            expired = [sid for sid, ts in self._last_modified.items() if now - ts > LIVE_THRESHOLD_S]
            for sid in expired:
                del self._last_modified[sid]
            return {sid for sid, ts in self._last_modified.items() if now - ts <= LIVE_THRESHOLD_S}

    def get_sessions(self, scan_fn: Callable[[Path], list[SessionInfo]]) -> list[SessionInfo]:
        with self._lock:
            if self._cached_sessions is not None:
                return self._cached_sessions
        sessions = scan_fn(self._projects_dir)
        with self._lock:
            self._cached_sessions = sessions
        return sessions
