use std::cell::RefCell;
use std::ops::Deref;
use std::rc::Weak;
use std::sync::Mutex;

use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::utils::{Logical, Point, Rectangle, Size};
use smithay::wayland::compositor;
use smithay::wayland::session_lock::{LockSurface, LockSurfaceState};
use smithay::wayland::shell::wlr_layer::{
    Anchor, ExclusiveZone, Layer, LayerSurface, LayerSurfaceAttributes, LayerSurfaceState,
};
use smithay::wayland::shell::xdg::{
    PopupState, PopupSurface, SurfaceCachedState, ToplevelState, ToplevelSurface,
    XdgPopupSurfaceData, XdgToplevelSurfaceData, XdgToplevelSurfaceRoleAttributes,
};

use crate::windows::Window;

pub trait Surface {
    type State;

    fn surface(&self) -> &WlSurface;

    fn alive(&self) -> bool;

    fn initial_configure(&self);

    fn initial_configure_sent(&self) -> bool;

    fn set_state<F: FnMut(&mut Self::State)>(&self, f: F);

    fn resize(&self, size: Size<i32, Logical>);

    fn acked_size(&self) -> Size<i32, Logical>;

    fn geometry(&self) -> Option<Rectangle<i32, Logical>> {
        compositor::with_states(self.surface(), |states| {
            states.cached_state.get::<SurfaceCachedState>().current().geometry
        })
    }
}

impl Surface for ToplevelSurface {
    type State = ToplevelState;

    fn surface(&self) -> &WlSurface {
        self.wl_surface()
    }

    fn alive(&self) -> bool {
        ToplevelSurface::alive(self)
    }

    fn initial_configure(&self) {
        if !self.initial_configure_sent() {
            self.send_configure();
        }
    }

    fn initial_configure_sent(&self) -> bool {
        compositor::with_states(self.surface(), |states| {
            states
                .data_map
                .get::<XdgToplevelSurfaceData>()
                .unwrap()
                .lock()
                .unwrap()
                .initial_configure_sent
        })
    }

    fn set_state<F: FnMut(&mut Self::State)>(&self, f: F) {
        self.with_pending_state(f);

        if self.initial_configure_sent() {
            self.send_configure();
        }
    }

    fn resize(&self, size: Size<i32, Logical>) {
        self.set_state(|state| state.size = Some(size));
    }

    fn acked_size(&self) -> Size<i32, Logical> {
        // In desktop mode the initial configure carries `size = None` so the
        // client picks its own preferred size; `last_acked.state.size` is
        // therefore `None` and the configure-based path returns (0, 0). Fall
        // back to the client-set `xdg_surface.set_window_geometry`, which
        // every mainstream toolkit (GTK, Qt, Chromium, Firefox) sets on first
        // commit. Without this fallback the desktop floating branch in
        // `Window::surface_commit_common` never updates `output_rectangle.size`
        // and the window is technically mapped but rendered with zero area.
        let configured = compositor::with_states(self.surface(), |states| {
            states
                .data_map
                .get::<Mutex<XdgToplevelSurfaceRoleAttributes>>()
                .and_then(|attr| attr.lock().ok())
                .and_then(|attr| attr.last_acked.as_ref()?.state.size)
        });
        if let Some(size) = configured {
            if size.w > 0 && size.h > 0 {
                return size;
            }
        }
        self.geometry()
            .map(|g| g.size)
            .filter(|s| s.w > 0 && s.h > 0)
            .unwrap_or_default()
    }
}

impl Surface for PopupSurface {
    type State = PopupState;

    fn surface(&self) -> &WlSurface {
        self.wl_surface()
    }

    fn alive(&self) -> bool {
        PopupSurface::alive(self)
    }

    fn initial_configure(&self) {
        if !self.initial_configure_sent() {
            let _ = self.send_configure();
        }
    }

    fn initial_configure_sent(&self) -> bool {
        compositor::with_states(self.surface(), |states| {
            states
                .data_map
                .get::<XdgPopupSurfaceData>()
                .unwrap()
                .lock()
                .unwrap()
                .initial_configure_sent
        })
    }

    fn set_state<F: FnMut(&mut Self::State)>(&self, f: F) {
        self.with_pending_state(f);

        if self.initial_configure_sent() {
            let _ = self.send_configure();
        }
    }

