//! Element passes and thumbnails for one window.

use super::*;

impl<S: Surface + 'static> Window<S> {
    pub fn textures(
        &self,
        textures: &mut Vec<ForgeElement>,
        output_scale: f64,
        window_scale: impl Into<Option<f64>>,
        location: impl Into<Option<Point<i32, Logical>>>,
    ) {
        self.textures_with_bounds(textures, output_scale, window_scale, location, None, None);
    }

    /// Like [`textures`] but rounds the bottom corners of the window body,
    /// used by the desktop SSD paint path so a server-decorated window's
    /// bottom matches the rounded top of its title bar. `round` is `None`
    /// for windows forge does not decorate.
    pub fn textures_rounded(
        &self,
        textures: &mut Vec<ForgeElement>,
        output_scale: f64,
        round: Option<&crate::rounded::Rounding>,
    ) {
        self.textures_with_bounds(textures, output_scale, None, None, None, round);
    }

    /// Return the topmost rendered texture for this window, used by Alt+Tab
    /// thumbnails and the close-animation tombstone. Falls back to the
    /// last-known-good thumbnail when the live cache is empty — this happens
    /// for clients (foot terminal, …) that commit a NULL buffer right before
    /// destroying the toplevel: the empty commit clears `texture_cache` and
    /// without this fallback the close animation has nothing to fade out.
    /// Still returns `None` for windows that have never rendered a frame.
    pub fn thumbnail_texture(&self) -> Option<RenderTexture> {
        self.pick_thumbnail_texture()
            .or_else(|| self.last_good_thumbnail.clone())
    }

    /// Pixel size of the thumbnail texture in physical pixels (or `None`).
    /// Mirrors [`thumbnail_texture`]'s picking rule so callers can size the
    /// thumbnail to the actual window content's aspect ratio, not a stray
    /// subsurface.
    pub fn thumbnail_size(&self) -> Option<smithay::utils::Size<i32, smithay::utils::Logical>> {
        self.pick_thumbnail_texture()
            .map(|t| t.size())
            .or_else(|| self.last_good_thumbnail.as_ref().map(|t| t.size()))
    }

    /// Pick the texture that best represents the window's visible content.
    /// Apps with CSD drop-shadows (Firefox, GTK4) commit a sub-surface that
    /// is larger than the content but mostly transparent — a plain
    /// max-by-area pick yields that shadow buffer and the thumbnail reads
    /// as empty. Prefer the texture whose size matches the xdg-shell
    /// `set_window_geometry` rect; fall back to max-by-area when the
    /// window does not declare a geometry.
    pub(super) fn pick_thumbnail_texture(&self) -> Option<RenderTexture> {
        let texs = &self.texture_cache.textures;
        if let Some(geom) = self.texture_cache.geometry_size {
            // Firefox (and other multi-surface clients) commit a content
            // surface plus a same-size transparent overlay. Both match the
            // geometry, so a plain closest-by-size pick can land on the
            // overlay and the thumbnail reads empty. Among the geometry-sized
            // surfaces, prefer one that carries opaque content.
            let near_geom = |t: &RenderTexture| {
                let s = t.size();
                (s.w - geom.w).abs() <= 2 && (s.h - geom.h).abs() <= 2
            };
            if let Some(t) = texs.iter().find(|t| near_geom(t) && t.is_opaque()) {
                return Some(t.clone());
            }
            if let Some(t) = texs.iter().min_by_key(|t| {
                let s = t.size();
                ((s.w - geom.w).abs() + (s.h - geom.h).abs()) as i64
            }) {
                return Some(t.clone());
            }
        }
        // No geometry declared: prefer the largest opaque surface, then fall
        // back to the largest by area.
        texs.iter()
            .filter(|t| t.is_opaque())
            .max_by_key(|t| {
                let s = t.size();
                (s.w as i64) * (s.h as i64)
            })
            .or_else(|| {
                texs.iter().max_by_key(|t| {
                    let s = t.size();
                    (s.w as i64) * (s.h as i64)
                })
            })
            .cloned()
    }

    /// Capture the current content texture into a plain offscreen 2D texture
    /// for the app switcher. Rendered at native scale (1.0) — the size the
    /// source already composites correctly — so the carousel can then scale an
    /// ordinary texture instead of the client's dmabuf/external buffer, which
    /// renders black when scaled on some GPUs (Librem 5 / etnaviv). No-op when
    /// already captured or there is no content yet.
    pub fn snapshot_thumbnail(&mut self, renderer: &mut GlesRenderer, output_scale: f64) {
        if self.thumbnail_snapshot.is_some() {
            return;
        }
        // Capture the WHOLE window (all surfaces composited), not a single
        // picked texture: Firefox draws the start page into its main surface
        // but live web content into a separate same-size subsurface, so
        // picking one leaves the other (transparent) and the thumbnail goes
        // black. Compositing the geometry rect at (0,0) reproduces the window.
        let size_log = self
            .texture_cache
            .geometry_size
            .or_else(|| self.pick_thumbnail_texture().map(|t| t.size()));
        let Some(size_log) = size_log else { return };
        if size_log.w <= 0 || size_log.h <= 0 {
            return;
        }
        let size_phys = size_log.to_f64().to_physical(output_scale).to_i32_round();
        if size_phys.w <= 0 || size_phys.h <= 0 {
            return;
        }

        let bounds = Rectangle::<i32, Physical>::new(Point::from((0, 0)), size_phys);
        let mut elements: Vec<ForgeElement> = Vec::new();
        // location (0,0): a window with a geometry keeps its content at the
        // texture-cache origin, so this lands the content top-left at (0,0).
        self.textures_with_bounds(
            &mut elements,
            output_scale,
            None,
            Some(Point::<i32, Logical>::from((0, 0))),
            Some(bounds),
            None,
        );
        if elements.is_empty() {
            return;
        }

        let buffer_size = Size::<i32, BufferCoord>::from((size_phys.w, size_phys.h));
        let tex: GlesTexture = match crate::drawing::render_elements_offscreen(
            renderer,
            Fourcc::Abgr8888,
            buffer_size,
            size_phys,
            output_scale,
            Transform::Normal,
            [0.0, 0.0, 0.0, 0.0],
            &elements,
            &[bounds],
        ) {
            Ok((tex, _sync)) => tex,
            Err(_) => return,
        };

        self.thumbnail_snapshot = Some(RenderTexture::new(Texture::new(tex, size_log, output_scale, true)));
    }

    /// The texture the app switcher should show: the offscreen snapshot if it
    /// has been captured, otherwise the live content texture.
    pub fn switcher_thumbnail(&self) -> Option<RenderTexture> {
        self.thumbnail_snapshot
            .clone()
            .or_else(|| self.thumbnail_texture())
    }

    /// Logical size of [`switcher_thumbnail`], for the carousel's aspect fit.
    pub fn switcher_thumbnail_size(&self) -> Option<Size<i32, Logical>> {
        self.thumbnail_snapshot
            .as_ref()
            .map(|t| t.size())
            .or_else(|| self.thumbnail_size())
    }

    pub fn textures_with_bounds(
        &self,
        textures: &mut Vec<ForgeElement>,
        output_scale: f64,
        window_scale: impl Into<Option<f64>>,
        location: impl Into<Option<Point<i32, Logical>>>,
        bounds: impl Into<Option<Rectangle<i32, Physical>>>,
        round: Option<&crate::rounded::Rounding>,
    ) {
        let location = location
            .into()
            .unwrap_or_else(|| self.bounds(output_scale).loc);
        let window_scale = window_scale.into().unwrap_or(1.0);

        let bounds = bounds.into().unwrap_or_else(|| {
            let mut b = self
                .bounds(output_scale)
                .to_physical_precise_round(output_scale);
            b.loc = location.to_physical_precise_round(output_scale);
            b
        });

        // Clip each popup to its own content (its `output_rectangle` carries no
        // size), not the parent's bounds, so a side-opening submenu is not cut
        // off at the parent window's edge.
        for popup in self.popups.iter().rev() {
            let popup_loc = location + popup.bounds(output_scale).loc.scale(window_scale);
            let clip = Rectangle::new(
                (popup_loc + popup.texture_cache.origin_delta)
                    .to_physical_precise_round(output_scale),
                popup.texture_cache.size().to_physical_precise_round(output_scale),
            );
            popup.textures_with_bounds(
                textures,
                output_scale,
                window_scale,
                Some(popup_loc),
                Some(clip),
                None,
            );
        }

        // Round only the texture that represents the window body — the one
        // covering the bottom corners — never a stray subsurface (CSD
        // shadow, popup overlay).
        let round_id: Option<Id> = round
            .and_then(|_| self.pick_thumbnail_texture())
            .map(|t| t.id().clone());

        // Re-apply the origin shift that `TextureCache::reset_origin` removed
        // when normalising negative subsurface offsets to a (0,0)-based space.
        // Without this a subsurface positioned above the parent origin (e.g. a
        // slide-up reveal) stays pinned at the origin instead of moving. Matches
        // the input path, which subtracts `origin_delta` (see `surface_under`).
        let physical_loc = ( location + self.texture_cache.origin_delta ).to_physical_precise_round(output_scale);
        for texture in self.texture_cache.textures.iter().rev() {
            if let Some(r) = round {
                if Some(texture.id()) == round_id.as_ref() {
                    ForgeElement::add_element_rounded(
                        textures,
                        texture.clone(),
                        physical_loc,
                        bounds,
                        window_scale,
                        output_scale,
                        r.program.clone(),
                        r.radius,
                    );
                    continue;
                }
            }
            ForgeElement::add_element(
                textures,
                texture.clone(),
                physical_loc,
                bounds,
                window_scale,
                output_scale,
            );
        }
    }

}
