use smithay::utils::{Logical, Point};
use smithay::wayland::shell::wlr_layer::Layer;

use crate::windows::surface::{ForgeLayerSurface, InputSurface};
use crate::windows::window::Window;

type LayerWindow = Window<ForgeLayerSurface>;

#[derive(Debug, Default)]
pub struct Layers {
    background: Vec<LayerWindow>,
    bottom: Vec<LayerWindow>,
    top: Vec<LayerWindow>,
    overlay: Vec<LayerWindow>,
}

impl Layers {
    pub fn add(&mut self, window: LayerWindow) {
        match window.surface.layer() {
            Layer::Background => self.background.push(window),
            Layer::Bottom => self.bottom.push(window),
            Layer::Top => self.top.push(window),
            Layer::Overlay => self.overlay.push(window),
        }
    }

    pub fn len(&self) -> usize {
        self.background.len() + self.bottom.len() + self.top.len() + self.overlay.len()
    }

    pub fn iter(&self) -> impl Iterator<Item = &LayerWindow> {
        self.background.iter().chain(&self.bottom).chain(&self.top).chain(&self.overlay)
    }

    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut LayerWindow> {
        self.background
            .iter_mut()
            .chain(&mut self.bottom)
            .chain(&mut self.top)
            .chain(&mut self.overlay)
            .rev()
    }

    // The per-level iterators below serve the primary's scene and input
    // paths, so they exclude surfaces bound to an extra output; those are
    // reached via `background_on`. `iter`/`iter_mut` stay global (commits,
    // popups, frame callbacks, teardown).

    pub fn background(&self) -> impl Iterator<Item = &LayerWindow> {
        self.background
            .iter()
            .chain(&self.bottom)
            .rev()
            .filter(|w| w.surface.target_output.is_none())
    }

    /// Background/bottom layers bound to the named extra output.
    pub fn background_on<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a LayerWindow> {
        self.background
            .iter()
            .chain(&self.bottom)
            .rev()
            .filter(move |w| w.surface.target_output.as_deref() == Some(name))
    }

    pub fn foreground(&self) -> impl Iterator<Item = &LayerWindow> {
        self.top
            .iter()
            .chain(&self.overlay)
            .rev()
            .filter(|w| w.surface.target_output.is_none())
    }

    pub fn overlay(&self) -> impl Iterator<Item = &LayerWindow> {
        self.overlay.iter().rev().filter(|w| w.surface.target_output.is_none())
    }

    pub fn overlay_mut(&mut self) -> impl Iterator<Item = &mut LayerWindow> {
        self.overlay.iter_mut().rev().filter(|w| w.surface.target_output.is_none())
    }

    pub fn request_frames(&mut self, runtime: u32) {
        for window in self.iter_mut() {
            window.request_frame(runtime);
        }
    }

    pub fn foreground_surface_at(
        &self,
        output_scale: f64,
        position: Point<f64, Logical>,
    ) -> Option<(&LayerWindow, InputSurface)> {
        self.overlay
            .iter()
            .rev()
            .chain(self.top.iter().rev())
            .filter(|w| w.surface.target_output.is_none())
            .find_map(|w| Some((w, w.surface_at(output_scale, position)?)))
    }

    pub fn background_surface_at(
        &self,
        output_scale: f64,
        position: Point<f64, Logical>,
    ) -> Option<(&LayerWindow, InputSurface)> {
        self.bottom
            .iter()
            .rev()
            .chain(self.background.iter().rev())
            .filter(|w| w.surface.target_output.is_none())
            .find_map(|w| Some((w, w.surface_at(output_scale, position)?)))
    }

    pub fn overlay_surface_at(
        &self,
        output_scale: f64,
        position: Point<f64, Logical>,
    ) -> Option<(&LayerWindow, InputSurface)> {
        self.overlay
            .iter()
            .rev()
            .filter(|w| w.surface.target_output.is_none())
            .find_map(|w| Some((w, w.surface_at(output_scale, position)?)))
    }

    pub fn apply_transaction(&mut self) {
        apply_window_transactions(&mut self.background);
        apply_window_transactions(&mut self.bottom);
        apply_window_transactions(&mut self.top);
        apply_window_transactions(&mut self.overlay);
    }
}

fn apply_window_transactions(windows: &mut Vec<LayerWindow>) {
    windows.retain_mut(|w| {
        if w.alive() {
            w.apply_transaction();
            true
        } else {
            false
        }
    });
}
