use serde::{Deserialize, Serialize};

use super::devices::{CameraMode, DeviceCatalog};
use lesavka_common::eye_source::{
    EyeSourceMode, default_eye_source_mode, display_size_for_source_mode, native_eye_source_modes,
};

pub const DEFAULT_AUDIO_GAIN_PERCENT: u32 = 200;
pub const MAX_AUDIO_GAIN_PERCENT: u32 = 800;
pub const DEFAULT_MIC_GAIN_PERCENT: u32 = 100;
pub const MAX_MIC_GAIN_PERCENT: u32 = 400;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum InputRouting {
    Local,
    Remote,
}

impl InputRouting {
    /// Keeps `as_env` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
    /// Inputs are the typed parameters; output is the return value or side effect.
    pub fn as_env(self) -> &'static str {
        match self {
            Self::Local => "0",
            Self::Remote => "1",
        }
    }
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum WebcamTransport {
    Hevc,
    #[default]
    Mjpeg,
}

impl WebcamTransport {
    /// Return the stable GTK row id for the upstream webcam transport.
    ///
    /// Inputs: the selected transport. Output: the id stored in the combo box.
    /// Why: the UI must map operator intent onto the exact codec environment
    /// variable the relay child consumes when it starts its webcam pipeline.
    pub const fn as_id(self) -> &'static str {
        match self {
            Self::Hevc => "hevc",
            Self::Mjpeg => "mjpeg",
        }
    }

    /// Parse a GTK row id back into an upstream webcam transport.
    ///
    /// Inputs: raw combo id. Output: a supported transport when recognized.
    /// Why: unsupported codecs should never silently enter launcher state,
    /// since the server decoder path is calibrated per ingress codec.
    pub fn from_id(raw: &str) -> Option<Self> {
        match raw.trim().to_ascii_lowercase().as_str() {
            "hevc" | "h265" | "h.265" => Some(Self::Hevc),
            "mjpeg" | "mjpg" | "jpeg" => Some(Self::Mjpeg),
            _ => None,
        }
    }

    /// Parse the camera codec advertised by the server caps response.
    ///
    /// Inputs: raw server codec string. Output: matching launcher transport.
    /// Why: the relay child must send the codec the server is currently
    /// configured to consume, otherwise MJPEG UVC can receive HEVC bytes and
    /// browsers show a black webcam preview.
    pub fn from_server_codec(raw: &str) -> Option<Self> {
        Self::from_id(raw)
    }

    /// Return the environment value consumed by the relay child.
    ///
    /// Inputs: the selected transport. Output: canonical `LESAVKA_CAM_CODEC`.
    /// Why: the launcher and headless client share the same camera pipeline
    /// selector, so keeping this value canonical prevents HEVC/MJPEG drift.
    pub const fn env_value(self) -> &'static str {
        self.as_id()
    }

    /// Short label displayed in the launcher dropdown.
    ///
    /// Inputs: the selected transport. Output: a human-readable label. Why:
    /// this makes the compact control understandable without implying YUY2 or
    /// H.264 are ready for the current calibrated upstream path.
    pub const fn label(self) -> &'static str {
        match self {
            Self::Hevc => "HEVC",
            Self::Mjpeg => "MJPEG",
        }
    }
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum UpstreamAudioTransport {
    Opus,
    #[default]
    Pcm,
}

