//! Output/monitor management: the primary and extend-mode screens, their
//! layout in the global logical space, per-connector scale picks, topbar
//! configuration and the mobile override. Operates on `Windows`' output state.

#[cfg(feature = "winit")]
use smithay::output::Mode;

use super::*;

impl Windows {
    pub fn set_output(&mut self, output: Output) {
        self.canvas = *output.canvas();
        self.output = output;
    }

    /// Register an extend-mode screen beside the primary. The primary stays
    /// in `self.output`; extras only contribute their region to the global
    /// space and their own scale — chrome and layers remain on the primary.
    pub fn add_extra_output(&mut self, output: Output) {
        let name = output.smithay_output().name();
        self.extra_outputs.retain(|o| o.smithay_output().name() != name);
        self.extra_outputs.push(output);
        self.place_deferred_on(&name);
    }

    pub fn remove_extra_output(&mut self, name: &str) {
        self.extra_outputs.retain(|o| o.smithay_output().name() != name);
        self.extra_wallpapers.remove(name);
    }

    pub fn clear_extra_outputs(&mut self) {
        self.extra_outputs.clear();
        self.extra_wallpapers.clear();
    }

    pub fn primary_output(&self) -> &Output {
        &self.output
    }

    pub fn extra_outputs(&self) -> &[Output] {
        &self.extra_outputs
    }

    /// Every screen's region of the global logical space, primary first.
    pub fn output_rects(&self) -> Vec<Rectangle<i32, Logical>> {
        let mut rects = vec![self.output.logical_rect()];
        rects.extend(self.extra_outputs.iter().map(|o| o.logical_rect()));
        rects
    }

    /// Resolve a client-named wl_output to a capture source: its canvas
    /// plus, for an extend screen, its connector name (`None` = primary).
    pub fn capture_source(
        &self,
        wl_output: &smithay::reexports::wayland_server::protocol::wl_output::WlOutput,
    ) -> (Canvas, Option<String>) {
        for o in &self.extra_outputs {
            if o.smithay_output().owns(wl_output) {
                return (*o.canvas(), Some(o.smithay_output().name()));
            }
        }
        (self.canvas, None)
    }

    /// Live layout snapshot for Settings: connector name, region of the
    /// global space, and the scale in force. Primary first.
    pub fn output_layout(&self) -> Vec<(String, Rectangle<i32, Logical>, f64)> {
        std::iter::once(&self.output)
            .chain(self.extra_outputs.iter())
            .map(|o| (o.smithay_output().name(), o.logical_rect(), o.scale()))
            .collect()
    }

    /// Per-connector pinned scale in force right now (`0.0` = automatic), so
    /// Settings shows what the compositor actually holds rather than what the
    /// config file last recorded.
    pub fn output_scale_picks(&self) -> Vec<(String, f64)> {
        std::iter::once(&self.output)
            .chain(self.extra_outputs.iter())
            .map(|o| (o.smithay_output().name(), o.scale_pick().unwrap_or(0.0)))
            .collect()
    }

    /// Apply a per-connector scale pick to any screen, primary included;
    /// `None` returns it to its own panel-DPI automatic. Returns the scale
    /// now in force so the caller can retune that head's damage tracker.
    pub fn set_output_scale_pick(&mut self, name: &str, pick: Option<f64>) -> Option<f64> {
        let o = if self.output.smithay_output().name() == name {
            &mut self.output
        } else {
            self.extra_outputs
                .iter_mut()
                .find(|o| o.smithay_output().name() == name)?
        };
        o.set_scale_pick(pick);
        let applied = o.scale();
        let primary = self.output.smithay_output().name() == name;

        self.start_transaction();
        if primary {
            // Every window's buffer scale follows the primary's, so the whole
            // session relayouts; `refresh_output_overlaps` then re-points the
            // ones living on another screen at their own.
            self.canvas = *self.output.canvas();
            with_all_windows!(self, |window| window.update_scale(applied));
        }
        self.resize_all();
        Some(applied)
    }

    /// User-driven placement of an extend screen from Settings; pins the
    /// location so the automatic re-flow leaves it alone.
    pub fn set_extra_output_position(&mut self, name: &str, pos: Point<i32, Logical>) -> bool {
        let Some(o) = self
            .extra_outputs
            .iter_mut()
            .find(|o| o.smithay_output().name() == name)
        else {
            return false;
        };
        o.pin_location(pos);
        self.start_transaction();
        self.resize_all();
        true
    }

    /// The screen under `pos`: the primary when inside it (or when no extra
    /// contains the point), otherwise the extra whose region contains it.
    pub fn output_at(&self, pos: Point<f64, Logical>) -> &Output {
        if self.output.logical_rect().to_f64().contains(pos) {
            return &self.output;
        }
        self.extra_outputs
            .iter()
            .find(|o| o.logical_rect().to_f64().contains(pos))
            .unwrap_or(&self.output)
    }