    fn resize(&self, _size: Size<i32, Logical>) {}

    fn acked_size(&self) -> Size<i32, Logical> {
        self.with_pending_state(|state| state.positioner.rect_size)
    }
}

#[derive(Debug)]
pub struct ForgeLayerSurface {
    pub exclusive_zone: ExclusiveZone,
    pub anchor: Anchor,
    surface: LayerSurface,
    layer: Layer,
}

impl ForgeLayerSurface {
    pub fn new(layer: Layer, surface: LayerSurface) -> Self {
        Self { surface, layer, exclusive_zone: ExclusiveZone::default(), anchor: Anchor::default() }
    }

    pub fn layer(&self) -> Layer {
        self.layer
    }
}

impl Surface for ForgeLayerSurface {
    type State = LayerSurfaceState;

    fn surface(&self) -> &WlSurface {
        self.surface.wl_surface()
    }

    fn alive(&self) -> bool {
        self.surface.alive()
    }

    fn initial_configure(&self) {
        if !self.initial_configure_sent() {
            self.send_configure();
        }
    }

    fn initial_configure_sent(&self) -> bool {
        compositor::with_states(self.surface(), |states| {
            states
                .data_map
                .get::<Mutex<LayerSurfaceAttributes>>()
                .unwrap()
                .lock()
                .unwrap()
                .initial_configure_sent
        })
    }

    fn set_state<F: FnMut(&mut Self::State)>(&self, f: F) {
        self.surface.with_pending_state(f);

        if self.initial_configure_sent() {
            self.surface.send_configure();
        }
    }

    fn resize(&self, size: Size<i32, Logical>) {
        self.set_state(|state| state.size = Some(size));
    }

    fn acked_size(&self) -> Size<i32, Logical> {
        compositor::with_states(self.surface(), |states| {
            states
                .data_map
                .get::<Mutex<LayerSurfaceAttributes>>()
                .and_then(|attr| attr.lock().ok())
                .and_then(|attr| attr.last_acked.as_ref()?.state.size)
        })
        .unwrap_or_default()
    }

    fn geometry(&self) -> Option<Rectangle<i32, Logical>> {
        None
    }
}

impl Deref for ForgeLayerSurface {
    type Target = LayerSurface;

    fn deref(&self) -> &Self::Target {
        &self.surface
    }
}

impl Surface for LockSurface {
    type State = LockSurfaceState;

    fn surface(&self) -> &WlSurface {
        self.wl_surface()
    }

    fn alive(&self) -> bool {
        LockSurface::alive(self)
    }

    fn initial_configure(&self) {}

    fn initial_configure_sent(&self) -> bool {
        true
    }

    fn set_state<F: FnMut(&mut Self::State)>(&self, f: F) {
        self.with_pending_state(f);
        self.send_configure();
    }

    fn resize(&self, size: Size<i32, Logical>) {
        self.set_state(|state| {
            state.size = Some((size.w as u32, size.h as u32).into());
        });
    }

    fn acked_size(&self) -> Size<i32, Logical> {
        let size =
            self.with_committed_state(|state| state.and_then(|s| s.size)).unwrap_or_default();
        (size.w as i32, size.h as i32).into()
    }

    fn geometry(&self) -> Option<Rectangle<i32, Logical>> {
        None
    }
}

pub struct InputSurface {
    pub toplevel: Option<InputSurfaceKind>,
    pub surface_offset: Point<f64, Logical>,
    pub surface_scale: f64,
    pub surface: WlSurface,
}

impl InputSurface {
    pub fn new(surface: WlSurface, offset: Point<f64, Logical>, scale: f64) -> Self {
        Self { surface, surface_scale: scale, surface_offset: offset, toplevel: None }
    }
}

pub enum InputSurfaceKind {
    Layout((Weak<RefCell<Window>>, Option<String>)),
    Layer((WlSurface, Option<String>)),
    X11(WlSurface),
}

impl InputSurfaceKind {
    pub fn take_app_id(&mut self) -> Option<String> {
        match self {
            Self::Layout((_, app_id)) | Self::Layer((_, app_id)) => app_id.take(),
            Self::X11(_) => None,
        }
    }
}
