use cursor_icon::CursorIcon;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Physical, Point, Rectangle, Size};

use ltk::core::{RenderOptions, UiSurface};
use ltk::{Color, Element, External, Rect};

use crate::drawing::{ForgeElement, RenderTexture, Texture};
use crate::output::Canvas;

// Grab tolerance for edge/corner handles, in logical pixels.
const HANDLE: f64 = 12.0;

#[derive(Clone, Copy)]
enum Edge {
    N,
    S,
    E,
    W,
    NE,
    NW,
    SE,
    SW,
}

#[derive(Clone, Copy)]
enum Grab {
    Draw { anchor: Point<f64, Logical> },
    Move { start_pos: Point<f64, Logical>, start_rect: Rectangle<i32, Logical> },
    Resize { edge: Edge, start_rect: Rectangle<i32, Logical> },
}

/// Fullscreen region-selection overlay. The selection rectangle persists across
/// captures and can be redrawn, moved or resized; ENTER confirms the capture
/// (handled in `input.rs`), ESC cancels.
pub struct ScreenshotSelector {
    visible: bool,
    selection: Option<Rectangle<i32, Logical>>,
    grab: Option<Grab>,
    /// Whether the rectangle was drawn/moved/resized during the current
    /// session. Read at the next `begin` to decide whether to keep the
    /// rectangle or re-default to the focused window, then reset.
    customized: bool,
    fullscreen: bool,
    prev_selection: Option<Rectangle<i32, Logical>>,
    ui: UiSurface<()>,
    ui_size: (u32, u32),
    /// One cached overlay per output, keyed by the output's global origin:
    /// the selection lives in global logical space and each screen draws its
    /// own local view of it.
    overlays: std::collections::HashMap<(i32, i32), OverlaySlot>,
}

struct OverlaySlot {
    size_px: (u32, u32),
    sel: Option<(i32, i32, i32, i32)>,
    texture: Option<RenderTexture>,
}

impl std::fmt::Debug for ScreenshotSelector {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ScreenshotSelector")
            .field("visible", &self.visible)
            .field("selection", &self.selection)
            .finish()
    }
}

impl ScreenshotSelector {
    pub fn new() -> Self {
        Self {
            visible: false,
            selection: None,
            grab: None,
            customized: false,
            fullscreen: false,
            prev_selection: None,
            ui: UiSurface::new(1, 1),
            ui_size: (1, 1),
            overlays: std::collections::HashMap::new(),
        }
    }

    pub fn is_active(&self) -> bool {
        self.visible
    }

    /// Enter capture mode. If the previous session left the rectangle untouched
    /// (or there is none yet) the selection defaults to `default` — the focused
    /// window, re-evaluated on every open; if it was customized last time, that
    /// rectangle is kept.
    pub fn begin(&mut self, default: Option<Rectangle<i32, Logical>>) {
        self.visible = true;
        self.grab = None;
        self.fullscreen = false;
        self.overlays.clear();
        if !self.customized {
            self.selection = default;
        }
        self.customized = false;
    }

    pub fn cancel(&mut self) {
        self.visible = false;
        self.grab = None;
        self.overlays.clear();
    }

    /// Confirm the current selection: returns the region (when non-empty) and
    /// leaves capture mode. The rectangle is kept for the next session.
    pub fn confirm(&mut self) -> Option<Rectangle<i32, Logical>> {
        self.grab = None;
        let sel = self.selection.filter(|r| r.size.w > 0 && r.size.h > 0);
        if sel.is_some() {
            self.visible = false;
            self.overlays.clear();
        }
        sel
    }

    /// Toggle the selection between the full output and the previous rectangle.
    pub fn toggle_fullscreen(&mut self, full: Rectangle<i32, Logical>) {
        if !self.visible {
            return;
        }
        if self.fullscreen {
            self.selection = self.prev_selection.take();
            self.fullscreen = false;
        } else {
            self.prev_selection = self.selection;
            self.selection = Some(full);
            self.fullscreen = true;
        }
    }

    /// Desired pointer shape for the current pointer position over the overlay.
    pub fn cursor_at(&self, pos: Point<f64, Logical>) -> CursorIcon {
        if let Some(grab) = self.grab {
            return match grab {
                Grab::Move { .. } => CursorIcon::Grabbing,
                Grab::Resize { edge, .. } => resize_cursor(edge),
                Grab::Draw { .. } => CursorIcon::Crosshair,
            };
        }
        match self.selection.and_then(|r| hit_test(r, pos)) {
            Some(Hit::Resize(edge)) => resize_cursor(edge),
            Some(Hit::Move) => CursorIcon::Grab,
            None => CursorIcon::Crosshair,
        }
    }

    pub fn on_press(&mut self, pos: Point<f64, Logical>) {
        if !self.visible {
            return;
        }
        self.fullscreen = false;
        self.grab = Some(match self.selection.and_then(|r| hit_test(r, pos)) {
            Some(Hit::Resize(edge)) => Grab::Resize { edge, start_rect: self.selection.unwrap() },
            Some(Hit::Move) => Grab::Move { start_pos: pos, start_rect: self.selection.unwrap() },
            None => Grab::Draw { anchor: pos },
        });
    }