    #[cfg(feature = "winit")]
    pub fn resize_output(&mut self, mode: Mode) {
        // Update mode without changing the transform (winit needs Flipped180)
        self.output.smithay_output().change_current_state(
            Some(mode), None, None, None,
        );
        self.output.smithay_output().set_preferred(mode);
        self.output.canvas_mut().set_mode(mode);
        // Re-derive the scale for the new mode: the initial mode may have been
        // a transient portrait window (restored from saved geometry), which
        // pins the scale to 2× and never recovers otherwise.
        self.output.recompute_scale(mode);
        self.canvas = *self.output.canvas();
        self.resize_all();
    }

    pub fn canvas(&self) -> &Canvas {
        &self.canvas
    }

    pub fn output_name(&self) -> String {
        self.output.smithay_output().name()
    }

    pub fn available_top_y(&self) -> i32 {
        self.output.available().loc.y
    }

    pub fn set_scale(&mut self, scale: f64) {
        self.start_transaction();
        self.output.set_scale(scale);

        with_all_windows!(self, |window| window.update_scale(scale));

        self.resize_all();
    }

    /// Scale currently in force, whatever decided it.
    pub fn output_scale(&self) -> f64 {
        self.output.scale()
    }

    /// Scale the automatic path would choose, ignoring any override.
    pub fn automatic_output_scale(&self) -> f64 {
        self.output.automatic_scale()
    }

    /// The primary's own pinned scale, if it has one.
    pub fn output_scale_pick(&self) -> Option<f64> {
        self.output.scale_pick()
    }

    /// Re-derive the output scale from the current precedence and relayout
    /// every window against it. Used when the inputs to that decision move:
    /// a hotplug, or the user picking a different interface scale.
    pub fn apply_output_scale(&mut self) {
        self.start_transaction();
        self.output.refresh_scale();

        let scale = self.output.scale();
        with_all_windows!(self, |window| window.update_scale(scale));

        self.resize_all();
    }

    pub fn configure_topbar(&mut self, height: i32) {
        self.output.canvas_mut().set_topbar_height(height);
        self.canvas.set_topbar_height(height);
        self.dirty = true;
        self.resize_all();
    }

    pub fn set_mobile_override(&mut self, enabled: Option<bool>) {
        self.output.canvas_mut().set_mobile_override(enabled);
        self.canvas.set_mobile_override(enabled);
        self.dirty = true;
        self.resize_all();
    }

}

// Moving a window to another output (multi-monitor placement).
impl Windows {
    /// Connector name of the screen under `pos`; `None` for the primary.
    pub fn output_name_at(&self, pos: Point<f64, Logical>) -> Option<String> {
        let o = self.output_at(pos);
        (!self.is_primary(o)).then(|| o.smithay_output().name())
    }

    /// Move the window identified by `oid` to the next screen in the
    /// arrangement (left to right, wrapping), preserving its relative
    /// position within the screen. No-op with a single screen.
    pub fn move_window_to_next_output(
        &mut self,
        oid: &smithay::reexports::wayland_server::backend::ObjectId,
        forward: bool,
    ) -> bool {
        if self.extra_outputs.is_empty() {
            return false;
        }
        // Screens ordered as they sit on the desk, so "next" is spatial.
        let mut screens: Vec<(Option<String>, Rectangle<i32, Logical>)> = self.output_layout()
            .into_iter()
            .map(|(name, rect, _)| {
                let primary = name == self.output.smithay_output().name();
                (( !primary ).then_some(name), rect)
            })
            .collect();
        screens.sort_by_key(|(_, r)| (r.loc.x, r.loc.y));
        let Some(rect) = self.window_rect_for(oid) else { return false };
        let current = Self::owner_rect_in(&self.output_rects(), rect);
        let Some(idx) = screens.iter().position(|(_, r)| *r == current) else { return false };
        let next = if forward {
            (idx + 1) % screens.len()
        } else {
            (idx + screens.len() - 1) % screens.len()
        };
        if next == idx {
            return false;
        }
        self.move_window_to_output(oid, screens[next].0.as_deref())
    }

    fn window_rect_for(
        &self,
        oid: &smithay::reexports::wayland_server::backend::ObjectId,
    ) -> Option<Rectangle<i32, Logical>> {
        self.layouts.layouts().iter().find_map(|layout| {
            layout
                .windows()
                .find(|w| &w.borrow().surface.surface().id() == oid)
                .map(|w| w.borrow().output_rectangle())
        })
    }

