"""Host-side runtime ABI for observation-capable device plugins.

The contract deliberately contains no transport name, vendor topic, capture
suffix, codec, or viewer implementation. Concrete plugins discover native
evidence and export it into the host's canonical recorded-viewer artifact.
"""

from __future__ import annotations

import threading
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from pathlib import Path
from typing import Literal, Protocol

from .models import ObservationSessionCandidate, ReplayCommand

RecordingProgressPulse = Callable[[], None]
RecordingExportResult = Mapping[str, object]


class PluginRecordingExportError(RuntimeError):
    """A plugin rejected or failed to convert one native recording."""


class PluginRecordingExportCancelled(PluginRecordingExportError):
    """A plugin cooperatively stopped a recording conversion."""


class RecordingExporter(Protocol):
    def __call__(
        self,
        source: Path,
        destination: Path,
        *,
        cancel_event: threading.Event | None = None,
        activity_callback: RecordingProgressPulse | None = None,
    ) -> RecordingExportResult: ...


class RecordedPointColorRenderer(Protocol):
    def __call__(
        self,
        command: ReplayCommand,
        *,
        application_id: str,
        recording_id: str,
        color_mode: Literal["intensity", "height", "distance", "rgb", "class"],
        palette: Literal["turbo", "viridis", "plasma", "grayscale", "custom"],
        custom_color: str,
    ) -> bytes: ...


ObservationArchiveDiscovery = Callable[
    [Path],
    tuple[ObservationSessionCandidate, ...],
]
ObservationArchiveRecovery = Callable[[Path], object]


def _no_recovery(_: Path) -> object:
    return None


@dataclass(frozen=True, slots=True)
class ObservationArchiveSource:
    """One plugin-owned evidence namespace reconciled by the host catalog."""

    plugin_id: str
    archive_id: str
    root: Path
    discover: ObservationArchiveDiscovery
    recover: ObservationArchiveRecovery = _no_recovery


@dataclass(frozen=True, slots=True)
class ObservationRuntimeContribution:
    """Optional observation capabilities contributed by a device plugin."""

    archives: tuple[ObservationArchiveSource, ...]
    recording_exporter: RecordingExporter
    point_color_renderer: RecordedPointColorRenderer | None = None
