"""launch_profiles.py — SaltyBot bringup launch profiles (Issue #577).

Defines three named profiles: minimal, full, debug.
Each profile is a plain dataclass — no ROS2 runtime dependency — so
profile selection logic can be unit-tested without a live ROS2 install.

Profile hierarchy:
  minimal ⊂ full ⊂ debug

Usage (from launch files)::

    from saltybot_bringup.launch_profiles import get_profile, Profile

    p = get_profile("full")
    if p.enable_slam:
        ...
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Dict


@dataclass
class Profile:
    """All runtime-configurable flags for a single launch profile."""

    name: str

    # ── Group A: Drivers (always on in all profiles) ──────────────────────
    enable_esp32_bridge: bool = True
    enable_sensors:      bool = True   # RealSense + RPLIDAR
    enable_motor_daemon: bool = True
    enable_imu:          bool = True

    # ── Group B: Perception ────────────────────────────────────────────────
    enable_uwb:              bool = False
    enable_person_detection: bool = False
    enable_object_detection: bool = False
    enable_depth_costmap:    bool = False
    enable_gimbal:           bool = False

    # ── Group C: Navigation ────────────────────────────────────────────────
    enable_slam:        bool = False
    enable_nav2:        bool = False
    enable_follower:    bool = False
    enable_docking:     bool = False
    enable_lidar_avoid: bool = False

    # ── Group D: UI / Social / Audio ──────────────────────────────────────
    enable_rosbridge:    bool = False
    enable_audio:        bool = False
    enable_social:       bool = False
    enable_csi_cameras:  bool = False

    # ── Debug / recording extras ──────────────────────────────────────────
    enable_bag_recorder: bool = False
    enable_diagnostics:  bool = True   # sensor health + DiagnosticArray
    enable_rviz:         bool = False
    enable_verbose_logs: bool = False

    # ── Timing: inter-group health-gate delays (seconds) ──────────────────
    #  Each delay is the minimum wall-clock time after t=0 before the group
    #  starts.  Increase for slower hardware or cold-start scenarios.
    t_drivers:    float = 0.0    # Group A
    t_perception: float = 8.0   # Group B (sensors need ~6 s to initialise)
    t_navigation: float = 16.0  # Group C (SLAM needs ~8 s to build first map)
    t_ui:         float = 22.0  # Group D (nav2 needs ~4 s to load costmaps)

    # ── Safety ────────────────────────────────────────────────────────────
<<<<<<< HEAD
    watchdog_timeout_s:  float = 5.0   # max silence from ESP32 bridge (s)
=======
    watchdog_timeout_s:  float = 5.0   # max silence from ESP32-S3 bridge (s)
>>>>>>> 291dd68 (feat: remove all STM32/Mamba/BlackPill references — ESP32-S3 only)
    cmd_vel_deadman_s:   float = 0.5   # cmd_vel watchdog in bridge
    max_linear_vel:      float = 0.5   # m/s cap passed to bridge + follower
    follow_distance_m:   float = 1.5   # target follow distance (m)

    # ── Hardware conditionals ─────────────────────────────────────────────
    #  Paths checked at launch; absent devices skip the relevant node.
    esp32_port:  str = "/dev/esp32-bridge"
    uwb_port_a:  str = "/dev/uwb-anchor0"
    uwb_port_b:  str = "/dev/uwb-anchor1"
    gimbal_port: str = "/dev/ttyTHS1"

    def to_dict(self) -> Dict:
        """Return flat dict (e.g. for passing to launch Parameters)."""
        import dataclasses
        return dataclasses.asdict(self)


# ── Profile factory ────────────────────────────────────────────────────────────

def _minimal() -> Profile:
<<<<<<< HEAD
    """Minimal: ESP32 bridge + sensors + motor daemon.
=======
    """Minimal: ESP32-S3 bridge + sensors + motor daemon.
>>>>>>> 291dd68 (feat: remove all STM32/Mamba/BlackPill references — ESP32-S3 only)

    Safe drive control only.  No AI, no nav, no social.
    Boot time ~4 s.  RAM ~400 MB.
    """
    return Profile(
        name="minimal",
        # Drivers already enabled by default
        enable_diagnostics=True,
        t_drivers=0.0,
        t_perception=0.0,   # unused
        t_navigation=0.0,   # unused
        t_ui=0.0,           # unused
    )


def _full() -> Profile:
    """Full: complete autonomous stack.

    SLAM + Nav2 + perception + audio + social + rosbridge.
    Boot time ~22 s cold.  RAM ~3 GB.
    """
    return Profile(
        name="full",
        # Drivers
        enable_esp32_bridge=True,
        enable_sensors=True,
        enable_motor_daemon=True,
        enable_imu=True,
        # Perception
        enable_uwb=True,
        enable_person_detection=True,
        enable_object_detection=True,
        enable_depth_costmap=True,
        enable_gimbal=True,
        # Navigation
        enable_slam=True,
        enable_nav2=True,
        enable_follower=True,
        enable_docking=True,
        enable_lidar_avoid=True,
        # UI
        enable_rosbridge=True,
        enable_audio=True,
        enable_social=True,
        enable_csi_cameras=True,
        # Extras
        enable_bag_recorder=True,
        enable_diagnostics=True,
    )


def _debug() -> Profile:
    """Debug: full stack + verbose logging + RViz + extra recording.

    Adds /diagnostics aggregation, RViz2, verbose node output.
    Boot time same as full.  RAM ~3.5 GB (RViz + extra logs).
    """
    p = _full()
    p.name = "debug"
    p.enable_rviz = True
    p.enable_verbose_logs = True
    p.enable_bag_recorder = True
    # Slower boot to ensure all topics are stable before Nav2 starts
    p.t_navigation = 20.0
    p.t_ui = 26.0
    return p


_PROFILES: Dict[str, Profile] = {
    "minimal": _minimal(),
    "full":    _full(),
    "debug":   _debug(),
}


def get_profile(name: str) -> Profile:
    """Return the named Profile.

    Args:
        name: One of "minimal", "full", "debug".

    Raises:
        ValueError: If name is not a known profile.
    """
    if name not in _PROFILES:
        raise ValueError(
            f"Unknown launch profile {name!r}. "
            f"Valid profiles: {sorted(_PROFILES)}"
        )
    return _PROFILES[name]


def profile_names() -> list:
    return sorted(_PROFILES.keys())
