use std::borrow::Cow;
use std::cell::{RefCell, RefMut};
use std::mem;
use std::rc::{Rc, Weak};
use std::time::Instant;

use forge_ipc::{AppIdMatcher, WindowScale};
use smithay::backend::renderer::element::Element;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::backend::renderer::ImportAll;
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::State;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::reexports::wayland_server::{DisplayHandle, Resource};
use smithay::utils::{Logical, Physical, Point, Rectangle, Size};
use smithay::wayland::compositor;
use smithay::wayland::compositor::{SubsurfaceCachedState, TraversalAction};
use smithay::wayland::foreign_toplevel_list::ForeignToplevelHandle;
use smithay::wayland::session_lock::LockSurface;
use smithay::wayland::shell::wlr_layer::{
    KeyboardInteractivity, Layer, LayerSurface, LayerSurfaceCachedState,
};
use smithay::wayland::shell::xdg::{PopupSurface, ToplevelSurface};

use crate::drawing::{ForgeElement, ForgeSurfaceData, RenderTexture, Texture};
use crate::input::{HandleGesture, TouchState};
use crate::layer::Layers;
use crate::orientation::Orientation;
use crate::output::{Canvas, Output, GESTURE_HANDLE_HEIGHT};
use crate::windows::layout::Layouts;
use crate::windows::surface::{ForgeLayerSurface, InputSurface, Surface};
use crate::windows::window::Window;

pub mod focus;
pub mod layout;
pub mod surface;
pub mod window;

use crate::windows::focus::FocusTarget;

macro_rules! with_all_windows {
    ($windows:expr, | $window:tt | $fn:expr) => {{
        match $windows.pending_view() {
            View::Lock(Some($window)) => $fn,
            _ => (),
        }

        for $window in $windows.layouts.windows() {
            $fn
        }
        for $window in $windows.layers.iter() {
            $fn
        }
    }};
}

macro_rules! with_all_windows_mut {
    ($windows:expr, | $window:tt | $fn:expr) => {{
        match $windows.pending_view_mut() {
            View::Lock(Some($window)) => $fn,
            _ => (),
        }

        for mut $window in $windows.layouts.windows_mut() {
            $fn
        }
        for $window in $windows.layers.iter_mut() {
            $fn
        }
    }};
}

// Declared after the `with_all_windows[_mut]!` macros so these child modules
// are in the macros' textual scope.
mod capture;
mod lifecycle;
mod orientation;
mod render;
mod outputs;
mod ssd;
mod drag;
mod window_ops;
mod ipc;
mod switcher;
mod hit_test;
mod closing;
mod wallpaper;
mod cursor;
mod lock;
mod z_order;
mod session;
pub use capture::ScreenshotSink;
use cursor::import_cursor_surface;
use closing::ClosingWindow;
mod transaction;
pub(crate) use transaction::start_transaction;
use transaction::Transaction;
use drag::{MoveDrag, MoveDragX11, ResizeDrag, ResizeDragX11};


#[derive(Debug)]
pub struct Windows {
    pub window_scales: Vec<(AppIdMatcher, WindowScale)>,

    /// Regex patterns matched against the toplevel `app_id` to force CSD on
    /// the rendering side. The xdg-decoration protocol covers clients that
    /// participate; this list is the user's safety valve for clients that
    /// paint their own title bar without binding the protocol (and would
    /// otherwise end up with two stacked bars). Loaded from
    /// `forge.toml -> decoration.csd_apps`. See the matching SSD render
    /// guard in `Layouts::textures_and_ssd`.
    pub csd_apps: Vec<AppIdMatcher>,

    /// `forge.toml -> windows.snap_edge_fraction`: depth of the snap bands
    /// as a fraction of the work area's width (sides) or height (top and
    /// bottom).
    pub snap_edge_fraction: f64,

    orphan_popups: Vec<Window<PopupSurface>>,
    layouts: Layouts,
    layers: Layers,
    view: View,

    /// Surface a client attached through `wl_pointer.set_cursor`. Owns no
    /// layout: it is committed, imported and composed on the pointer path only.
    cursor_surface: Option<WlSurface>,

    /// Single source of truth for input focus. Keyboard focus and the xdg
    /// `Activated` state are both derived from this each frame in
    /// [`Self::focus`], so they cannot drift apart. `None` falls back to
    /// the active layout's window.
    focus_target: Option<FocusTarget>,

