"""Dataclass schema for runtime configuration used by Python pipeline tools."""

from __future__ import annotations

from dataclasses import dataclass, field
import json
from pathlib import Path
from typing import Any


@dataclass(slots=True)
class ComboModel:
    """One switch combination used for an acquisition sweep."""

    input: int
    output: int


@dataclass(slots=True)
class RadarSweepModel:
    """Sweep settings for LibreVNA acquisition."""

    # Keep schema defaults minimal/safe; operational values come from run_config.json.
    start_hz: float = 0.0
    stop_hz: float = 0.0
    points: int = 1
    if_bandwidth_hz: float = 1.0
    power_dbm: float = -30.0


@dataclass(slots=True)
class RadarModel:
    """Radar section of run configuration."""

    model: str = ""
    serial: str = ""
    driver_mode: str = "mock"
    mock_signal_hz: float = 1_000_000.0
    sweep: RadarSweepModel = field(default_factory=RadarSweepModel)


@dataclass(slots=True)
class SwitchModel:
    """Generic switch section of run configuration."""

    name: str
    driver_mode: str = "mock"
    driver: str = ""
    radar_port: int = 0
    positions: int = 1
    default_position: int = 0
    gpio_chip: str = ""
    pin_a: int = -1
    pin_b: int = -1
    invert_logic: bool = False


@dataclass(slots=True)
class RingEndpointModel:
    """Shared-memory ring endpoint description."""

    name: str
    capacity: int = 1
    slot_size_bytes: int = 4096


@dataclass(slots=True)
class RingsModel:
    """Ring endpoints used by orchestration pipeline."""

    raw: RingEndpointModel = field(default_factory=lambda: RingEndpointModel(name=""))
    raw_tap: RingEndpointModel = field(default_factory=lambda: RingEndpointModel(name=""))
    preprocessed: RingEndpointModel = field(default_factory=lambda: RingEndpointModel(name=""))
    preprocessed_tap: RingEndpointModel = field(default_factory=lambda: RingEndpointModel(name=""))
    results: RingEndpointModel = field(default_factory=lambda: RingEndpointModel(name=""))


@dataclass(slots=True)
class RuntimeModel:
    """Runtime process behavior and paths."""

    settling_ms: int = 0
    idle_sleep_ms: int = 2
    continuous: bool = False
    processing_live_config_path: str = ""


@dataclass(slots=True)
class PreprocessModel:
    """Selected preprocessing artifacts for live acquisition."""

    calibration_set: str = ""
    reference_set: str = ""
    calibration_bundle_path: str = ""
    reference_bundle_path: str = ""


@dataclass(slots=True)
class RunConfigModel:
    """Top-level runtime config model consumed by C++ processes and GUI."""

    radar: RadarModel = field(default_factory=RadarModel)
    input_switch: SwitchModel = field(default_factory=lambda: SwitchModel(name=""))
    output_switch: SwitchModel = field(default_factory=lambda: SwitchModel(name=""))
    rings: RingsModel = field(default_factory=RingsModel)
    runtime: RuntimeModel = field(default_factory=RuntimeModel)
    preprocess: PreprocessModel = field(default_factory=PreprocessModel)
    combos: list[ComboModel] = field(default_factory=list)

    @staticmethod
    def build_full_combos(input_positions: int, output_positions: int) -> list[ComboModel]:
        """Build full Cartesian product of input/output switch positions."""
        return [
            ComboModel(input=input_pos, output=output_pos)
            for output_pos in range(output_positions)
            for input_pos in range(input_positions)
        ]

    def ensure_combos(self) -> None:
        """Populate combos with full matrix when no explicit run combos are set."""
        if self.combos:
            return
        self.combos = self.build_full_combos(self.input_switch.positions, self.output_switch.positions)

    @classmethod
    def from_dict(cls, payload: dict[str, Any]) -> RunConfigModel:
        """Build model from JSON-like payload using codec layer."""
        from python_app.models.run_config_codec import run_config_from_dict

        return run_config_from_dict(payload)

    @classmethod
    def load_from_path(cls, path: Path) -> RunConfigModel:
        """Load JSON file from disk and decode into model."""
        payload = json.loads(path.read_text(encoding="utf-8"))
        if not isinstance(payload, dict):
            raise ValueError(f"Config root must be JSON object: {path}")
        return cls.from_dict(payload)

    def clone(self) -> RunConfigModel:
        """Create deep copy through codec round-trip."""
        return RunConfigModel.from_dict(self.to_dict())

    def to_dict(self) -> dict[str, Any]:
        """Encode model into JSON-serializable dictionary."""
        from python_app.models.run_config_codec import run_config_to_dict

        return run_config_to_dict(self)
