// server/src/camera.rs

#![cfg_attr(coverage, allow(dead_code, unused_imports, unused_variables))]

mod selection;

use std::sync::{OnceLock, RwLock};

use selection::{select_camera_config, select_hdmi_mirror_config};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CameraOutput {
    Uvc,
    Hdmi,
}

impl CameraOutput {
    /// Return the canonical string name for the transport.
    pub fn as_str(self) -> &'static str {
        match self {
            CameraOutput::Uvc => "uvc",
            CameraOutput::Hdmi => "hdmi",
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CameraCodec {
    H264,
    Hevc,
    Mjpeg,
}

impl CameraCodec {
    /// Return the canonical string name for the codec.
    pub fn as_str(self) -> &'static str {
        match self {
            CameraCodec::H264 => "h264",
            CameraCodec::Hevc => "hevc",
            CameraCodec::Mjpeg => "mjpeg",
        }
    }
}

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

#[derive(Clone, Debug)]
pub struct HdmiConnector {
    pub name: String,
    pub id: Option<u32>,
    pub modes: Vec<HdmiMode>,
}

#[derive(Clone, Debug)]
pub struct CameraConfig {
    pub output: CameraOutput,
    pub codec: CameraCodec,
    pub width: u32,
    pub height: u32,
    pub fps: u32,
    pub hdmi: Option<HdmiConnector>,
}

impl CameraConfig {
    /// Return the HDMI display mode that should be driven on the wire.
    ///
    /// Inputs: the selected camera config and optional HDMI mode overrides.
    /// Outputs: a width/height pair for the physical display pipeline.
    /// Why: the client webcam uplink can stay at a known-good capture profile
    /// while HDMI output scales to a mode the capture adapter actually locks.
    pub fn hdmi_display_size(&self) -> (u32, u32) {
        if self.output != CameraOutput::Hdmi {
            return (self.width, self.height);
        }

        if let (Some(width), Some(height)) = (
            selection::read_u32_from_env("LESAVKA_HDMI_WIDTH"),
            selection::read_u32_from_env("LESAVKA_HDMI_HEIGHT"),
        ) {
            return (width, height);
        }

        self.hdmi
            .as_ref()
            .and_then(|hdmi| selection::preferred_hdmi_mode(&hdmi.modes))
            .map(|mode| (mode.width, mode.height))
            .unwrap_or((self.width, self.height))
    }
}

static LAST_CONFIG: OnceLock<RwLock<CameraConfig>> = OnceLock::new();

/// Refresh the cached camera config from the current environment.
///
/// Inputs: none; the selector consults the current environment and local
/// `/sys/class/drm` state.
/// Outputs: the newly selected camera configuration, which is also stored as
/// the last-known config for future reads.
/// Why: callers need a single entrypoint for changing the active output mode
/// without re-implementing the selection rules.
pub fn update_camera_config() -> CameraConfig {
    let cfg = select_camera_config();
    let lock = LAST_CONFIG.get_or_init(|| RwLock::new(cfg.clone()));
    *lock.write().unwrap() = cfg.clone();
    cfg
}

#[cfg(coverage)]
pub fn current_camera_config() -> CameraConfig {
    update_camera_config()
}

/// Return the last selected camera configuration.
///
/// Inputs: none.
/// Outputs: the cached camera configuration, or a freshly selected one when
/// the cache has not been initialized yet.
/// Why: call sites can read the active config without worrying about whether
/// initialization already happened in this process.
#[cfg(not(coverage))]
pub fn current_camera_config() -> CameraConfig {
    if let Some(lock) = LAST_CONFIG.get() {
        return lock.read().unwrap().clone();
    }
    update_camera_config()
}

/// Build an HDMI display config that mirrors an already-selected camera profile.
///
/// Inputs: the primary camera configuration.
/// Outputs: an HDMI config with the same uplink codec, size, and rate.
/// Why: the experimental HDMI capture-card path must be testable beside the
/// normal UVC gadget without negotiating a second client capture profile.
pub fn hdmi_mirror_config(primary: &CameraConfig) -> CameraConfig {
    select_hdmi_mirror_config(primary)
}

#[cfg(test)]
#[path = "tests/camera.rs"]
mod tests;