    /// Reconciliation cache: the toplevel currently carrying the xdg
    /// `Activated` state. Driven by [`Self::focus`]; never set directly.
    activated: Option<ToplevelSurface>,
    transaction: Option<Transaction>,
    textures: Vec<ForgeElement>,
    start_time: Instant,
    output: Output,
    /// Extend-mode screens beside the primary, each with its own region of
    /// the global logical space and its own scale. Empty while mirroring.
    extra_outputs: Vec<Output>,
    /// Session-lock surfaces for the extend screens, keyed by connector
    /// name. The primary's lives in `View::Lock`; these are display-only
    /// (keyboard focus stays on the primary's).
    extra_locks: Vec<(String, Box<Window<LockSurface>>)>,

    canvas: Canvas,
    unlocked_orientation: Orientation,
    orientation_locked: bool,
    dirty: bool,

    /// Last known compositor-space pointer position. Updated on every
    /// pointer motion via `pointer_moved`. Read at render time so the
    /// SSD title-bar can hit-test which window-control button (if any)
    /// is hovered and paint it with the theme's hover surface — without
    /// this, the close-button red-on-hover effect never fires because
    /// forge's SSD input pipeline is geometric and doesn't pump events
    /// into ltk's `UiSurface`.
    pointer_pos: Option<Point<f64, Logical>>,
    /// Corner being pushed by the pointer and how far along the push is
    /// (0..1); drives the accent fill drawn in that corner.
    corner_feedback: Option<(crate::corner_action::Corner, f64)>,
    /// Identity of the SSD button under the cursor on the previous
    /// motion event, as `(window-rc-pointer, action)`. Used by
    /// `pointer_moved` to mark the compositor dirty only when crossing
    /// a button boundary, so plain motion across the desktop does not
    /// thrash the render loop. The window pointer is a raw address, not
    /// dereferenced — only compared for equality.
    last_ssd_hover: Option<(usize, crate::ssd::SsdAction)>,

    wallpaper: Option<RenderTexture>,
    wallpaper_loaded: bool,
    wallpaper_scale: f64,
    /// Per-extend-screen wallpaper rasters, keyed by connector name; each
    /// baked to that screen's physical size and scale. `None` caches "no
    /// wallpaper file" so a missing file is not re-probed every frame.
    extra_wallpapers: std::collections::HashMap<
        String,
        (Option<RenderTexture>, Size<i32, Physical>, f64),
    >,

    /// Most-recently-used window order by globally-unique `ObjectId`.
    /// Front of the list = most recent. Used by the Alt+Tab switcher.
    /// `ObjectId` is used (not `protocol_id`) because `protocol_id` is
    /// per-client: two unrelated clients can each have a surface whose
    /// `protocol_id()` returns the same `u32`, causing the wrong window
    /// to be skipped or misplaced in the switcher.
    mru: Vec<smithay::reexports::wayland_server::backend::ObjectId>,

    /// In-progress title-bar drag (desktop mode). `None` when no drag is
    /// active.
    move_drag: Option<MoveDrag>,

    move_drag_x11: Option<MoveDragX11>,

    /// In-progress edge-handle resize drag.
    resize_drag: Option<ResizeDrag>,

    resize_drag_x11: Option<ResizeDragX11>,

    /// Next offset to apply when spawning a new floating window, so that
    /// identical defaults cascade diagonally instead of stacking.
    next_cascade: i32,

    /// Windows whose toplevel was destroyed but that are still on screen,
    /// fading out for the close animation. Each frame their snapshot is
    /// rendered with a shrink+fade transform until the timer expires, then
    /// the entry drops on `tick_closing_animations`.
    closing_windows: Vec<ClosingWindow>,

    /// Region (logical) to capture on the next in-loop render, and where the
    /// encoded PNG goes. Capturing inside the render pass keeps the GL context
    /// and imported client textures live, which an out-of-loop readback would
    /// not.
    pub pending_screenshot: Option<(Rectangle<i32, Logical>, ScreenshotSink)>,
    pub screenshot_result: Option<Vec<u8>>,

    /// Portal consent modal, rendered on top of everything.
    pub confirm: crate::confirm::ConfirmDialog,

