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 {
    pub fn as_env(self) -> &'static str {
        match self {
            Self::Local => "0",
            Self::Remote => "1",
        }
    }
}

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

impl ViewMode {
    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 {
    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 {
    pub fn as_id(self) -> &'static str {
        match self {
            Self::ThisEye => "self",
            Self::OtherEye => "other",
            Self::Off => "off",
        }
    }

    pub fn from_id(raw: &str) -> Option<Self> {
        match raw {
            "self" => Some(Self::ThisEye),
            "other" => Some(Self::OtherEye),
            "off" => Some(Self::Off),
            _ => None,
        }
    }

    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 {
    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",
        }
    }

    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,
        }
    }

    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 {
    pub fn as_id(self) -> &'static str {
        match self {
            Self::Vga => "vga",
            Self::P480 => "480p",
            Self::P576 => "576p",
            Self::P720 => "720p",
            Self::P1080 => "1080p",
        }
    }

    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,
        }
    }

    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"
    }

    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],
        }
    }

    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,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct DeviceSelection {
    pub camera: Option<String>,
    pub microphone: Option<String>,
    pub speaker: Option<String>,
    pub keyboard: Option<String>,
    pub mouse: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChannelSelection {
    pub camera: bool,
    pub microphone: bool,
    pub audio: bool,
}

impl Default for ChannelSelection {
    fn default() -> Self {
        Self {
            camera: false,
            microphone: false,
            audio: true,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LauncherState {
    pub server_available: bool,
    pub server_version: Option<String>,
    pub routing: InputRouting,
    pub view_mode: ViewMode,
    pub displays: [DisplaySurface; 2],
    pub feed_sources: [FeedSourcePreset; 2],
    pub preview_source: PreviewSourceSize,
    pub breakout_limit: PreviewSourceSize,
    pub breakout_display: PreviewSourceSize,
    pub capture_sizes: [CaptureSizePreset; 2],
    pub capture_fps: [u32; 2],
    pub capture_bitrates_kbit: [u32; 2],
    pub breakout_sizes: [BreakoutSizePreset; 2],
    pub devices: DeviceSelection,
    pub camera_quality: Option<CameraMode>,
    pub channels: ChannelSelection,
    pub audio_gain_percent: u32,
    pub mic_gain_percent: u32,
    pub swap_key: String,
    pub swap_key_binding: bool,
    pub swap_key_binding_token: u64,
    pub capture_power: CapturePowerStatus,
    pub remote_active: bool,
    pub notes: Vec<String>,
}

impl Default for LauncherState {
    fn default() -> Self {
        Self {
            server_available: false,
            server_version: None,
            routing: InputRouting::Remote,
            view_mode: ViewMode::Unified,
            displays: [DisplaySurface::Preview, DisplaySurface::Preview],
            feed_sources: [FeedSourcePreset::ThisEye, FeedSourcePreset::ThisEye],
            preview_source: PreviewSourceSize::default(),
            breakout_limit: PreviewSourceSize::default(),
            breakout_display: PreviewSourceSize::default(),
            capture_sizes: [CaptureSizePreset::P1080, CaptureSizePreset::P1080],
            capture_fps: [60, 60],
            capture_bitrates_kbit: [18_000, 18_000],
            breakout_sizes: [BreakoutSizePreset::Source, BreakoutSizePreset::Source],
            devices: DeviceSelection::default(),
            camera_quality: None,
            channels: ChannelSelection::default(),
            audio_gain_percent: DEFAULT_AUDIO_GAIN_PERCENT,
            mic_gain_percent: DEFAULT_MIC_GAIN_PERCENT,
            swap_key: "pause".to_string(),
            swap_key_binding: false,
            swap_key_binding_token: 0,
            capture_power: CapturePowerStatus::default(),
            remote_active: false,
            notes: Vec::new(),
        }
    }
}