    pub fn on_motion(&mut self, pos: Point<f64, Logical>) {
        if !self.visible {
            return;
        }
        if self.grab.is_some() {
            self.customized = true;
        }
        match self.grab {
            Some(Grab::Draw { anchor }) => {
                if let Some(r) = rect_from_corners(anchor, pos) {
                    self.selection = Some(r);
                }
            }
            Some(Grab::Move { start_pos, start_rect }) => {
                let dx = (pos.x - start_pos.x).round() as i32;
                let dy = (pos.y - start_pos.y).round() as i32;
                let loc = Point::from((start_rect.loc.x + dx, start_rect.loc.y + dy));
                self.selection = Some(Rectangle::new(loc, start_rect.size));
            }
            Some(Grab::Resize { edge, start_rect }) => {
                self.selection = Some(apply_resize(start_rect, edge, pos));
            }
            None => {}
        }
    }

    pub fn on_release(&mut self) {
        self.grab = None;
    }

    pub fn render(
        &mut self,
        textures: &mut Vec<ForgeElement>,
        canvas: &Canvas,
        renderer: &mut GlesRenderer,
        origin: Point<i32, Logical>,
    ) {
        if !self.visible {
            return;
        }
        let scale = canvas.scale();
        let out = canvas.physical_resolution();
        let w_px = out.w.max(1) as u32;
        let h_px = out.h.max(1) as u32;

        let logical = canvas.size();
        let lw = logical.w.max(1) as f32;
        let lh = logical.h.max(1) as f32;

        // The selection is global; this output's overlay draws its local view.
        let sel = self
            .selection
            .filter(|r| r.size.w > 0 && r.size.h > 0)
            .map(|r| (r.loc.x - origin.x, r.loc.y - origin.y, r.size.w, r.size.h));

        let key = (origin.x, origin.y);
        let needs = match self.overlays.get(&key) {
            Some(slot) => slot.size_px != (w_px, h_px) || slot.sel != sel || slot.texture.is_none(),
            None => true,
        };
        if needs {
            let texture = self.build_overlay(renderer, scale, w_px, h_px, lw, lh, sel);
            self.overlays.insert(key, OverlaySlot { size_px: (w_px, h_px), sel, texture });
        }

        if let Some(tex) = self.overlays.get(&key).and_then(|s| s.texture.clone()) {
            let rect = Rectangle::<i32, Physical>::new(Point::from((0, 0)), out);
            ForgeElement::add_element(textures, tex, rect.loc, rect, None, scale);
        }
    }

    fn build_overlay(
        &mut self,
        renderer: &mut GlesRenderer,
        scale: f64,
        w_px: u32,
        h_px: u32,
        lw: f32,
        lh: f32,
        sel: Option<(i32, i32, i32, i32)>,
    ) -> Option<RenderTexture> {
        if (w_px, h_px) != self.ui_size {
            self.ui.resize(w_px, h_px);
            self.ui_size = (w_px, h_px);
        }
        self.ui.set_dpi_scale(scale as f32);

        // Express the selection as fractions of the full output so the draw is
        // independent of the canvas coordinate space (logical vs physical).
        let frac = sel.map(|(x, y, w, h)| {
            (x as f32 / lw, y as f32 / lh, w as f32 / lw, h as f32 / lh)
        });

        let draw_scale = scale as f32;
        let tree: Element<()> = External::cpu(w_px as f32, h_px as f32, move |c, rect| {
            let dim = Color::rgba(0.0, 0.0, 0.0, 0.45);
            let accent = Color::rgba(0.30, 0.68, 0.90, 1.0);
            match frac {
                None => {
                    c.fill_rect(rect, dim, 0.0);
                }
                Some((fx, fy, fw, fh)) => {
                    let sx = rect.x + fx * rect.width;
                    let sy = rect.y + fy * rect.height;
                    let sw = fw * rect.width;
                    let sh = fh * rect.height;
                    let right = rect.x + rect.width;
                    let bottom = rect.y + rect.height;

                    let top_h = sy - rect.y;
                    if top_h > 0.0 {
                        c.fill_rect(Rect { x: rect.x, y: rect.y, width: rect.width, height: top_h }, dim, 0.0);
                    }
                    let bot_h = bottom - (sy + sh);
                    if bot_h > 0.0 {
                        c.fill_rect(Rect { x: rect.x, y: sy + sh, width: rect.width, height: bot_h }, dim, 0.0);
                    }
                    let left_w = sx - rect.x;
                    if left_w > 0.0 {
                        c.fill_rect(Rect { x: rect.x, y: sy, width: left_w, height: sh }, dim, 0.0);
                    }
                    let right_w = right - (sx + sw);
                    if right_w > 0.0 {
                        c.fill_rect(Rect { x: sx + sw, y: sy, width: right_w, height: sh }, dim, 0.0);
                    }

                    // Physical-space canvas: scale the logical stroke and the
                    // handle squares so they match the 12-logical-px HANDLE
                    // grab band at any output scale.
                    c.stroke_rect(
                        Rect { x: sx, y: sy, width: sw, height: sh },
                        accent,
                        2.0 * draw_scale,
                        0.0,
                    );

                    let hs = 6.0 * draw_scale;
                    let pts = [
                        (sx, sy),
                        (sx + sw / 2.0, sy),
                        (sx + sw, sy),
                        (sx, sy + sh / 2.0),
                        (sx + sw, sy + sh / 2.0),
                        (sx, sy + sh),
                        (sx + sw / 2.0, sy + sh),
                        (sx + sw, sy + sh),
                    ];
                    for (cx, cy) in pts {
                        c.fill_rect(Rect { x: cx - hs, y: cy - hs, width: hs * 2.0, height: hs * 2.0 }, accent, 0.0);
                    }
                }
            }
        })
        .into();

        let opts = RenderOptions::full_canvas(w_px, h_px).background(Color::TRANSPARENT);
        let _ = self.ui.render(&tree, opts);

        let mut rgba = vec![0u8; (w_px as usize) * (h_px as usize) * 4];
        if let ltk::core::Canvas::Software(sc) = self.ui.canvas() {
            sc.write_to_wayland_buf(&mut rgba, false);
        }

        let texture = Texture::from_buffer(renderer, scale, &rgba, w_px as i32, h_px as i32, false);
        Some(RenderTexture::new(texture))
    }
}