    /// Mapped X11Surfaces. Populated from the `XwmHandler` impl on `Forge`,
    /// consumed in `import_buffers` and the textures pass.
    pub x11_windows: crate::x11_window::X11Windows,

    toplevel_z_order: Vec<WlSurface>,

    /// Per-app rect + maximized snapshot persisted to disk. Saved on
    /// every drag-end / maximize toggle for the focused toplevel,
    /// consulted in `surface_commit` to seed a freshly-mapped floating
    /// window with the geometry the user last left it at (eydos #7).
    geometry_store: crate::geometry_store::GeometryStore,

    /// Per-session, per-toplevel-name WM state for xdg-session-management.
    session_store: crate::session_store::SessionStore,
}


impl Windows {
    pub fn new(display: &DisplayHandle) -> Self {
        let output = Output::new_dummy(display);
        let canvas = *output.canvas();

        Self {
            output,
            extra_outputs: Vec::new(),
            extra_locks: Vec::new(),
            canvas,
            start_time: Instant::now(),
            orientation_locked: true,
            dirty: true,
            unlocked_orientation: Orientation::default(),
            orphan_popups: Vec::new(),
            cursor_surface: None,
            window_scales: Vec::new(),
            csd_apps: Vec::new(),
            snap_edge_fraction: 0.25,
            closing_windows: Vec::new(),
            pending_screenshot: None,
            screenshot_result: None,
            confirm: crate::confirm::ConfirmDialog::new(),
            x11_windows: crate::x11_window::X11Windows::new(),
            toplevel_z_order: Vec::new(),
            transaction: None,
            focus_target: None,
            activated: None,
            textures: Vec::new(),
            layouts: Layouts::default(),
            layers: Layers::default(),
            view: View::default(),
            wallpaper: None,
            wallpaper_loaded: false,
            wallpaper_scale: 0.0,
            extra_wallpapers: std::collections::HashMap::new(),
            mru: Vec::new(),
            move_drag: None,
            move_drag_x11: None,
            resize_drag: None,
            resize_drag_x11: None,
            next_cascade: 0,
            pointer_pos: None,
            corner_feedback: None,
            last_ssd_hover: None,
            geometry_store: crate::geometry_store::GeometryStore::load(),
            session_store: crate::session_store::SessionStore::load(),
        }
    }

    pub fn session_store_mut(&mut self) -> &mut crate::session_store::SessionStore {
        &mut self.session_store
    }

    /// Mark a window as most-recently-used. Front of the list = most recent.
    pub fn mru_touch(&mut self, oid: smithay::reexports::wayland_server::backend::ObjectId) {
        self.mru.retain(|id| id != &oid);
        self.mru.insert(0, oid);
        // Cap to keep growth bounded; old entries fall out.
        if self.mru.len() > 64 { self.mru.truncate(64); }
    }


    /// `ObjectId` of the currently focused toplevel, or `None` if there
    /// is no focus. Used by the app switcher to start the selection on
    /// the active window so Tab / Shift+Tab cycle relative to it.
    pub fn focused_object_id(
        &self,
    ) -> Option<smithay::reexports::wayland_server::backend::ObjectId> {
        if let Some(target) = &self.focus_target {
            match target {
                FocusTarget::Layout(weak) => {
                    if let Some(w) = weak.upgrade() {
                        return Some(w.borrow().surface.surface().id());
                    }
                },
                FocusTarget::Layer(s, _) | FocusTarget::X11(s) => return Some(s.id()),
            }
        }
        self.activated.as_ref().map(|s| s.wl_surface().id())
    }

    /// A window's full on-screen footprint — client body plus the SSD title
    /// bar — in logical coordinates, the region a window screenshot should grab.
    fn window_capture_rect(&self, w: &Window) -> Rectangle<i32, Logical> {
        let rect = w.output_rectangle();
        let bar_h = self.bar_h_for(w);
        Rectangle::new(
            Point::from((rect.loc.x, rect.loc.y - bar_h)),
            Size::from((rect.size.w, rect.size.h + bar_h)),
        )
    }

    pub fn focused_window_capture_rect(&self) -> Option<Rectangle<i32, Logical>> {
        let surface = self.activated.as_ref()?.wl_surface().clone();
        for layout in self.layouts.layouts() {
            for window in layout.windows() {
                let w = window.borrow();
                if w.owns_surface(&surface) {
                    return Some(self.window_capture_rect(&w));
                }
            }
        }
        None
    }