impl UpstreamAudioTransport {
    /// Return the stable GTK row id for upstream microphone transport.
    #[must_use]
    pub const fn as_id(self) -> &'static str {
        match self {
            Self::Opus => "opus",
            Self::Pcm => "pcm",
        }
    }

    /// Parse a GTK row id back into an upstream microphone transport.
    ///
    /// Inputs: compact id or familiar operator alias. Output: a supported
    /// transport. Why: PCM is the known-good route while Opus remains
    /// explicitly selectable for calibration and bandwidth experiments.
    pub fn from_id(raw: &str) -> Option<Self> {
        match raw.trim().to_ascii_lowercase().as_str() {
            "opus" | "compressed" => Some(Self::Opus),
            "pcm" | "raw" | "s16le" | "uncompressed" => Some(Self::Pcm),
            _ => None,
        }
    }

    /// Return the environment/control value consumed by the relay child.
    #[must_use]
    pub const fn env_value(self) -> &'static str {
        self.as_id()
    }

    /// Return a short label displayed in the launcher dropdown.
    #[must_use]
    pub const fn label(self) -> &'static str {
        match self {
            Self::Opus => "Opus",
            Self::Pcm => "PCM",
        }
    }

    /// Convert the UI enum into the shared transport enum.
    #[must_use]
    pub const fn as_common_codec(self) -> lesavka_common::audio_transport::UpstreamAudioCodec {
        match self {
            Self::Opus => lesavka_common::audio_transport::UpstreamAudioCodec::Opus,
            Self::Pcm => lesavka_common::audio_transport::UpstreamAudioCodec::PcmS16le,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ViewMode {
    Unified,
    Breakout,
}

impl ViewMode {
    /// Keeps `as_env` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
    /// Inputs are the typed parameters; output is the return value or side effect.
    pub fn as_env(self) -> &'static str {
        match self {
            Self::Unified => "unified",
            Self::Breakout => "breakout",
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DisplaySurface {
    Preview,
    Window,
}

impl DisplaySurface {
    /// Keeps `label` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
    /// Inputs are the typed parameters; output is the return value or side effect.
    pub fn label(self) -> &'static str {
        match self {
            Self::Preview => "preview",
            Self::Window => "window",
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FeedSourcePreset {
    ThisEye,
    OtherEye,
    Off,
}

impl FeedSourcePreset {
    /// Keeps `as_id` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
    /// Inputs are the typed parameters; output is the return value or side effect.
    pub fn as_id(self) -> &'static str {
        match self {
            Self::ThisEye => "self",
            Self::OtherEye => "other",
            Self::Off => "off",
        }
    }

    /// Keeps `from_id` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
    /// Inputs are the typed parameters; output is the return value or side effect.
    pub fn from_id(raw: &str) -> Option<Self> {
        match raw {
            "self" => Some(Self::ThisEye),
            "other" => Some(Self::OtherEye),
            "off" => Some(Self::Off),
            _ => None,
        }
    }

    /// Keeps `label` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
    /// Inputs are the typed parameters; output is the return value or side effect.
    pub fn label(self, monitor_id: usize) -> &'static str {
        match (monitor_id, self) {
            (_, Self::Off) => "Off",
            (0, Self::ThisEye) => "Left Eye",
            (0, Self::OtherEye) => "Right Eye",
            (1, Self::ThisEye) => "Right Eye",
            (1, Self::OtherEye) => "Left Eye",
            _ => "This Eye",
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BreakoutSizePreset {
    P360,
    P540,
    P720,
    P900,
    P1080,
    P1440,
    Source,
    FillDisplay,
}

impl BreakoutSizePreset {
    /// Keeps `as_id` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
    /// Inputs are the typed parameters; output is the return value or side effect.
    pub fn as_id(self) -> &'static str {
        match self {
            Self::P360 => "360p",
            Self::P540 => "540p",
            Self::P720 => "720p",
            Self::P900 => "900p",
            Self::P1080 => "1080p",
            Self::P1440 => "1440p",
            Self::Source => "source",
            Self::FillDisplay => "fill",
        }
    }

    /// Keeps `from_id` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
    /// Inputs are the typed parameters; output is the return value or side effect.
    pub fn from_id(raw: &str) -> Option<Self> {
        match raw {
            "360p" => Some(Self::P360),
            "540p" => Some(Self::P540),
            "720p" => Some(Self::P720),
            "900p" => Some(Self::P900),
            "1080p" => Some(Self::P1080),
            "1440p" => Some(Self::P1440),
            "source" => Some(Self::Source),
            "fill" => Some(Self::FillDisplay),
            _ => None,
        }
    }

    /// Keeps `label` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
    /// Inputs are the typed parameters; output is the return value or side effect.
    pub fn label(self) -> &'static str {
        match self {
            Self::P360 => "360p",
            Self::P540 => "540p",
            Self::P720 => "720p",
            Self::P900 => "900p",
            Self::P1080 => "1080p",
            Self::P1440 => "1440p",
            Self::Source => "Source",
            Self::FillDisplay => "Display",
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CaptureSizePreset {
    #[serde(alias = "P360")]
    Vga,
    #[serde(alias = "P540")]
    P480,
    P576,
    P720,
    #[serde(alias = "P900", alias = "P1440", alias = "Source")]
    P1080,
}

impl CaptureSizePreset {
    /// Keeps `as_id` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
    /// Inputs are the typed parameters; output is the return value or side effect.
    pub fn as_id(self) -> &'static str {
        match self {
            Self::Vga => "vga",
            Self::P480 => "480p",
            Self::P576 => "576p",
            Self::P720 => "720p",
            Self::P1080 => "1080p",
        }
    }

    /// Keeps `from_id` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
    /// Inputs are the typed parameters; output is the return value or side effect.
    pub fn from_id(raw: &str) -> Option<Self> {
        match raw {
            "vga" | "360p" => Some(Self::Vga),
            "480p" | "540p" => Some(Self::P480),
            "576p" => Some(Self::P576),
            "720p" => Some(Self::P720),
            "900p" | "1080p" | "1440p" | "source" => Some(Self::P1080),
            _ => None,
        }
    }

    /// Keeps `label` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
    /// Inputs are the typed parameters; output is the return value or side effect.
    pub fn label(self) -> &'static str {
        match self {
            Self::Vga => "VGA",
            Self::P480 => "480p",
            Self::P576 => "576p",
            Self::P720 => "720p",
            Self::P1080 => "1080p",
        }
    }

    pub fn transport_label(self) -> &'static str {
        "device H.264 pass-through"
    }

    /// Keeps `source_mode` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
    /// Inputs are the typed parameters; output is the return value or side effect.
    pub fn source_mode(self) -> EyeSourceMode {
        match normalize_capture_size_preset(self) {
            Self::P720 => native_eye_source_modes()[1],
            Self::P1080 => native_eye_source_modes()[0],
            Self::Vga | Self::P480 | Self::P576 => native_eye_source_modes()[1],
        }
    }

    /// Keeps `from_source_mode` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
    /// Inputs are the typed parameters; output is the return value or side effect.
    pub fn from_source_mode(mode: EyeSourceMode) -> Self {
        match (mode.width, mode.height, mode.fps) {
            (1280, 720, 60) => Self::P720,
            _ => Self::P1080,
        }
    }

    pub fn display_size(self) -> (u32, u32) {
        display_size_for_source_mode(self.source_mode())
    }

    pub fn display_aspect_ratio(self) -> f32 {
        let (width, height) = self.display_size();
        width.max(1) as f32 / height.max(1) as f32
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct PreviewSourceSize {
    pub width: u32,
    pub height: u32,
    pub fps: u32,
}

impl Default for PreviewSourceSize {
    fn default() -> Self {
        let mode = default_eye_source_mode();
        Self {
            width: mode.width,
            height: mode.height,
            fps: mode.fps,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BreakoutSizeChoice {
    pub preset: BreakoutSizePreset,
    pub width: i32,
    pub height: i32,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CaptureSizeChoice {
    pub preset: CaptureSizePreset,
    pub width: i32,
    pub height: i32,
    pub fps: u32,
    pub max_bitrate_kbit: u32,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FeedSourceChoice {
    pub preset: FeedSourcePreset,
    pub label: &'static str,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CaptureFpsChoice {
    pub fps: u32,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CaptureBitrateChoice {
    pub max_bitrate_kbit: u32,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CapturePowerStatus {
    pub available: bool,
    pub enabled: bool,
    pub unit: String,
    pub detail: String,
    pub active_leases: u32,
    pub mode: String,
    pub detected_devices: u32,
}

impl Default for CapturePowerStatus {
    fn default() -> Self {
        Self {
            available: false,
            enabled: false,
            unit: "relay.service".to_string(),
            detail: "unknown".to_string(),
            active_leases: 0,
            mode: "auto".to_string(),
            detected_devices: 0,
        }
    }
}

include!("selection_models/sync_and_state_status.rs");
