use std::ops::{Add, Deref, Sub};
use std::time::Duration;

use smithay::output::{
    Mode, Output as SmithayOutput, OutputModeSource, PhysicalProperties, Scale, Subpixel,
};
use smithay::reexports::wayland_server::backend::GlobalId;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::reexports::wayland_server::DisplayHandle;
use smithay::utils::{Logical, Physical, Rectangle, Size};
use smithay::wayland::shell::wlr_layer::{Anchor, ExclusiveZone, Layer};

use crate::forge::Forge;
use crate::orientation::Orientation;

pub const GESTURE_HANDLE_HEIGHT: i32 = 15;

const NANOS_PER_SECOND: u64 = 1_000_000_000_000;
const MOBILE_DPI_THRESHOLD: f64 = 200.0;

#[derive(Debug)]
pub struct Output {
    global: Option<GlobalId>,
    display: DisplayHandle,
    output: SmithayOutput,
    canvas: Canvas,
}

impl Drop for Output {
    fn drop(&mut self) {
        if let Some(global) = self.global.take() {
            self.display.remove_global::<Forge>(global);
        }
    }
}

impl Output {
    pub fn new(
        display: &DisplayHandle,
        name: impl Into<String>,
        mode: Mode,
        properties: PhysicalProperties,
    ) -> Self {
        let scale = Self::compute_scale(mode, &properties);
        let output = SmithayOutput::new(name.into(), properties);
        let global = Some(output.create_global::<Forge>(display));

        let mut this =
            Self { global, output, canvas: Canvas::new(mode, scale), display: display.clone() };

        this.set_mode(this.mode);
        this
    }

    /// Compute display scale from EDID physical size and resolution.
    /// Mobile panels (>200 DPI) get scale 2.0, desktop monitors get 1.0.
    fn compute_scale(mode: Mode, properties: &PhysicalProperties) -> f64 {
        let (pw_mm, ph_mm) = (properties.size.w as f64, properties.size.h as f64);
        let (res_w, res_h) = (mode.size.w as f64, mode.size.h as f64);

        if pw_mm > 0.0 && ph_mm > 0.0 && res_w > 0.0 && res_h > 0.0 {
            let dpi_x = res_w / (pw_mm / 25.4);
            let dpi_y = res_h / (ph_mm / 25.4);
            let dpi = (dpi_x + dpi_y) / 2.0;

            if dpi > MOBILE_DPI_THRESHOLD {
                2.0
            } else {
                1.0
            }
        } else {
            1.0
        }
    }

    pub fn new_dummy(display: &DisplayHandle) -> Self {
        let mode = Mode { size: (0, 0).into(), refresh: 0 };
        let properties = PhysicalProperties {
            subpixel: Subpixel::Unknown,
            serial_number: "dummy-0".into(),
            model: "dummy-0".into(),
            make: "dummy-0".into(),
            size: (0, 0).into(),
        };
        let output = SmithayOutput::new("dummy-0".into(), properties);
        let canvas = Canvas::new(mode, 1.0);

        Self { global: None, output, canvas, display: display.clone() }
    }

    pub fn set_scale(&mut self, mut scale: f64) {
        scale = (scale * 120.0).round() / 120.0;
        self.canvas.scale = scale;
        self.set_mode(self.canvas.mode);
    }

    pub fn set_mode(&mut self, mode: Mode) {
        self.output.change_current_state(
            Some(mode),
            Some(self.orientation.surface_transform()),
            Some(Scale::Fractional(self.scale())),
            None,
        );
        self.output.set_preferred(mode);
        self.canvas.mode = mode;
    }

    pub fn primary_rectangle(&self, secondary_visible: bool) -> Rectangle<i32, Logical> {
        let avail = self.available();
        let (w, h) = (avail.size.w, avail.size.h);

        let size = match (h > w && secondary_visible, w > h && secondary_visible) {
            (true, _) => (w, (h + 1) / 2),
            (_, true) => ((w + 1) / 2, h),
            _ => (w, h),
        };

        Rectangle::new(avail.loc, size.into())
    }

    pub fn secondary_rectangle(&self) -> Rectangle<i32, Logical> {
        let avail = self.available();
        let (w, h) = (avail.size.w, avail.size.h);
        let mut loc = avail.loc;

        if h > w {
            loc.y += (h + 1) / 2;
            Rectangle::new(loc, (w, h / 2).into())
        } else {
            loc.x += (w + 1) / 2;
            Rectangle::new(loc, (w / 2, h).into())
        }
    }