fn resize_cursor(edge: Edge) -> CursorIcon {
    match edge {
        Edge::N | Edge::S => CursorIcon::NsResize,
        Edge::E | Edge::W => CursorIcon::EwResize,
        Edge::NW | Edge::SE => CursorIcon::NwseResize,
        Edge::NE | Edge::SW => CursorIcon::NeswResize,
    }
}

enum Hit {
    Resize(Edge),
    Move,
}

fn hit_test(rect: Rectangle<i32, Logical>, pos: Point<f64, Logical>) -> Option<Hit> {
    let x0 = rect.loc.x as f64;
    let y0 = rect.loc.y as f64;
    let x1 = (rect.loc.x + rect.size.w) as f64;
    let y1 = (rect.loc.y + rect.size.h) as f64;

    if pos.x < x0 - HANDLE || pos.x > x1 + HANDLE || pos.y < y0 - HANDLE || pos.y > y1 + HANDLE {
        return None;
    }

    let near_l = (pos.x - x0).abs() <= HANDLE;
    let near_r = (pos.x - x1).abs() <= HANDLE;
    let near_t = (pos.y - y0).abs() <= HANDLE;
    let near_b = (pos.y - y1).abs() <= HANDLE;

    let edge = if near_t && near_l {
        Some(Edge::NW)
    } else if near_t && near_r {
        Some(Edge::NE)
    } else if near_b && near_l {
        Some(Edge::SW)
    } else if near_b && near_r {
        Some(Edge::SE)
    } else if near_t {
        Some(Edge::N)
    } else if near_b {
        Some(Edge::S)
    } else if near_l {
        Some(Edge::W)
    } else if near_r {
        Some(Edge::E)
    } else {
        None
    };

    if let Some(e) = edge {
        return Some(Hit::Resize(e));
    }
    if pos.x > x0 && pos.x < x1 && pos.y > y0 && pos.y < y1 {
        return Some(Hit::Move);
    }
    None
}

fn rect_from_corners(a: Point<f64, Logical>, b: Point<f64, Logical>) -> Option<Rectangle<i32, Logical>> {
    let x0 = a.x.min(b.x).round() as i32;
    let y0 = a.y.min(b.y).round() as i32;
    let x1 = a.x.max(b.x).round() as i32;
    let y1 = a.y.max(b.y).round() as i32;
    let (w, h) = (x1 - x0, y1 - y0);
    if w <= 0 || h <= 0 {
        return None;
    }
    Some(Rectangle::new(Point::from((x0, y0)), Size::from((w, h))))
}

fn apply_resize(start: Rectangle<i32, Logical>, edge: Edge, pos: Point<f64, Logical>) -> Rectangle<i32, Logical> {
    let mut x0 = start.loc.x;
    let mut y0 = start.loc.y;
    let mut x1 = start.loc.x + start.size.w;
    let mut y1 = start.loc.y + start.size.h;
    let px = pos.x.round() as i32;
    let py = pos.y.round() as i32;

    match edge {
        Edge::N => y0 = py,
        Edge::S => y1 = py,
        Edge::E => x1 = px,
        Edge::W => x0 = px,
        Edge::NE => {
            y0 = py;
            x1 = px;
        }
        Edge::NW => {
            y0 = py;
            x0 = px;
        }
        Edge::SE => {
            y1 = py;
            x1 = px;
        }
        Edge::SW => {
            y1 = py;
            x0 = px;
        }
    }

    let lx = x0.min(x1);
    let rx = x0.max(x1);
    let ty = y0.min(y1);
    let by = y0.max(y1);
    Rectangle::new(Point::from((lx, ty)), Size::from(((rx - lx).max(1), (by - ty).max(1))))
}
