// Include-based coverage for client monitor enumeration logic.
//
// Scope: include `client/src/output/display.rs` with deterministic GTK/GDK
// stubs to exercise sorting and filtering branches.
// Targets: `client/src/output/display.rs`.
// Why: monitor-layout selection should remain stable even when CI has no real
// display server attached.

#[allow(dead_code)]
mod gtk {
    pub mod gdk {
        use std::cell::RefCell;

        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
        pub struct Rectangle {
            pub x: i32,
            pub y: i32,
            pub w: i32,
            pub h: i32,
        }

        impl Rectangle {
            pub fn new(x: i32, y: i32, w: i32, h: i32) -> Self {
                Self { x, y, w, h }
            }

            pub fn width(&self) -> i32 {
                self.w
            }

            pub fn height(&self) -> i32 {
                self.h
            }
        }

        #[derive(Clone, Debug)]
        pub struct Monitor {
            connector: Option<String>,
            model: Option<String>,
            geometry: Rectangle,
            scale_factor: i32,
        }

        impl Monitor {
            pub fn new(
                connector: Option<&str>,
                model: Option<&str>,
                geometry: Rectangle,
                scale_factor: i32,
            ) -> Self {
                Self {
                    connector: connector.map(str::to_owned),
                    model: model.map(str::to_owned),
                    geometry,
                    scale_factor,
                }
            }

            pub fn connector(&self) -> Option<String> {
                self.connector.clone()
            }

            pub fn model(&self) -> Option<String> {
                self.model.clone()
            }

            pub fn geometry(&self) -> Rectangle {
                self.geometry
            }

            pub fn scale_factor(&self) -> i32 {
                self.scale_factor
            }
        }

        #[derive(Clone, Debug)]
        pub enum Object {
            Monitor(Monitor),
            Other,
        }

        impl Object {
            #[allow(clippy::extra_unused_type_parameters)]
            pub fn downcast<T>(self) -> Result<Monitor, Self> {
                match self {
                    Self::Monitor(monitor) => Ok(monitor),
                    other => Err(other),
                }
            }
        }

        #[derive(Clone, Debug, Default)]
        pub struct MonitorList {
            pub items: Vec<Object>,
        }

        #[derive(Clone, Debug)]
        pub struct Display {
            monitors: MonitorList,
        }

        impl Display {
            pub fn default() -> Option<Self> {
                DISPLAY.with(|slot| slot.borrow().clone())
            }

            pub fn monitors(&self) -> MonitorList {
                self.monitors.clone()
            }
        }

        thread_local! {
            static DISPLAY: RefCell<Option<Display>> = const { RefCell::new(None) };
        }

        pub fn set_mock_display(display: Option<Display>) {
            DISPLAY.with(|slot| {
                *slot.borrow_mut() = display;
            });
        }

        pub fn display_from_items(items: Vec<Object>) -> Display {
            Display {
                monitors: MonitorList { items },
            }
        }
    }

    pub mod prelude {
        use super::gdk::{MonitorList, Object};

        pub trait ListModelExt {
            fn n_items(&self) -> u32;
            fn item(&self, idx: u32) -> Option<Object>;
        }

        impl ListModelExt for MonitorList {
            fn n_items(&self) -> u32 {
                self.items.len() as u32
            }

            fn item(&self, idx: u32) -> Option<Object> {
                self.items.get(idx as usize).cloned()
            }
        }
    }
}

#[allow(warnings)]
mod display_include_contract {
    use crate::gtk;
    include!(env!("LESAVKA_CLIENT_OUTPUT_DISPLAY_SRC"));

    use crate::gtk::gdk as mock_gdk;
    use serial_test::serial;

    #[test]
    #[serial]
    fn enumerate_monitors_falls_back_when_display_is_missing() {
        mock_gdk::set_mock_display(None);
        let monitors = enumerate_monitors();
        assert_eq!(monitors.len(), 1);
        assert_eq!(monitors[0].geometry.width(), 1920);
        assert_eq!(monitors[0].geometry.height(), 1080);
        assert!(!monitors[0].is_internal);
    }

    #[test]
    #[serial]
    fn enumerate_monitors_sorts_external_monitors_first() {
        let items = vec![
            mock_gdk::Object::Monitor(mock_gdk::Monitor::new(
                Some("eDP-1"),
                Some("internal"),
                mock_gdk::Rectangle::new(0, 0, 1920, 1200),
                2,
            )),
            mock_gdk::Object::Monitor(mock_gdk::Monitor::new(
                Some("HDMI-A-1"),
                Some("external"),
                mock_gdk::Rectangle::new(1920, 0, 1920, 1080),
                1,
            )),
            mock_gdk::Object::Monitor(mock_gdk::Monitor::new(
                Some("my-internal-panel"),
                Some("alt"),
                mock_gdk::Rectangle::new(-1920, 0, 1280, 720),
                1,
            )),
        ];
        mock_gdk::set_mock_display(Some(mock_gdk::display_from_items(items)));

        let monitors = enumerate_monitors();
        assert_eq!(monitors.len(), 3);
        assert!(!monitors[0].is_internal, "external monitor should be first");
        assert!(monitors[1].is_internal);
        assert!(monitors[2].is_internal);
    }

    #[test]
    #[serial]
    fn enumerate_monitors_ignores_non_monitor_objects() {
        let items = vec![
            mock_gdk::Object::Other,
            mock_gdk::Object::Monitor(mock_gdk::Monitor::new(
                Some("DP-1"),
                Some("dock"),
                mock_gdk::Rectangle::new(0, 0, 2560, 1440),
                1,
            )),
        ];
        mock_gdk::set_mock_display(Some(mock_gdk::display_from_items(items)));

        let monitors = enumerate_monitors();
        assert_eq!(monitors.len(), 1);
        assert_eq!(monitors[0].geometry.width(), 2560);
        assert_eq!(monitors[0].scale_factor, 1);
    }
}