    pub fn set_orientation(&mut self, orientation: Orientation) {
        self.canvas.orientation = orientation;
        self.set_mode(self.canvas.mode);
    }

    pub fn enter(&self, surface: &WlSurface) {
        self.output.enter(surface);
    }

    pub fn leave(&self, surface: &WlSurface) {
        self.output.leave(surface);
    }

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

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

    pub fn exclusive(&mut self) -> &mut ExclusiveSpace {
        &mut self.canvas.exclusive
    }

    pub fn smithay_output(&self) -> &SmithayOutput {
        &self.output
    }
}

impl Deref for Output {
    type Target = Canvas;

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

#[derive(Copy, Clone, Debug)]
pub struct Canvas {
    exclusive: ExclusiveSpace,
    orientation: Orientation,
    scale: f64,
    mode: Mode,
    pub status_bar_height: i32,
    /// Reserved logical pixels at the top for an external topbar (e.g. crustace).
    /// Set via IPC `{"action":"configure_topbar","height":N}`.
    pub topbar_height: i32,
    /// When `Some`, overrides the auto-detected `is_mobile()` result.
    /// Set via env `FORGE_MOBILE` before launch or IPC `{"action":"set_mobile","enabled":bool}`.
    mobile_override: Option<bool>,
}

impl Canvas {
    pub fn set_mode(&mut self, mode: Mode) {
        self.mode = mode;
    }

    fn new(mode: Mode, scale: f64) -> Self {
        Self {
            mode,
            scale,
            orientation: Orientation::default(),
            exclusive: ExclusiveSpace::default(),
            status_bar_height: 0,  // Default disabled; set_statusbar_enabled(true) enables it
            topbar_height: 0,
            mobile_override: None,
        }
    }

    /// Set status bar height (built-in status bar has been removed).
    pub fn set_statusbar_enabled(&mut self, _enabled: bool) {
        self.status_bar_height = 0;
    }

    pub fn is_mobile(&self) -> bool {
        if let Some(forced) = self.mobile_override {
            return forced;
        }
        if self.scale < 2.0 {
            return false;
        }
        // High DPI alone isn't enough — HiDPI laptops also have scale >= 2.0.
        // Phones have small logical resolution (shorter side < 600 logical pixels).
        // Laptops at 2x: 2560x1440 → 720 logical, 3840x2160 → 1080 logical.
        // Phones at 2x:  720x1440 → 360 logical.
        let min_physical = self.mode.size.w.min(self.mode.size.h);
        let logical_min = (min_physical as f64 / self.scale) as i32;
        logical_min < 600
    }

    pub fn set_mobile_override(&mut self, mobile: Option<bool>) {
        self.mobile_override = mobile;
    }

    pub fn set_topbar_height(&mut self, height: i32) {
        self.topbar_height = height.max(0);
    }

    pub fn orientation(&self) -> Orientation {
        self.orientation
    }

    pub fn physical_resolution(&self) -> Size<i32, Physical> {
        self.mode.size
    }

    pub fn physical_size(&self) -> Size<i32, Physical> {
        let (w, h) = self.physical_resolution().into();
        match self.orientation {
            Orientation::Portrait | Orientation::InversePortrait => (w, h).into(),
            Orientation::Landscape | Orientation::InverseLandscape => (h, w).into(),
        }
    }

    pub fn resolution(&self) -> Size<i32, Logical> {
        self.physical_resolution().to_f64().to_logical(self.scale).to_i32_round()
    }

    pub fn size(&self) -> Size<i32, Logical> {
        self.physical_size().to_f64().to_logical(self.scale).to_i32_round()
    }

    pub fn gesture_handle_height(&self) -> i32 {
        if self.is_mobile() { GESTURE_HANDLE_HEIGHT } else { 0 }
    }

    pub fn wm_size(&self) -> Size<i32, Logical> {
        let mut size = self.size();
        size.h -= self.gesture_handle_height();
        size
    }

    pub fn available(&self) -> Rectangle<i32, Logical> {
        let top_offset = *self.exclusive.top + self.status_bar_height + self.topbar_height;
        let loc = (*self.exclusive.left, top_offset);
        let mut size = self.wm_size();
        size.w -= *self.exclusive.left + self.exclusive.right;
        size.h -= top_offset + self.exclusive.bottom;
        Rectangle::new(loc.into(), size)
    }