    pub fn window_rect_by_id(&self, id: u64) -> Option<Rectangle<i32, Logical>> {
        use smithay::reexports::wayland_server::Resource as _;
        for layout in self.layouts.layouts() {
            for window in layout.windows() {
                let w = window.borrow();
                if w.surface.surface().id().protocol_id() as u64 == id {
                    return Some(self.window_capture_rect(&w));
                }
            }
        }
        None
    }

    pub fn damaged(&mut self) -> bool {
        if self.dirty {
            return true;
        }

        // Drop expired close-animation entries first; this also flips
        // `self.dirty` to true on the very tick the last entry expires so
        // the frame loop renders one more clean frame after the animation
        // ends.
        self.tick_closing_animations();
        if self.dirty || !self.closing_windows.is_empty() {
            return true;
        }

        match &self.view {
            View::Workspace => {
                self.layouts.windows().any(|w| w.dirty()) || self.layers.iter().any(Window::dirty)
            },
            View::Fullscreen(window) => {
                window.borrow().dirty() || self.layers.overlay().any(Window::dirty)
            },
            View::Lock(window) => window.as_ref().is_some_and(|w| w.dirty()),
        }
    }

    pub fn on_double_tap(&mut self, point: Point<f64, Logical>) {
        if !matches!(self.view, View::Workspace) {
            return;
        }

        let canvas_size = self.canvas.size().to_f64();
        if point.y < (canvas_size.h - GESTURE_HANDLE_HEIGHT as f64) {
            return;
        }

        if point.x >= canvas_size.w / 1.5 {
            self.layouts.cycle_active(&self.output, 1);
        } else if point.x < canvas_size.w / 3.0 {
            self.layouts.cycle_active(&self.output, -1);
        }
    }

    /// True when the homescreen is what the user sees — no *visible* app on
    /// the active workspace (it is empty, or every window is minimised). The
    /// bottom swipe-up is then a homescreen gesture: the launcher from the
    /// dock-top zone, the switcher of the minimised apps from the very bottom.
    pub fn homescreen_active(&self) -> bool {
        matches!(self.view, View::Workspace)
            && self.layouts.active().windows().all(|w| w.borrow().minimized)
    }

    /// True when at least one toplevel exists (minimised or not), across
    /// every layout and the X11 set. Distinguishes "homescreen with
    /// minimised apps" (a bottom swipe opens the switcher) from "no apps at
    /// all" (it opens the launcher).
    pub fn has_windows(&self) -> bool {
        self.layouts.layouts().iter().any(|l| l.window_count() > 0)
            || self
                .x11_windows
                .entries()
                .iter()
                .any(|e| !e.surface.is_override_redirect() && e.surface.wl_surface().is_some())
    }


    /// Handle gesture in progress (bottom-edge handle drag). The overview it
    /// used to drive is gone; the only live outcome is settling on release.
    pub fn on_gesture(&mut self, _touch_state: &mut TouchState, _gesture: HandleGesture) -> bool {
        false
    }

    pub fn is_fullscreen(&self) -> bool {
        matches!(self.view, View::Fullscreen(_))
    }

    /// Handle gesture released: a vertical drag off the handle while a window
    /// is fullscreen exits fullscreen back to the workspace.
    pub fn on_gesture_done(&mut self, gesture: HandleGesture) -> bool {
        if let (HandleGesture::Vertical(position), View::Fullscreen(_)) = (gesture, &self.view) {
            let available = self.canvas.available_overview().to_f64();
            if position < available.loc.y || position >= available.loc.y + available.size.h {
                return false;
            }

            // Through the restore path, so the saved pre-fullscreen rect is
            // consumed and the client's Fullscreen state unset together.
            self.unfullscreen(None);
        }
        false
    }


    pub fn add_window_scale(&mut self, app_id: AppIdMatcher, scale: WindowScale) {
        self.window_scales.retain(|(m, _)| m.base() != app_id.base());

        match scale {
            WindowScale::Additive(s) | WindowScale::Subtractive(s) if s == 0.0 => (),
            WindowScale::Multiplicative(s) | WindowScale::Divisive(s) if s == 1.0 => (),
            _ => self.window_scales.push((app_id, scale)),
        }

        let window_scales = mem::take(&mut self.window_scales);
        let output_scale = self.output.scale();
        with_all_windows_mut!(self, |window| window.set_window_scale(&window_scales, output_scale));
        self.window_scales = window_scales;
    }