    /// Move the window identified by `oid` onto `screen` (`None` = primary),
    /// keeping its position within the screen proportional. Returns false
    /// when the window is unknown or already there.
    pub fn move_window_to_output(
        &mut self,
        oid: &smithay::reexports::wayland_server::backend::ObjectId,
        screen: Option<&str>,
    ) -> bool {
        let Some(window_rc) = self.layouts.layouts().iter().find_map(|layout| {
            layout
                .windows()
                .find(|w| &w.borrow().surface.surface().id() == oid)
                .cloned()
        }) else {
            return false;
        };

        let target = match screen {
            Some(name) => match self
                .extra_outputs
                .iter()
                .find(|o| o.smithay_output().name() == name)
            {
                Some(o) => o.logical_rect(),
                None => return false,
            },
            None => self.output.logical_rect(),
        };
        let rect = window_rc.borrow().output_rectangle();
        let current = Self::owner_rect_in(&self.output_rects(), rect);
        if current == target {
            return false;
        }

        // Keep the window where it sat proportionally, then let the fit pass
        // deal with a smaller screen.
        let fx = (rect.loc.x - current.loc.x) as f64 / current.size.w.max(1) as f64;
        let fy = (rect.loc.y - current.loc.y) as f64 / current.size.h.max(1) as f64;
        let loc = Point::<i32, Logical>::from((
            target.loc.x + (fx * target.size.w as f64).round() as i32,
            target.loc.y + (fy * target.size.h as f64).round() as i32,
        ));

        let scale = self.output.scale();
        {
            let mut w = window_rc.borrow_mut();
            let maximized = w.is_maximized();
            if maximized {
                w.unmaximize(scale);
            }
            w.set_floating_position(loc);
            let area = self.owner_area(Rectangle::new(loc, w.output_rectangle().size));
            let bar_h = self.bar_h_for(&w);
            w.clamp_to_available(scale, area, bar_h);
            if maximized {
                w.toggle_maximize(scale, area, bar_h);
            }
        }
        self.record_geometry(&mut window_rc.borrow_mut());
        self.refresh_output_overlaps();
        self.dirty = true;
        true
    }
}

/// Which screen owns a rect, and the derived per-output bookkeeping.
impl Windows {
    /// [`Self::owner_output`] over a precollected rect list (primary first),
    /// for loops that hold a mutable borrow of the window collections.
    pub(super) fn owner_rect_in(
        rects: &[Rectangle<i32, Logical>],
        rect: Rectangle<i32, Logical>,
    ) -> Rectangle<i32, Logical> {
        let overlap = |r: &Rectangle<i32, Logical>| {
            r.intersection(rect)
                .map(|i| i.size.w as i64 * i.size.h as i64)
                .unwrap_or(0)
        };
        let mut best = rects[0];
        let mut best_overlap = overlap(&rects[0]);
        for r in &rects[1..] {
            let candidate = overlap(r);
            if candidate > best_overlap {
                best_overlap = candidate;
                best = *r;
            }
        }
        best
    }

    /// The screen owning `rect` (max overlap; the primary on ties or no
    /// overlap at all).
    pub(super) fn owner_output(&self, rect: Rectangle<i32, Logical>) -> &Output {
        let overlap = |o: &Output| {
            o.logical_rect()
                .intersection(rect)
                .map(|i| i.size.w as i64 * i.size.h as i64)
                .unwrap_or(0)
        };
        let mut best = &self.output;
        let mut best_overlap = overlap(&self.output);
        for o in &self.extra_outputs {
            let candidate = overlap(o);
            if candidate > best_overlap {
                best_overlap = candidate;
                best = o;
            }
        }
        best
    }

    pub(super) fn is_primary(&self, output: &Output) -> bool {
        output.smithay_output().name() == self.output.smithay_output().name()
    }

    /// Reconcile every desktop window's wl_output enter/leave with the
    /// screens its rect overlaps, and point its preferred fractional scale
    /// at its owner's — so a window moved to the external re-renders at
    /// that screen's density. Redundant calls are cheap: smithay
    /// deduplicates the protocol side.
    pub fn refresh_output_overlaps(&mut self) {
        if self.extra_outputs.is_empty() {
            return;
        }
        for window in self.layouts.windows() {
            let rect = window.output_rectangle();
            for output in std::iter::once(&self.output).chain(self.extra_outputs.iter()) {
                window.sync_output_overlap(output, output.logical_rect().overlaps(rect));
            }
            window.update_scale(self.owner_output(rect).scale());
        }
    }

    /// Work area of the screen owning `rect`, so re-fit and maximize target
    /// a window's own screen instead of the primary. Extras carry no
    /// chrome, so their whole region is the work area.
    pub(super) fn owner_area(&self, rect: Rectangle<i32, Logical>) -> Rectangle<i32, Logical> {
        let owner = self.owner_output(rect);
        if self.is_primary(owner) {
            self.output.available()
        } else {
            owner.logical_rect()
        }
    }


}