    pub fn available_overview(&self) -> Rectangle<i32, Logical> {
        let reserved = |e: Exclusivity| match e {
            Exclusivity::Top(_) | Exclusivity::Overlay(_) => 0,
            Exclusivity::Background(r) | Exclusivity::Bottom(r) => r,
        };

        let (top, right, bottom, left) = (
            reserved(self.exclusive.top) + self.status_bar_height,
            reserved(self.exclusive.right),
            reserved(self.exclusive.bottom),
            reserved(self.exclusive.left),
        );

        let mut size = self.wm_size();
        size.w -= left + right;
        size.h -= top + bottom;
        Rectangle::new((left, top).into(), size)
    }

    pub fn available_fullscreen(&self) -> Rectangle<i32, Logical> {
        let reserved = |e: Exclusivity| match e {
            Exclusivity::Overlay(r) => r,
            _ => 0,
        };

        let (top, right, bottom, left) = (
            reserved(self.exclusive.top),
            reserved(self.exclusive.right),
            reserved(self.exclusive.bottom),
            reserved(self.exclusive.left),
        );

        let mut size = self.size();
        size.w -= left + right;
        size.h -= top + bottom;
        Rectangle::new((left, top).into(), size)
    }

    pub fn scale(&self) -> f64 {
        self.scale
    }

    pub fn frame_interval(&self) -> Duration {
        let refresh = if self.mode.refresh > 0 { self.mode.refresh as u64 } else { 60_000 };
        Duration::from_nanos(NANOS_PER_SECOND / refresh)
    }
}

impl From<&Canvas> for OutputModeSource {
    fn from(canvas: &Canvas) -> Self {
        Self::Static {
            size: canvas.physical_resolution(),
            scale: canvas.scale().into(),
            transform: canvas.orientation().surface_transform(),
        }
    }
}

#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct ExclusiveSpace {
    pub top: Exclusivity,
    pub right: Exclusivity,
    pub bottom: Exclusivity,
    pub left: Exclusivity,
}

impl ExclusiveSpace {
    pub fn update(&mut self, anchor: Anchor, zone: ExclusiveZone, layer: Layer) {
        let ExclusiveZone::Exclusive(reserved) = zone else { return };
        let exclusivity = Exclusivity::from_layer(layer, reserved as i32);

        if anchor == Anchor::TOP || anchor == !Anchor::BOTTOM {
            self.top = exclusivity;
        } else if anchor == Anchor::LEFT || anchor == !Anchor::RIGHT {
            self.left = exclusivity;
        } else if anchor == Anchor::BOTTOM || anchor == !Anchor::TOP {
            self.bottom = exclusivity;
        } else if anchor == Anchor::RIGHT || anchor == !Anchor::LEFT {
            self.right = exclusivity;
        }
    }

    pub fn reset(&mut self, anchor: Anchor, zone: ExclusiveZone) {
        if matches!(zone, ExclusiveZone::Exclusive(_)) {
            self.update(anchor, ExclusiveZone::Exclusive(0), Layer::Bottom);
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Exclusivity {
    Overlay(i32),
    Top(i32),
    Background(i32),
    Bottom(i32),
}

impl Default for Exclusivity {
    fn default() -> Self {
        Self::Background(0)
    }
}

impl Exclusivity {
    fn from_layer(layer: Layer, reserved: i32) -> Self {
        match layer {
            Layer::Bottom => Self::Bottom(reserved),
            Layer::Background => Self::Background(reserved),
            Layer::Top => Self::Top(reserved),
            Layer::Overlay => Self::Overlay(reserved),
        }
    }

    fn value(self) -> i32 {
        match self {
            Self::Bottom(r) | Self::Background(r) | Self::Top(r) | Self::Overlay(r) => r,
        }
    }
}

impl Add<Exclusivity> for i32 {
    type Output = i32;

    fn add(self, e: Exclusivity) -> Self::Output {
        self + e.value()
    }
}

impl Sub<Exclusivity> for i32 {
    type Output = i32;

    fn sub(self, e: Exclusivity) -> Self::Output {
        self - e.value()
    }
}

impl Deref for Exclusivity {
    type Target = i32;

    fn deref(&self) -> &Self::Target {
        match self {
            Self::Bottom(r) | Self::Background(r) | Self::Top(r) | Self::Overlay(r) => r,
        }
    }
}