    pub fn surface_visible(&self, surface: &WlSurface) -> bool {
        if let View::Lock(Some(window)) = self.pending_view() {
            if window.owns_surface(surface) {
                return true;
            }
        }

        let mut visible_xdg = false;
        if self.canvas.is_mobile() {
            self.layouts.with_visible(|w| visible_xdg |= w.owns_surface(surface));
        } else {
            // Desktop renders every stacked toplevel, not just the active
            // layout. Mirror that here so idle-inhibitor checks (and any
            // other "is this surface on screen" check) don't treat
            // background-but-visible windows as hidden.
            visible_xdg = self.layouts.windows().any(|w| w.owns_surface(surface));
        }

        visible_xdg || self.layers.iter().any(|l| l.owns_surface(surface))
    }

    pub fn runtime(&self) -> u32 {
        self.start_time.elapsed().as_millis() as u32
    }

    fn pending_view(&self) -> &View {
        self.transaction.as_ref().and_then(|t| t.view.as_ref()).unwrap_or(&self.view)
    }

    fn pending_view_mut(&mut self) -> &mut View {
        self.transaction.as_mut().and_then(|t| t.view.as_mut()).unwrap_or(&mut self.view)
    }

    fn set_view(&mut self, view: View) {
        if let View::Lock(_) = self.pending_view() {
            return;
        }

        self.start_transaction().view = Some(view);
    }

    pub fn set_dirty(&mut self) {
        self.dirty = true;
    }

    /// Record a pointer motion. Stores the position so the SSD render
    /// path can compute hover state for window-control buttons, and
    /// marks the compositor dirty when the pointer crosses into / out
    /// of a button (so the next frame can repaint the hover surface).
    /// Pointer motion that stays inside the same button — or moves
    /// across non-button areas — does not dirty.
    pub fn set_corner_feedback(&mut self, fb: Option<(crate::corner_action::Corner, f64)>) {
        if self.corner_feedback != fb {
            self.corner_feedback = fb;
            self.set_dirty();
        }
    }

    pub fn pointer_moved(&mut self, pos: Point<f64, Logical>) {
        self.pointer_pos = Some(pos);
        let cur = self.ssd_button_hover_summary(pos);
        if cur != self.last_ssd_hover {
            self.last_ssd_hover = cur;
            self.set_dirty();
        }
    }

    /// Compress the current pointer-vs-SSD-buttons relationship to
    /// `(window-identity, button-action)` for transition detection.
    /// Returns `None` when the pointer is off all SSD bars or sitting
    /// on the move-area of a bar (Move never paints a hover surface,
    /// so it should not trigger a redraw).
    fn ssd_button_hover_summary(
        &self,
        pos: Point<f64, Logical>,
    ) -> Option<(usize, crate::ssd::SsdAction)> {
        let (w, action) = self.ssd_window_hit_test(pos)?;
        if matches!(action, crate::ssd::SsdAction::Move) { return None; }
        let addr = std::rc::Rc::as_ptr(&w) as usize;
        Some((addr, action))
    }

    pub fn raise(&mut self, surface: &WlSurface) {
        if let Some(pos) = self.layouts.position(surface) {
            self.layouts.set_active(&self.output, Some(pos), false);
            if let Some(id) = self.layouts.layout_id_for(surface) {
                self.layouts.raise_to_top(id);
            }
            self.mru_touch(surface.id());
        }
    }

    pub fn set_urgent(&mut self, surface: &WlSurface, urgent: bool) {
        if let Some(window) = self.layouts.find_window(surface) {
            window.borrow_mut().urgent = urgent;
        }
    }

    pub fn parent_geometry(&self, surface: &WlSurface) -> Rectangle<i32, Logical> {
        with_all_windows!(self, |window| {
            if window.owns_surface(surface) {
                return window.bounds(self.output.scale());
            }
        });

        Rectangle::from_size(self.output.size())
    }



}


#[derive(Default, Debug)]
enum View {
    Fullscreen(Rc<RefCell<Window>>),
    Lock(Option<Box<Window<LockSurface>>>),
    #[default]
    Workspace,
}
