#[cfg(debug_assertions)]
use std::{thread, time::Duration};

use anyhow::{Context, Result, bail, ensure};
use x11rb::{
    COPY_DEPTH_FROM_PARENT, CURRENT_TIME, NONE, connect,
    connection::Connection,
    protocol::{
        Event,
        shape::{ConnectionExt as ShapeConnectionExt, SK, SO},
        xproto::{
            AtomEnum, ButtonPressEvent, ButtonReleaseEvent, ChangeWindowAttributesAux,
            ClipOrdering, Colormap, ConfigureWindowAux, ConnectionExt, CreateGCAux,
            CreateWindowAux, Cursor, EventMask, ExposeEvent, Gcontext, GrabMode, GrabStatus,
            KeyButMask, KeyPressEvent, LineStyle, MotionNotifyEvent, Point, PropMode, Rectangle,
            StackMode, Window, WindowClass,
        },
    },
    rust_connection::RustConnection,
    wrapper::ConnectionExt as _,
};

use crate::pipeline::{CaptureArea, VideoEffect};

const OVERLAY_OPACITY: u32 = 0xb000_0000;
const INPUT_CAPTURE_OPACITY: u32 = 0x0000_0000;
const HANDLE_SIZE: i32 = 18;
const BORDER_LINE_WIDTH: u32 = 2;
const BORDER_SHAPE_PADDING: i32 = 8;
const LABEL_CHAR_WIDTH: i32 = 8;
const LABEL_TEXT_HEIGHT: i32 = 14;
const LABEL_PADDING: i32 = 12;
const LABEL_BOX_PADDING_X: i32 = 10;
const LABEL_BOX_PADDING_Y: i32 = 6;
const HELP_TITLE: &str = "Frame your recording";
const HELP_TEXT: &str = "Drag to select. Use the handles to refine the frame.";
#[cfg(test)]
const SHORTCUT_TEXT: &str = "Drag inside to move  Handles resize  Enter confirms";
const CONFIRM_TEXT: &str = "RECORD THIS AREA";
const CONFIRM_TEXT_COMPACT: &str = "USE AREA";
const CONFIRM_TEXT_TINY: &str = "USE";
const CANCEL_TEXT: &str = "CANCEL";
const CANCEL_TEXT_TINY: &str = "CANCEL";
const ACTION_BUTTON_HEIGHT: i32 = 42;
const ACTION_BUTTON_PADDING_X: i32 = 18;
const ACTION_BUTTON_GAP: i32 = 10;
const ACTION_BUTTON_SHADOW_OFFSET: i32 = 5;
const ACTION_BUTTON_PANEL_PADDING_X: i32 = 10;
const ACTION_BUTTON_PANEL_PADDING_Y: i32 = 8;
const ACTION_BUTTON_PANEL_HEADER_HEIGHT: i32 = 0;
const ACTION_BUTTON_PANEL_SHADOW_OFFSET: i32 = 5;
const ACTION_RAIL_GAP: i32 = 6;
const CONFIRM_BUTTON_MIN_WIDTH: i32 = 164;
const CANCEL_BUTTON_MIN_WIDTH: i32 = 96;
const SELECTION_GRID_THICKNESS: i32 = 1;
const MOVE_AFFORDANCE_LENGTH: i32 = 22;
const MOVE_AFFORDANCE_GAP: i32 = 5;
const MOVE_AFFORDANCE_THICKNESS: i32 = 2;
const SELECTION_EDGE_METER_LENGTH: i32 = 54;
const SELECTION_EDGE_METER_THICKNESS: i32 = 3;
const SELECTION_CORNER_LENGTH: i32 = 42;
const SELECTION_CORNER_THICKNESS: i32 = 4;
const XC_BOTTOM_LEFT_CORNER: u16 = 12;
const XC_BOTTOM_RIGHT_CORNER: u16 = 14;
const XC_BOTTOM_SIDE: u16 = 16;
const XC_CROSSHAIR: u16 = 34;
const XC_FLEUR: u16 = 52;
const XC_HAND2: u16 = 60;
const XC_LEFT_PTR: u16 = 68;
const XC_LEFT_SIDE: u16 = 70;
const XC_RIGHT_SIDE: u16 = 96;
const XC_TOP_LEFT_CORNER: u16 = 134;
const XC_TOP_RIGHT_CORNER: u16 = 136;
const XC_TOP_SIDE: u16 = 138;
const XK_ESCAPE: u32 = 0xff1b;
const XK_RETURN: u32 = 0xff0d;
const XK_KP_ENTER: u32 = 0xff8d;
const XK_SPACE: u32 = 0x20;
const XK_BACK_SPACE: u32 = 0xff08;
const XK_DELETE: u32 = 0xffff;
const XK_LEFT: u32 = 0xff51;
const XK_UP: u32 = 0xff52;
const XK_RIGHT: u32 = 0xff53;
const XK_DOWN: u32 = 0xff54;
#[cfg(debug_assertions)]
const AUTODRAG_ENV: &str = "OCULA_SELECT_AUTODRAG";

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Selection {
    pub area: CaptureArea,
    pub screen_width: i32,
    pub screen_height: i32,
}

pub fn select_area() -> Result<Selection> {
    select_area_with_effect(VideoEffect::None)
}

pub fn select_area_once() -> Result<Selection> {
    select_area_once_with_effect(VideoEffect::None)
}

pub fn adjust_area(initial: CaptureArea) -> Result<Selection> {
    adjust_area_with_effect(initial, VideoEffect::None)
}

pub fn select_area_with_effect(effect: VideoEffect) -> Result<Selection> {
    select_area_with_mode(SelectorMode::Editable, None, effect)
}

pub fn select_area_once_with_effect(effect: VideoEffect) -> Result<Selection> {
    select_area_with_mode(SelectorMode::ConfirmOnRelease, None, effect)
}

pub fn adjust_area_with_effect(initial: CaptureArea, effect: VideoEffect) -> Result<Selection> {
    select_area_with_mode(SelectorMode::Editable, Some(initial), effect)
}

fn select_area_with_mode(
    mode: SelectorMode,
    initial: Option<CaptureArea>,
    effect: VideoEffect,
) -> Result<Selection> {
    ensure!(
        std::env::var_os("DISPLAY").is_some(),
        "--select requires an X11/XWayland DISPLAY; use --area X,Y,WIDTH,HEIGHT instead"
    );

    eprintln!("{}", mode.instructions());

    let (conn, screen_num) = connect(None).context("failed to connect to X11 display")?;
    let screen = &conn.setup().roots[screen_num];
    let root = screen.root;
    let width = screen.width_in_pixels;
    let height = screen.height_in_pixels;
    let screen_width = i32::from(width);
    let screen_height = i32::from(height);

    let window = conn.generate_id()?;
    conn.create_window(
        COPY_DEPTH_FROM_PARENT,
        window,
        root,
        0,
        0,
        width,
        height,
        0,
        WindowClass::INPUT_OUTPUT,
        0,
        &CreateWindowAux::new()
            .background_pixel(screen.black_pixel)
            .override_redirect(1)
            .event_mask(EventMask::EXPOSURE),
    )?;
    let input_window = conn.generate_id()?;
    let input_capture_uses_surface = input_capture_uses_surface();
    let mut input_window_aux = CreateWindowAux::new()
        .override_redirect(1)
        .event_mask(pointer_event_mask() | EventMask::KEY_PRESS);
    if input_capture_uses_surface {
        input_window_aux = input_window_aux.background_pixel(screen.black_pixel);
    }
    conn.create_window(
        COPY_DEPTH_FROM_PARENT,
        input_window,
        root,
        0,
        0,
        width,
        height,
        0,
        if input_capture_uses_surface {
            WindowClass::INPUT_OUTPUT
        } else {
            WindowClass::INPUT_ONLY
        },
        0,
        &input_window_aux,
    )?;
    let mut guard = SelectorWindow::new(&conn, window, input_window);

    let opacity_atom = conn
        .intern_atom(false, b"_NET_WM_WINDOW_OPACITY")?
        .reply()?
        .atom;
    conn.change_property32(
        PropMode::REPLACE,
        window,
        opacity_atom,
        AtomEnum::CARDINAL,
        &[OVERLAY_OPACITY],
    )?;
    if input_capture_uses_surface {
        conn.change_property32(
            PropMode::REPLACE,
            input_window,
            opacity_atom,
            AtomEnum::CARDINAL,
            &[INPUT_CAPTURE_OPACITY],
        )?;
        conn.change_property8(
            PropMode::REPLACE,
            input_window,
            AtomEnum::WM_NAME,
            AtomEnum::STRING,
            b"Ocula Area Selector Input",
        )?;
    }
    conn.change_property8(
        PropMode::REPLACE,
        window,
        AtomEnum::WM_NAME,
        AtomEnum::STRING,
        b"Ocula Area Selector",
    )?;
    let initial = initial.map(|area| move_area(area, 0, 0, screen_width, screen_height));
    update_overlay_shape(
        &conn,
        window,
        screen_width,
        screen_height,
        initial,
        SelectorDrawMode::Full,
        effect,
    )?;

    let graphics = SelectorGraphics::new(
        &conn,
        window,
        screen.white_pixel,
        screen.black_pixel,
        screen.default_colormap,
        effect,
    )?;
    let cursors = SelectorCursors::new(&conn)?;
    conn.change_window_attributes(
        input_window,
        &ChangeWindowAttributesAux::new().cursor(cursors.id(CursorKind::Crosshair)),
    )?;

    conn.map_window(window)?;
    conn.configure_window(
        window,
        &ConfigureWindowAux::new().stack_mode(StackMode::ABOVE),
    )?;
    conn.map_window(input_window)?;
    conn.configure_window(
        input_window,
        &ConfigureWindowAux::new().stack_mode(StackMode::ABOVE),
    )?;
    conn.flush()?;

    let pointer = conn
        .grab_pointer(
            false,
            input_window,
            pointer_event_mask(),
            GrabMode::ASYNC,
            GrabMode::ASYNC,
            NONE,
            cursors.id(CursorKind::Crosshair),
            CURRENT_TIME,
        )?
        .reply()?;
    ensure!(
        pointer.status == GrabStatus::SUCCESS,
        "failed to grab pointer for area selection"
    );
    guard.pointer_grabbed = true;

    let keyboard = conn
        .grab_keyboard(
            false,
            input_window,
            CURRENT_TIME,
            GrabMode::ASYNC,
            GrabMode::ASYNC,
        )?
        .reply()?;
    ensure!(
        keyboard.status == GrabStatus::SUCCESS,
        "failed to grab keyboard for area selection"
    );
    guard.keyboard_grabbed = true;

    #[cfg(debug_assertions)]
    spawn_autodrag_if_requested(root, input_window);

    let mut selection = initial;
    let mut drag = None;
    let mut active_cursor = CursorKind::Crosshair;

    loop {
        let event = conn.wait_for_event()?;
        match event {
            Event::Expose(event) => redraw_selection(
                &conn,
                window,
                &graphics,
                event,
                screen_width,
                screen_height,
                selection,
                effect,
            )?,
            Event::ButtonPress(event) if is_cancel_button_press(&event) => {
                bail!("area selection cancelled");
            }
            Event::ButtonPress(event) if is_primary_button_press(&event) => {
                let point = button_press_point(&event);
                #[cfg(debug_assertions)]
                if std::env::var_os(AUTODRAG_ENV).is_some() {
                    eprintln!("autodrag: button press at {},{}", point.0, point.1);
                }
                if let Some(area) = selection {
                    match action_button_hit(point, area, screen_width, screen_height) {
                        Some(ActionButton::Confirm) => {
                            return Ok(Selection {
                                area,
                                screen_width,
                                screen_height,
                            });
                        }
                        Some(ActionButton::Cancel) => bail!("area selection cancelled"),
                        None => {}
                    }
                }

                let Some(active_drag) =
                    drag_for_primary_press(point, selection, screen_width, screen_height)
                else {
                    continue;
                };
                drag = Some(active_drag);
                update_active_cursor(
                    &conn,
                    &cursors,
                    &mut active_cursor,
                    cursor_kind_for_drag(active_drag),
                )?;
                redraw(
                    &conn,
                    window,
                    &graphics,
                    screen_width,
                    screen_height,
                    selection,
                    SelectorDrawMode::Dragging,
                    effect,
                )?;
            }
            Event::MotionNotify(event) => {
                let point = motion_point(&conn, input_window, &event);
                #[cfg(debug_assertions)]
                if std::env::var_os(AUTODRAG_ENV).is_some() {
                    eprintln!("autodrag: motion at {},{}", point.0, point.1);
                }
                update_active_cursor(
                    &conn,
                    &cursors,
                    &mut active_cursor,
                    cursor_kind_for_state(point, selection, drag, screen_width, screen_height),
                )?;

                let Some(active_drag) = drag else {
                    continue;
                };

                let next_selection = update_drag(active_drag, point, screen_width, screen_height)?;
                if next_selection == selection {
                    continue;
                }
                selection = next_selection;
                redraw(
                    &conn,
                    window,
                    &graphics,
                    screen_width,
                    screen_height,
                    selection,
                    SelectorDrawMode::Dragging,
                    effect,
                )?;
            }
            Event::ButtonRelease(event) if is_primary_button_release(&event) => {
                if let Some(active_drag) = drag.take() {
                    let point = button_release_point(&event);
                    #[cfg(debug_assertions)]
                    if std::env::var_os(AUTODRAG_ENV).is_some() {
                        eprintln!("autodrag: button release at {},{}", point.0, point.1);
                    }
                    selection = update_drag(active_drag, point, screen_width, screen_height)?;
                    update_active_cursor(
                        &conn,
                        &cursors,
                        &mut active_cursor,
                        cursor_kind_for_state(point, selection, drag, screen_width, screen_height),
                    )?;
                    if let Some(area) = selection_on_release(mode, selection) {
                        return Ok(Selection {
                            area,
                            screen_width,
                            screen_height,
                        });
                    }
                }
                redraw(
                    &conn,
                    window,
                    &graphics,
                    screen_width,
                    screen_height,
                    selection,
                    SelectorDrawMode::Full,
                    effect,
                )?;
            }
            Event::KeyPress(event) => match key_action(&conn, &event)? {
                KeyAction::Confirm if let Some(area) = selection => {
                    return Ok(Selection {
                        area,
                        screen_width,
                        screen_height,
                    });
                }
                KeyAction::Cancel => bail!("area selection cancelled"),
                KeyAction::Reset => {
                    selection = None;
                    drag = None;
                    update_active_cursor(
                        &conn,
                        &cursors,
                        &mut active_cursor,
                        CursorKind::Crosshair,
                    )?;
                    redraw(
                        &conn,
                        window,
                        &graphics,
                        screen_width,
                        screen_height,
                        selection,
                        SelectorDrawMode::Full,
                        effect,
                    )?;
                }
                KeyAction::Move { dx, dy } if let Some(area) = selection => {
                    selection = Some(move_area(area, dx, dy, screen_width, screen_height));
                    redraw(
                        &conn,
                        window,
                        &graphics,
                        screen_width,
                        screen_height,
                        selection,
                        SelectorDrawMode::Full,
                        effect,
                    )?;
                }
                KeyAction::Resize { dw, dh } if let Some(area) = selection => {
                    selection = Some(resize_area_by_delta(
                        area,
                        dw,
                        dh,
                        screen_width,
                        screen_height,
                    )?);
                    redraw(
                        &conn,
                        window,
                        &graphics,
                        screen_width,
                        screen_height,
                        selection,
                        SelectorDrawMode::Full,
                        effect,
                    )?;
                }
                _ => {}
            },
            _ => {}
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SelectorMode {
    Editable,
    ConfirmOnRelease,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SelectorDrawMode {
    Full,
    Dragging,
}

impl SelectorMode {
    fn instructions(self) -> &'static str {
        match self {
            Self::Editable => {
                "adjust the recording frame; drag inside to move; handles resize; Enter confirms; Escape cancels"
            }
            Self::ConfirmOnRelease => {
                "drag to frame the recording; release to use it; Escape cancels"
            }
        }
    }
}

fn selection_on_release(mode: SelectorMode, selection: Option<CaptureArea>) -> Option<CaptureArea> {
    match mode {
        SelectorMode::ConfirmOnRelease => selection,
        SelectorMode::Editable => None,
    }
}

struct SelectorGraphics {
    border_gc: Gcontext,
    edge_gc: Gcontext,
    glow_gc: Gcontext,
    grid_gc: Gcontext,
    meter_gc: Gcontext,
    hot_gc: Gcontext,
    handle_gc: Gcontext,
    handle_border_gc: Gcontext,
    panel_gc: Gcontext,
    panel_shadow_gc: Gcontext,
    panel_border_gc: Gcontext,
    text_gc: Gcontext,
    dim_text_gc: Gcontext,
    confirm_gc: Gcontext,
    confirm_text_gc: Gcontext,
    cancel_gc: Gcontext,
    cancel_text_gc: Gcontext,
    button_shadow_gc: Gcontext,
    button_border_gc: Gcontext,
    shader_primary_gc: Gcontext,
    shader_secondary_gc: Gcontext,
    shader_tertiary_gc: Gcontext,
}

impl SelectorGraphics {
    fn new(
        conn: &RustConnection,
        window: Window,
        foreground: u32,
        background: u32,
        colormap: Colormap,
        effect: VideoEffect,
    ) -> Result<Self> {
        let font = conn.generate_id()?;
        conn.open_font(font, b"fixed")?.check()?;
        let style = selector_effect_style(effect);
        let accent_color = color_pixel(conn, colormap, style.primary, foreground);
        let glow_color = color_pixel(conn, colormap, style.secondary, foreground);
        let edge_color = color_pixel(conn, colormap, b"#f4fdff", foreground);
        let grid_color = color_pixel(conn, colormap, style.tertiary, foreground);
        let meter_color = color_pixel(conn, colormap, style.confirm, foreground);
        let hot_color = color_pixel(conn, colormap, style.hot, foreground);
        let handle_color = color_pixel(conn, colormap, b"#f8ffff", foreground);
        let panel_color = color_pixel(conn, colormap, b"#101720", background);
        let panel_shadow_color = color_pixel(conn, colormap, b"#02060a", background);
        let dim_text_color = color_pixel(conn, colormap, b"#c8d6df", foreground);
        let confirm_color = color_pixel(conn, colormap, b"#5be0b2", foreground);
        let confirm_text_color = color_pixel(conn, colormap, b"#020806", background);
        let cancel_color = color_pixel(conn, colormap, b"#f7fbff", foreground);
        let cancel_text_color = color_pixel(conn, colormap, b"#05080c", background);

        let border_gc = conn.generate_id()?;
        conn.create_gc(
            border_gc,
            window,
            &CreateGCAux::new()
                .foreground(accent_color)
                .line_width(BORDER_LINE_WIDTH)
                .line_style(LineStyle::SOLID),
        )?;

        let edge_gc = conn.generate_id()?;
        conn.create_gc(
            edge_gc,
            window,
            &CreateGCAux::new()
                .foreground(edge_color)
                .line_width(1)
                .line_style(LineStyle::SOLID),
        )?;

        let glow_gc = conn.generate_id()?;
        conn.create_gc(
            glow_gc,
            window,
            &CreateGCAux::new()
                .foreground(glow_color)
                .line_width(5)
                .line_style(LineStyle::SOLID),
        )?;

        let grid_gc = conn.generate_id()?;
        conn.create_gc(
            grid_gc,
            window,
            &CreateGCAux::new()
                .foreground(grid_color)
                .line_width(u32::try_from(SELECTION_GRID_THICKNESS).unwrap_or(1))
                .line_style(LineStyle::SOLID),
        )?;

        let meter_gc = conn.generate_id()?;
        conn.create_gc(
            meter_gc,
            window,
            &CreateGCAux::new()
                .foreground(meter_color)
                .background(meter_color),
        )?;

        let hot_gc = conn.generate_id()?;
        conn.create_gc(
            hot_gc,
            window,
            &CreateGCAux::new()
                .foreground(hot_color)
                .line_width(3)
                .line_style(LineStyle::SOLID),
        )?;

        let handle_gc = conn.generate_id()?;
        conn.create_gc(
            handle_gc,
            window,
            &CreateGCAux::new()
                .foreground(handle_color)
                .background(handle_color),
        )?;

        let handle_border_gc = conn.generate_id()?;
        conn.create_gc(
            handle_border_gc,
            window,
            &CreateGCAux::new()
                .foreground(accent_color)
                .line_width(1)
                .line_style(LineStyle::SOLID),
        )?;

        let panel_gc = conn.generate_id()?;
        conn.create_gc(
            panel_gc,
            window,
            &CreateGCAux::new()
                .foreground(panel_color)
                .background(panel_color),
        )?;

        let panel_shadow_gc = conn.generate_id()?;
        conn.create_gc(
            panel_shadow_gc,
            window,
            &CreateGCAux::new()
                .foreground(panel_shadow_color)
                .background(panel_shadow_color),
        )?;

        let panel_border_gc = conn.generate_id()?;
        conn.create_gc(
            panel_border_gc,
            window,
            &CreateGCAux::new()
                .foreground(accent_color)
                .line_width(1)
                .line_style(LineStyle::SOLID),
        )?;

        let text_gc = conn.generate_id()?;
        conn.create_gc(
            text_gc,
            window,
            &CreateGCAux::new()
                .foreground(edge_color)
                .background(panel_color)
                .font(font),
        )?;

        let dim_text_gc = conn.generate_id()?;
        conn.create_gc(
            dim_text_gc,
            window,
            &CreateGCAux::new()
                .foreground(dim_text_color)
                .background(panel_color)
                .font(font),
        )?;

        let confirm_gc = conn.generate_id()?;
        conn.create_gc(
            confirm_gc,
            window,
            &CreateGCAux::new()
                .foreground(confirm_color)
                .background(confirm_color),
        )?;

        let confirm_text_gc = conn.generate_id()?;
        conn.create_gc(
            confirm_text_gc,
            window,
            &CreateGCAux::new()
                .foreground(confirm_text_color)
                .background(confirm_color)
                .font(font),
        )?;

        let cancel_gc = conn.generate_id()?;
        conn.create_gc(
            cancel_gc,
            window,
            &CreateGCAux::new()
                .foreground(cancel_color)
                .background(cancel_color),
        )?;

        let cancel_text_gc = conn.generate_id()?;
        conn.create_gc(
            cancel_text_gc,
            window,
            &CreateGCAux::new()
                .foreground(cancel_text_color)
                .background(cancel_color)
                .font(font),
        )?;

        let button_shadow_gc = conn.generate_id()?;
        conn.create_gc(
            button_shadow_gc,
            window,
            &CreateGCAux::new()
                .foreground(panel_shadow_color)
                .background(panel_shadow_color),
        )?;

        let button_border_gc = conn.generate_id()?;
        conn.create_gc(
            button_border_gc,
            window,
            &CreateGCAux::new()
                .foreground(edge_color)
                .line_width(1)
                .line_style(LineStyle::SOLID),
        )?;

        let shader_primary_gc = conn.generate_id()?;
        conn.create_gc(
            shader_primary_gc,
            window,
            &CreateGCAux::new()
                .foreground(accent_color)
                .line_width(2)
                .line_style(LineStyle::SOLID),
        )?;

        let shader_secondary_gc = conn.generate_id()?;
        conn.create_gc(
            shader_secondary_gc,
            window,
            &CreateGCAux::new()
                .foreground(glow_color)
                .line_width(1)
                .line_style(LineStyle::SOLID),
        )?;

        let shader_tertiary_gc = conn.generate_id()?;
        conn.create_gc(
            shader_tertiary_gc,
            window,
            &CreateGCAux::new()
                .foreground(grid_color)
                .background(grid_color)
                .line_width(1)
                .line_style(LineStyle::SOLID),
        )?;
        conn.close_font(font)?;

        Ok(Self {
            border_gc,
            edge_gc,
            glow_gc,
            grid_gc,
            meter_gc,
            hot_gc,
            handle_gc,
            handle_border_gc,
            panel_gc,
            panel_shadow_gc,
            panel_border_gc,
            text_gc,
            dim_text_gc,
            confirm_gc,
            confirm_text_gc,
            cancel_gc,
            cancel_text_gc,
            button_shadow_gc,
            button_border_gc,
            shader_primary_gc,
            shader_secondary_gc,
            shader_tertiary_gc,
        })
    }
}

#[derive(Debug, Clone, Copy)]
struct SelectorEffectStyle {
    primary: &'static [u8],
    secondary: &'static [u8],
    tertiary: &'static [u8],
    hot: &'static [u8],
    confirm: &'static [u8],
}

fn selector_effect_style(effect: VideoEffect) -> SelectorEffectStyle {
    match effect {
        VideoEffect::Neon => SelectorEffectStyle {
            primary: b"#00f0ff",
            secondary: b"#ff28d8",
            tertiary: b"#00ffa6",
            hot: b"#ff4fd8",
            confirm: b"#00e0a4",
        },
        VideoEffect::Heatmap => SelectorEffectStyle {
            primary: b"#ffc400",
            secondary: b"#ff3b00",
            tertiary: b"#2e7fff",
            hot: b"#ffffff",
            confirm: b"#ffb000",
        },
        VideoEffect::Glitch => SelectorEffectStyle {
            primary: b"#00ffc8",
            secondary: b"#ff2464",
            tertiary: b"#bdfffb",
            hot: b"#ff2464",
            confirm: b"#00ffc8",
        },
        VideoEffect::Glass | VideoEffect::Fracture => SelectorEffectStyle {
            primary: b"#d8fbff",
            secondary: b"#7aa7ff",
            tertiary: b"#ffffff",
            hot: b"#ff4f8f",
            confirm: b"#84f7ff",
        },
        VideoEffect::Prism | VideoEffect::Kaleido => SelectorEffectStyle {
            primary: b"#ff3048",
            secondary: b"#00d7ff",
            tertiary: b"#ffe100",
            hot: b"#ff28d8",
            confirm: b"#00d7ff",
        },
        VideoEffect::Aurora => SelectorEffectStyle {
            primary: b"#11f08a",
            secondary: b"#24aaff",
            tertiary: b"#b535ff",
            hot: b"#b535ff",
            confirm: b"#11f08a",
        },
        VideoEffect::Ripple => SelectorEffectStyle {
            primary: b"#32b8ff",
            secondary: b"#20ffd4",
            tertiary: b"#b8f3ff",
            hot: b"#20ffd4",
            confirm: b"#20ffd4",
        },
        VideoEffect::Crt => SelectorEffectStyle {
            primary: b"#38ff62",
            secondary: b"#008a3a",
            tertiary: b"#c2ffd0",
            hot: b"#38ff62",
            confirm: b"#38ff62",
        },
        VideoEffect::Holo => SelectorEffectStyle {
            primary: b"#40efff",
            secondary: b"#ff40ee",
            tertiary: b"#e2ffff",
            hot: b"#ff40ee",
            confirm: b"#40efff",
        },
        VideoEffect::Vapor => SelectorEffectStyle {
            primary: b"#44dfff",
            secondary: b"#d82bff",
            tertiary: b"#ff9b2c",
            hot: b"#d82bff",
            confirm: b"#44dfff",
        },
        VideoEffect::None => SelectorEffectStyle {
            primary: b"#55d7f2",
            secondary: b"#1c8ca3",
            tertiary: b"#9db2c3",
            hot: b"#f4fbff",
            confirm: b"#5be0b2",
        },
    }
}

fn color_pixel(conn: &RustConnection, colormap: Colormap, name: &[u8], fallback: u32) -> u32 {
    conn.alloc_named_color(colormap, name)
        .ok()
        .and_then(|cookie| cookie.reply().ok())
        .map_or(fallback, |reply| reply.pixel)
}

struct SelectorCursors {
    default_: Cursor,
    crosshair: Cursor,
    hand: Cursor,
    move_: Cursor,
    top_left: Cursor,
    top_right: Cursor,
    bottom_right: Cursor,
    bottom_left: Cursor,
    top: Cursor,
    right: Cursor,
    bottom: Cursor,
    left: Cursor,
}

impl SelectorCursors {
    fn new(conn: &RustConnection) -> Result<Self> {
        let font = conn.generate_id()?;
        conn.open_font(font, b"cursor")?.check()?;

        let cursors = Self {
            default_: create_cursor(conn, font, XC_LEFT_PTR)?,
            crosshair: create_cursor(conn, font, XC_CROSSHAIR)?,
            hand: create_cursor(conn, font, XC_HAND2)?,
            move_: create_cursor(conn, font, XC_FLEUR)?,
            top_left: create_cursor(conn, font, XC_TOP_LEFT_CORNER)?,
            top_right: create_cursor(conn, font, XC_TOP_RIGHT_CORNER)?,
            bottom_right: create_cursor(conn, font, XC_BOTTOM_RIGHT_CORNER)?,
            bottom_left: create_cursor(conn, font, XC_BOTTOM_LEFT_CORNER)?,
            top: create_cursor(conn, font, XC_TOP_SIDE)?,
            right: create_cursor(conn, font, XC_RIGHT_SIDE)?,
            bottom: create_cursor(conn, font, XC_BOTTOM_SIDE)?,
            left: create_cursor(conn, font, XC_LEFT_SIDE)?,
        };

        conn.close_font(font)?.check()?;
        Ok(cursors)
    }

    fn id(&self, kind: CursorKind) -> Cursor {
        match kind {
            CursorKind::Default => self.default_,
            CursorKind::Crosshair => self.crosshair,
            CursorKind::Pointer => self.hand,
            CursorKind::Move => self.move_,
            CursorKind::Resize(Handle::TopLeft) => self.top_left,
            CursorKind::Resize(Handle::TopRight) => self.top_right,
            CursorKind::Resize(Handle::BottomRight) => self.bottom_right,
            CursorKind::Resize(Handle::BottomLeft) => self.bottom_left,
            CursorKind::Resize(Handle::Top) => self.top,
            CursorKind::Resize(Handle::Right) => self.right,
            CursorKind::Resize(Handle::Bottom) => self.bottom,
            CursorKind::Resize(Handle::Left) => self.left,
        }
    }
}

fn create_cursor(conn: &RustConnection, font: u32, shape: u16) -> Result<Cursor> {
    let cursor = conn.generate_id()?;
    conn.create_glyph_cursor(
        cursor,
        font,
        font,
        shape,
        shape + 1,
        u16::MAX,
        u16::MAX,
        u16::MAX,
        0,
        0,
        0,
    )?;
    Ok(cursor)
}

struct SelectorWindow<'a> {
    conn: &'a RustConnection,
    window: Window,
    input_window: Window,
    pointer_grabbed: bool,
    keyboard_grabbed: bool,
}

impl<'a> SelectorWindow<'a> {
    fn new(conn: &'a RustConnection, window: Window, input_window: Window) -> Self {
        Self {
            conn,
            window,
            input_window,
            pointer_grabbed: false,
            keyboard_grabbed: false,
        }
    }
}

impl Drop for SelectorWindow<'_> {
    fn drop(&mut self) {
        if self.pointer_grabbed {
            let _ = self.conn.ungrab_pointer(CURRENT_TIME);
        }
        if self.keyboard_grabbed {
            let _ = self.conn.ungrab_keyboard(CURRENT_TIME);
        }
        let _ = self.conn.destroy_window(self.input_window);
        let _ = self.conn.destroy_window(self.window);
        let _ = self.conn.flush();
    }
}

fn is_primary_button_press(event: &ButtonPressEvent) -> bool {
    is_primary_button_detail(event.detail)
}

fn is_cancel_button_press(event: &ButtonPressEvent) -> bool {
    is_cancel_button_detail(event.detail)
}

fn is_primary_button_detail(detail: u8) -> bool {
    detail == 1
}

fn is_cancel_button_detail(detail: u8) -> bool {
    detail == 3
}

fn is_primary_button_release(event: &ButtonReleaseEvent) -> bool {
    event.detail == 1
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Drag {
    New {
        start: (i16, i16),
    },
    Move {
        start: (i16, i16),
        original: CaptureArea,
    },
    Resize {
        handle: Handle,
        original: CaptureArea,
    },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Handle {
    TopLeft,
    TopRight,
    BottomRight,
    BottomLeft,
    Top,
    Right,
    Bottom,
    Left,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Hit {
    Handle(Handle),
    Inside,
    Outside,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ActionButton {
    Confirm,
    Cancel,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CursorKind {
    Default,
    Crosshair,
    Pointer,
    Move,
    Resize(Handle),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum KeyAction {
    Confirm,
    Cancel,
    Reset,
    Move { dx: i32, dy: i32 },
    Resize { dw: i32, dh: i32 },
    Other,
}

fn start_drag(point: (i16, i16), selection: Option<CaptureArea>) -> Drag {
    let Some(area) = selection else {
        return Drag::New { start: point };
    };

    match hit_test(point, area) {
        Hit::Handle(handle) => Drag::Resize {
            handle,
            original: area,
        },
        Hit::Inside => Drag::Move {
            start: point,
            original: area,
        },
        Hit::Outside => Drag::New { start: point },
    }
}

fn drag_for_primary_press(
    point: (i16, i16),
    selection: Option<CaptureArea>,
    screen_width: i32,
    screen_height: i32,
) -> Option<Drag> {
    if let Some(area) = selection
        && action_control_surface_hit(point, area, screen_width, screen_height)
    {
        return None;
    }

    Some(start_drag(point, selection))
}

fn cursor_kind_for_state(
    point: (i16, i16),
    selection: Option<CaptureArea>,
    drag: Option<Drag>,
    screen_width: i32,
    screen_height: i32,
) -> CursorKind {
    if let Some(drag) = drag {
        return cursor_kind_for_drag(drag);
    }

    cursor_kind_for_point(point, selection, screen_width, screen_height)
}

fn cursor_kind_for_drag(drag: Drag) -> CursorKind {
    match drag {
        Drag::New { .. } => CursorKind::Crosshair,
        Drag::Move { .. } => CursorKind::Move,
        Drag::Resize { handle, .. } => CursorKind::Resize(handle),
    }
}

fn cursor_kind_for_point(
    point: (i16, i16),
    selection: Option<CaptureArea>,
    screen_width: i32,
    screen_height: i32,
) -> CursorKind {
    let Some(area) = selection else {
        return CursorKind::Crosshair;
    };

    if action_button_hit(point, area, screen_width, screen_height).is_some() {
        return CursorKind::Pointer;
    }
    if action_control_surface_hit(point, area, screen_width, screen_height) {
        return CursorKind::Default;
    }

    match hit_test(point, area) {
        Hit::Handle(handle) => CursorKind::Resize(handle),
        Hit::Inside => CursorKind::Move,
        Hit::Outside => CursorKind::Crosshair,
    }
}

fn update_active_cursor(
    conn: &RustConnection,
    cursors: &SelectorCursors,
    active: &mut CursorKind,
    next: CursorKind,
) -> Result<()> {
    if *active == next {
        return Ok(());
    }

    conn.change_active_pointer_grab(cursors.id(next), CURRENT_TIME, pointer_event_mask())?;
    *active = next;
    Ok(())
}

fn pointer_event_mask() -> EventMask {
    EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE | EventMask::POINTER_MOTION
}

fn input_capture_uses_surface() -> bool {
    // XWayland does not reliably expose INPUT_ONLY windows as compositor input
    // targets. Use a transparent InputOutput surface so the active selector,
    // not the desktop underneath, receives drag events.
    std::env::var_os("WAYLAND_DISPLAY").is_some()
        || std::env::var_os("XDG_SESSION_TYPE")
            .is_some_and(|session| session.to_string_lossy().eq_ignore_ascii_case("wayland"))
}

fn button_press_point(event: &ButtonPressEvent) -> (i16, i16) {
    (event.root_x, event.root_y)
}

fn button_release_point(event: &ButtonReleaseEvent) -> (i16, i16) {
    (event.root_x, event.root_y)
}

fn motion_point(_conn: &RustConnection, _window: Window, event: &MotionNotifyEvent) -> (i16, i16) {
    (event.root_x, event.root_y)
}

fn update_drag(
    drag: Drag,
    point: (i16, i16),
    screen_width: i32,
    screen_height: i32,
) -> Result<Option<CaptureArea>> {
    match drag {
        Drag::New { start } => area_from_screen_points(start, point, screen_width, screen_height),
        Drag::Move { start, original } => Ok(Some(move_area(
            original,
            i32::from(point.0) - i32::from(start.0),
            i32::from(point.1) - i32::from(start.1),
            screen_width,
            screen_height,
        ))),
        Drag::Resize { handle, original } => {
            resize_area(original, handle, point, screen_width, screen_height).map(Some)
        }
    }
}

fn hit_test(point: (i16, i16), area: CaptureArea) -> Hit {
    for (handle, rectangle) in handle_rectangles_with_handles(area) {
        if rectangle_contains(rectangle, point) {
            return Hit::Handle(handle);
        }
    }

    if point_in_area(point, area) {
        Hit::Inside
    } else {
        Hit::Outside
    }
}

fn point_in_area(point: (i16, i16), area: CaptureArea) -> bool {
    let x = i32::from(point.0);
    let y = i32::from(point.1);

    x >= area.x && x < area.right() && y >= area.y && y < area.bottom()
}

fn rectangle_contains(rectangle: Rectangle, point: (i16, i16)) -> bool {
    let x = i32::from(point.0);
    let y = i32::from(point.1);
    let left = i32::from(rectangle.x);
    let top = i32::from(rectangle.y);
    let right = left + i32::from(rectangle.width);
    let bottom = top + i32::from(rectangle.height);

    x >= left && x < right && y >= top && y < bottom
}

fn move_area(
    area: CaptureArea,
    dx: i32,
    dy: i32,
    screen_width: i32,
    screen_height: i32,
) -> CaptureArea {
    let max_x = (screen_width - area.width).max(0);
    let max_y = (screen_height - area.height).max(0);

    CaptureArea {
        x: area.x.saturating_add(dx).clamp(0, max_x),
        y: area.y.saturating_add(dy).clamp(0, max_y),
        width: area.width.min(screen_width.max(1)),
        height: area.height.min(screen_height.max(1)),
    }
}

fn resize_area_by_delta(
    area: CaptureArea,
    dw: i32,
    dh: i32,
    screen_width: i32,
    screen_height: i32,
) -> Result<CaptureArea> {
    let max_width = (screen_width - area.x).max(1);
    let max_height = (screen_height - area.y).max(1);
    let width = area.width.saturating_add(dw).clamp(1, max_width);
    let height = area.height.saturating_add(dh).clamp(1, max_height);

    CaptureArea::new(area.x, area.y, width, height)
}

fn resize_area(
    area: CaptureArea,
    handle: Handle,
    point: (i16, i16),
    screen_width: i32,
    screen_height: i32,
) -> Result<CaptureArea> {
    let x = i32::from(point.0).clamp(0, screen_width.max(0));
    let y = i32::from(point.1).clamp(0, screen_height.max(0));
    let (start, end) = match handle {
        Handle::TopLeft => ((area.right(), area.bottom()), (x, y)),
        Handle::TopRight => ((area.x, area.bottom()), (x, y)),
        Handle::BottomRight => ((area.x, area.y), (x, y)),
        Handle::BottomLeft => ((area.right(), area.y), (x, y)),
        Handle::Top => ((area.x, area.bottom()), (area.right(), y)),
        Handle::Right => ((area.x, area.y), (x, area.bottom())),
        Handle::Bottom => ((area.x, area.y), (area.right(), y)),
        Handle::Left => ((area.right(), area.y), (x, area.bottom())),
    };

    area_from_resize_points(start, end, handle, screen_width, screen_height)
}

fn area_from_resize_points(
    fixed: (i32, i32),
    moving: (i32, i32),
    handle: Handle,
    screen_width: i32,
    screen_height: i32,
) -> Result<CaptureArea> {
    let (x, width) = resize_interval(
        fixed.0,
        moving.0,
        screen_width,
        matches!(handle, Handle::TopLeft | Handle::BottomLeft | Handle::Left),
    );
    let (y, height) = resize_interval(
        fixed.1,
        moving.1,
        screen_height,
        matches!(handle, Handle::TopLeft | Handle::TopRight | Handle::Top),
    );

    CaptureArea::new(x, y, width, height)
}

fn resize_interval(
    fixed: i32,
    moving: i32,
    screen_limit: i32,
    fixed_is_max_when_equal: bool,
) -> (i32, i32) {
    if moving < fixed {
        return (moving, fixed.saturating_sub(moving));
    }
    if moving > fixed {
        return (fixed, moving.saturating_sub(fixed));
    }

    let limit = screen_limit.max(1);
    let (start, end) = if fixed_is_max_when_equal {
        (fixed.saturating_sub(1).max(0), fixed)
    } else {
        (fixed, fixed.saturating_add(1).min(limit))
    };

    if end > start {
        (start, end.saturating_sub(start))
    } else if fixed > 0 {
        (fixed.saturating_sub(1), 1)
    } else {
        (0, 1)
    }
}

fn key_action(conn: &RustConnection, event: &KeyPressEvent) -> Result<KeyAction> {
    let keymap = conn.get_keyboard_mapping(event.detail, 1)?.reply()?;
    Ok(key_action_from_keysyms(&keymap.keysyms, event.state))
}

fn key_action_from_keysyms(keysyms: &[u32], state: KeyButMask) -> KeyAction {
    if keysyms.contains(&XK_ESCAPE) {
        return KeyAction::Cancel;
    }

    if keysyms.contains(&XK_BACK_SPACE) || keysyms.contains(&XK_DELETE) {
        return KeyAction::Reset;
    }

    if keysyms
        .iter()
        .any(|keysym| matches!(*keysym, XK_RETURN | XK_KP_ENTER | XK_SPACE))
    {
        return KeyAction::Confirm;
    }

    let step = if has_modifier(state, KeyButMask::CONTROL) {
        10
    } else {
        1
    };

    for keysym in keysyms {
        let (dx, dy) = match *keysym {
            XK_LEFT => (-step, 0),
            XK_RIGHT => (step, 0),
            XK_UP => (0, -step),
            XK_DOWN => (0, step),
            _ => continue,
        };

        return if has_modifier(state, KeyButMask::SHIFT) {
            KeyAction::Resize { dw: dx, dh: dy }
        } else {
            KeyAction::Move { dx, dy }
        };
    }

    KeyAction::Other
}

fn has_modifier(state: KeyButMask, mask: KeyButMask) -> bool {
    u16::from(state) & u16::from(mask) != 0
}

#[allow(clippy::too_many_arguments)]
fn redraw_selection(
    conn: &RustConnection,
    window: Window,
    graphics: &SelectorGraphics,
    _event: ExposeEvent,
    screen_width: i32,
    screen_height: i32,
    selection: Option<CaptureArea>,
    effect: VideoEffect,
) -> Result<()> {
    redraw(
        conn,
        window,
        graphics,
        screen_width,
        screen_height,
        selection,
        SelectorDrawMode::Full,
        effect,
    )
}

#[allow(clippy::too_many_arguments)]
fn redraw(
    conn: &RustConnection,
    window: Window,
    graphics: &SelectorGraphics,
    screen_width: i32,
    screen_height: i32,
    selection: Option<CaptureArea>,
    draw_mode: SelectorDrawMode,
    effect: VideoEffect,
) -> Result<()> {
    update_overlay_shape(
        conn,
        window,
        screen_width,
        screen_height,
        selection,
        draw_mode,
        effect,
    )?;
    conn.clear_area(false, window, 0, 0, 0, 0)?;

    if let Some(area) = selection {
        draw_selection_frame(conn, window, graphics, area)?;
        draw_selection_info(conn, window, graphics, area, screen_width, screen_height)?;
        if draw_mode == SelectorDrawMode::Full {
            draw_shader_effect_overlay(conn, window, graphics, area, effect)?;
            draw_selection_grid(conn, window, graphics, area)?;
            draw_move_affordance(conn, window, graphics, area)?;
            draw_handles(conn, window, graphics, area)?;
            draw_action_buttons(conn, window, graphics, area, screen_width, screen_height)?;
        }
    } else {
        draw_help_label(conn, window, graphics, screen_width, screen_height)?;
    }

    conn.flush()?;
    Ok(())
}

fn draw_selection_frame(
    conn: &RustConnection,
    window: Window,
    graphics: &SelectorGraphics,
    area: CaptureArea,
) -> Result<()> {
    if let Some(rectangle) = rectangle_from_area(area) {
        conn.poly_rectangle(window, graphics.glow_gc, &[rectangle])?;
        conn.poly_rectangle(window, graphics.hot_gc, &[rectangle])?;
        conn.poly_rectangle(window, graphics.border_gc, &[rectangle])?;
        conn.poly_rectangle(window, graphics.edge_gc, &[rectangle])?;
    }

    let corners = selection_corner_rectangles(area);
    conn.poly_fill_rectangle(window, graphics.border_gc, &corners)?;
    let meters = selection_edge_meter_rectangles(area);
    if !meters.is_empty() {
        conn.poly_fill_rectangle(window, graphics.meter_gc, &meters)?;
    }
    Ok(())
}

fn draw_selection_info(
    conn: &RustConnection,
    window: Window,
    graphics: &SelectorGraphics,
    area: CaptureArea,
    screen_width: i32,
    screen_height: i32,
) -> Result<()> {
    let Some(panel) = selection_info_panel(area, screen_width, screen_height) else {
        return Ok(());
    };

    conn.poly_fill_rectangle(window, graphics.panel_shadow_gc, &[panel.shadow])?;
    conn.poly_fill_rectangle(window, graphics.panel_gc, &[panel.body])?;
    conn.poly_rectangle(window, graphics.panel_border_gc, &[panel.body])?;
    conn.image_text8(
        window,
        graphics.text_gc,
        panel.text_x,
        panel.text_y,
        panel.text.as_bytes(),
    )?;
    Ok(())
}

struct SelectionInfoPanel {
    body: Rectangle,
    shadow: Rectangle,
    text_x: i16,
    text_y: i16,
    text: String,
}

fn selection_info_panel(
    area: CaptureArea,
    screen_width: i32,
    screen_height: i32,
) -> Option<SelectionInfoPanel> {
    let text = format!(
        "{} x {}  |  x {}, y {}",
        area.width, area.height, area.x, area.y
    );
    let wanted_width = label_width(text.len()).saturating_add(LABEL_BOX_PADDING_X * 2);
    let wanted_height = LABEL_TEXT_HEIGHT.saturating_add(LABEL_BOX_PADDING_Y * 2);
    let width = wanted_width.min(screen_width.max(1));
    let height = wanted_height.min(screen_height.max(1));
    let max_x = screen_width.saturating_sub(width).max(0);
    let max_y = screen_height.saturating_sub(height).max(0);
    let x = area.x.saturating_add(LABEL_PADDING).clamp(0, max_x);
    let above = area.y.saturating_sub(height).saturating_sub(8);
    let below = area.bottom().saturating_add(8);
    let y = if above >= 0 {
        above
    } else if below.saturating_add(height) <= screen_height {
        below
    } else {
        area.y.saturating_add(8).clamp(0, max_y)
    };
    let body = rect_i32(x, y, width, height)?;
    let shadow = offset_rectangle(body, 0, 4).unwrap_or(body);
    let text_x = clamp_i16(x.saturating_add(LABEL_BOX_PADDING_X));
    let text_y = clamp_i16(
        y.saturating_add(LABEL_BOX_PADDING_Y)
            .saturating_add(LABEL_TEXT_HEIGHT)
            .saturating_sub(3),
    );

    Some(SelectionInfoPanel {
        body,
        shadow,
        text_x,
        text_y,
        text,
    })
}

fn draw_selection_grid(
    conn: &RustConnection,
    window: Window,
    graphics: &SelectorGraphics,
    area: CaptureArea,
) -> Result<()> {
    let grid = selection_grid_rectangles(area);
    if !grid.is_empty() {
        conn.poly_fill_rectangle(window, graphics.grid_gc, &grid)?;
    }
    Ok(())
}

fn draw_move_affordance(
    conn: &RustConnection,
    window: Window,
    graphics: &SelectorGraphics,
    area: CaptureArea,
) -> Result<()> {
    let affordance = move_affordance_rectangles(area);
    if !affordance.is_empty() {
        conn.poly_fill_rectangle(window, graphics.shader_primary_gc, &affordance)?;
    }
    Ok(())
}

fn draw_shader_effect_overlay(
    conn: &RustConnection,
    window: Window,
    graphics: &SelectorGraphics,
    area: CaptureArea,
    effect: VideoEffect,
) -> Result<()> {
    match effect {
        VideoEffect::Neon | VideoEffect::Glitch | VideoEffect::Holo => {
            draw_shader_circuit_lines(conn, window, graphics, area)?;
        }
        VideoEffect::Ripple | VideoEffect::Aurora | VideoEffect::Vapor => {
            draw_shader_flow_lines(conn, window, graphics, area)?;
        }
        VideoEffect::Glass | VideoEffect::Fracture | VideoEffect::Prism | VideoEffect::Kaleido => {
            draw_shader_facets(conn, window, graphics, area)?;
        }
        VideoEffect::Heatmap | VideoEffect::Crt => {
            draw_shader_scan_lines(conn, window, graphics, area)?;
        }
        VideoEffect::None => {}
    }

    Ok(())
}

fn draw_shader_circuit_lines(
    conn: &RustConnection,
    window: Window,
    graphics: &SelectorGraphics,
    area: CaptureArea,
) -> Result<()> {
    let line_count = (area.height / 36).clamp(3, 8);
    for i in 0..line_count {
        let y = area.y + ((i + 1) * area.height) / (line_count + 1);
        let jog = ((i * 17 + area.x + area.y) % area.width.max(1)).min(area.width);
        let points = [
            point(area.x + 10, y),
            point(area.x + jog / 2, y),
            point(area.x + jog / 2 + 12, y - 8),
            point(area.right() - 10, y - 8),
        ];
        conn.poly_line(
            x11rb::protocol::xproto::CoordMode::ORIGIN,
            window,
            if i % 2 == 0 {
                graphics.shader_primary_gc
            } else {
                graphics.shader_secondary_gc
            },
            &points,
        )?;
    }
    Ok(())
}

fn draw_shader_flow_lines(
    conn: &RustConnection,
    window: Window,
    graphics: &SelectorGraphics,
    area: CaptureArea,
) -> Result<()> {
    let line_count = (area.height / 34).clamp(3, 7);
    for i in 0..line_count {
        let y = area.y + ((i + 1) * area.height) / (line_count + 1);
        let mut points = Vec::new();
        let steps = 8;
        for step in 0..=steps {
            let x = area.x + (step * area.width) / steps;
            let wave = ((step * 11 + i * 9 + area.width) % 17) - 8;
            points.push(point(x, y + wave));
        }
        conn.poly_line(
            x11rb::protocol::xproto::CoordMode::ORIGIN,
            window,
            graphics.shader_secondary_gc,
            &points,
        )?;
    }
    Ok(())
}

fn draw_shader_facets(
    conn: &RustConnection,
    window: Window,
    graphics: &SelectorGraphics,
    area: CaptureArea,
) -> Result<()> {
    let columns = (area.width / 72).clamp(3, 9);
    for i in 0..columns {
        let x = area.x + ((i + 1) * area.width) / (columns + 1);
        let tilt = ((i * 19 + area.height) % 29) - 14;
        let points = [
            point(x, area.y + 8),
            point(x + tilt, area.y + area.height / 2),
            point(x - tilt / 2, area.bottom() - 8),
        ];
        conn.poly_line(
            x11rb::protocol::xproto::CoordMode::ORIGIN,
            window,
            if i % 2 == 0 {
                graphics.shader_primary_gc
            } else {
                graphics.shader_tertiary_gc
            },
            &points,
        )?;
    }
    Ok(())
}

fn draw_shader_scan_lines(
    conn: &RustConnection,
    window: Window,
    graphics: &SelectorGraphics,
    area: CaptureArea,
) -> Result<()> {
    let lines = shader_scan_rectangles(area);
    if !lines.is_empty() {
        conn.poly_fill_rectangle(window, graphics.shader_tertiary_gc, &lines)?;
    }
    Ok(())
}

fn draw_handles(
    conn: &RustConnection,
    window: Window,
    graphics: &SelectorGraphics,
    area: CaptureArea,
) -> Result<()> {
    for (_, rectangle) in handle_rectangles_with_handles(area) {
        let shadow = offset_rectangle(rectangle, 2, 2).unwrap_or(rectangle);
        conn.poly_fill_rectangle(window, graphics.panel_shadow_gc, &[shadow])?;
        conn.poly_fill_rectangle(window, graphics.handle_gc, &[rectangle])?;
        conn.poly_rectangle(window, graphics.handle_border_gc, &[rectangle])?;
    }
    Ok(())
}

fn draw_action_buttons(
    conn: &RustConnection,
    window: Window,
    graphics: &SelectorGraphics,
    area: CaptureArea,
    screen_width: i32,
    screen_height: i32,
) -> Result<()> {
    if let Some(panel) = action_button_panel_rectangle(area, screen_width, screen_height) {
        if let Some(shadow) = offset_rectangle(panel, 0, ACTION_BUTTON_PANEL_SHADOW_OFFSET) {
            conn.poly_fill_rectangle(window, graphics.button_shadow_gc, &[shadow])?;
        }
        conn.poly_fill_rectangle(window, graphics.panel_gc, &[panel])?;
        conn.poly_rectangle(window, graphics.button_border_gc, &[panel])?;
        if let Some(highlight) = rect_i32(
            i32::from(panel.x).saturating_add(2),
            i32::from(panel.y).saturating_add(2),
            i32::from(panel.width).saturating_sub(4),
            2,
        ) {
            conn.poly_fill_rectangle(window, graphics.edge_gc, &[highlight])?;
        }
    }

    for (button, rectangle) in action_button_rectangles(area, screen_width, screen_height) {
        let (fill_gc, text_gc) = match button {
            ActionButton::Confirm => (graphics.confirm_gc, graphics.confirm_text_gc),
            ActionButton::Cancel => (graphics.cancel_gc, graphics.cancel_text_gc),
        };
        let label = action_button_label(button, i32::from(rectangle.width));
        if let Some(shadow) = offset_rectangle(
            rectangle,
            0,
            ACTION_BUTTON_SHADOW_OFFSET.min(i32::from(u16::MAX)),
        ) {
            conn.poly_fill_rectangle(window, graphics.button_shadow_gc, &[shadow])?;
        }
        conn.poly_fill_rectangle(window, fill_gc, &[rectangle])?;
        conn.poly_rectangle(window, graphics.button_border_gc, &[rectangle])?;
        let text_width = label_width(label.len());
        let x = i32::from(rectangle.x)
            .saturating_add((i32::from(rectangle.width).saturating_sub(text_width) / 2).max(0));
        let y = i32::from(rectangle.y)
            .saturating_add((ACTION_BUTTON_HEIGHT + LABEL_TEXT_HEIGHT) / 2)
            .saturating_sub(3);
        conn.image_text8(
            window,
            text_gc,
            clamp_i16(x),
            clamp_i16(y),
            label.as_bytes(),
        )?;
    }

    Ok(())
}

fn draw_help_label(
    conn: &RustConnection,
    window: Window,
    graphics: &SelectorGraphics,
    screen_width: i32,
    screen_height: i32,
) -> Result<()> {
    let panel = help_panel_rectangle(screen_width, screen_height);
    conn.poly_fill_rectangle(window, graphics.panel_shadow_gc, &[panel.shadow])?;
    conn.poly_fill_rectangle(window, graphics.panel_gc, &[panel.body])?;
    conn.poly_rectangle(window, graphics.panel_border_gc, &[panel.body])?;
    conn.image_text8(
        window,
        graphics.text_gc,
        panel.title_x,
        panel.title_y,
        HELP_TITLE.as_bytes(),
    )?;
    conn.image_text8(
        window,
        graphics.dim_text_gc,
        panel.body_x,
        panel.body_y,
        HELP_TEXT.as_bytes(),
    )?;
    Ok(())
}

struct HelpPanel {
    body: Rectangle,
    shadow: Rectangle,
    title_x: i16,
    title_y: i16,
    body_x: i16,
    body_y: i16,
}

fn help_panel_rectangle(screen_width: i32, screen_height: i32) -> HelpPanel {
    let title_width = label_width(HELP_TITLE.len());
    let body_width = label_width(HELP_TEXT.len());
    let content_width = title_width.max(body_width);
    let wanted_width = content_width.saturating_add(LABEL_BOX_PADDING_X.saturating_mul(2));
    let wanted_height = LABEL_TEXT_HEIGHT
        .saturating_mul(2)
        .saturating_add(LABEL_BOX_PADDING_Y.saturating_mul(3));
    let panel_width = wanted_width.min(screen_width.max(1));
    let panel_height = wanted_height.min(screen_height.max(1));
    let x = screen_width.saturating_sub(panel_width).max(0) / 2;
    let y = screen_height.saturating_sub(panel_height).max(0) / 2;
    let body = rect_i32(x, y, panel_width, panel_height).unwrap_or(Rectangle {
        x: 0,
        y: 0,
        width: 1,
        height: 1,
    });
    let shadow = offset_rectangle(body, 0, 4).unwrap_or(body);
    let content_left = x.saturating_add(LABEL_BOX_PADDING_X);
    let title_x = content_left.saturating_add((content_width.saturating_sub(title_width)) / 2);
    let body_x = content_left.saturating_add((content_width.saturating_sub(body_width)) / 2);
    let title_y = y
        .saturating_add(LABEL_BOX_PADDING_Y)
        .saturating_add(LABEL_TEXT_HEIGHT);
    let body_y = title_y
        .saturating_add(LABEL_TEXT_HEIGHT)
        .saturating_add(LABEL_BOX_PADDING_Y);

    HelpPanel {
        body,
        shadow,
        title_x: clamp_i16(title_x),
        title_y: clamp_i16(title_y),
        body_x: clamp_i16(body_x),
        body_y: clamp_i16(body_y),
    }
}

fn selection_grid_rectangles(area: CaptureArea) -> Vec<Rectangle> {
    if area.width < 24 || area.height < 24 {
        return Vec::new();
    }

    let thickness = SELECTION_GRID_THICKNESS.max(1);
    [
        rect_i32(area.x + area.width / 3, area.y, thickness, area.height),
        rect_i32(
            area.x + area.width.saturating_mul(2) / 3,
            area.y,
            thickness,
            area.height,
        ),
        rect_i32(area.x, area.y + area.height / 3, area.width, thickness),
        rect_i32(
            area.x,
            area.y + area.height.saturating_mul(2) / 3,
            area.width,
            thickness,
        ),
    ]
    .into_iter()
    .flatten()
    .collect()
}

fn move_affordance_rectangles(area: CaptureArea) -> Vec<Rectangle> {
    if area.width < 96 || area.height < 72 {
        return Vec::new();
    }

    let length = MOVE_AFFORDANCE_LENGTH
        .min(area.width / 4)
        .min(area.height / 4)
        .max(MOVE_AFFORDANCE_THICKNESS);
    let gap = MOVE_AFFORDANCE_GAP.max(MOVE_AFFORDANCE_THICKNESS);
    let thickness = MOVE_AFFORDANCE_THICKNESS.max(1);
    let cx = area.x + area.width / 2;
    let cy = area.y + area.height / 2;
    let half = thickness / 2;

    [
        rect_i32(cx - gap - length, cy - half, length, thickness),
        rect_i32(cx + gap, cy - half, length, thickness),
        rect_i32(cx - half, cy - gap - length, thickness, length),
        rect_i32(cx - half, cy + gap, thickness, length),
    ]
    .into_iter()
    .flatten()
    .collect()
}

fn shader_effect_rectangles(area: CaptureArea, effect: VideoEffect) -> Vec<Rectangle> {
    match effect {
        VideoEffect::Neon | VideoEffect::Glitch | VideoEffect::Holo => {
            shader_circuit_rectangles(area)
        }
        VideoEffect::Ripple | VideoEffect::Aurora | VideoEffect::Vapor => {
            shader_flow_rectangles(area)
        }
        VideoEffect::Glass | VideoEffect::Fracture | VideoEffect::Prism | VideoEffect::Kaleido => {
            shader_facet_rectangles(area)
        }
        VideoEffect::Heatmap | VideoEffect::Crt | VideoEffect::None => shader_scan_rectangles(area),
    }
}

fn shader_circuit_rectangles(area: CaptureArea) -> Vec<Rectangle> {
    let line_count = (area.height / 36).clamp(3, 8);
    let mut rectangles = Vec::new();
    for i in 0..line_count {
        let y = area.y + ((i + 1) * area.height) / (line_count + 1);
        let jog = ((i * 17 + area.x + area.y) % area.width.max(1)).min(area.width);
        rectangles.extend([
            rect_i32(area.x + 10, y - 1, jog / 2, 3),
            rect_i32(area.x + jog / 2 + 10, y - 9, 3, 10),
            rect_i32(area.x + jog / 2 + 10, y - 9, area.width - jog / 2 - 20, 3),
        ]);
    }
    rectangles.into_iter().flatten().collect()
}

fn shader_flow_rectangles(area: CaptureArea) -> Vec<Rectangle> {
    let line_count = (area.height / 34).clamp(3, 7);
    let mut rectangles = Vec::new();
    for i in 0..line_count {
        let y = area.y + ((i + 1) * area.height) / (line_count + 1);
        let segment_width = (area.width / 8).max(1);
        for step in 0..8 {
            let x = area.x + step * segment_width;
            let wave = ((step * 11 + i * 9 + area.width) % 17) - 8;
            rectangles.push(rect_i32(x, y + wave, segment_width, 3));
        }
    }
    rectangles.into_iter().flatten().collect()
}

fn shader_facet_rectangles(area: CaptureArea) -> Vec<Rectangle> {
    let columns = (area.width / 72).clamp(3, 9);
    let mut rectangles = Vec::new();
    for i in 0..columns {
        let x = area.x + ((i + 1) * area.width) / (columns + 1);
        let tilt = ((i * 19 + area.height) % 29) - 14;
        let mid_y = area.y + area.height / 2;
        rectangles.extend([
            rect_i32(
                x.min(x + tilt),
                area.y + 8,
                (tilt.abs() + 2).max(2),
                mid_y - area.y,
            ),
            rect_i32(
                (x + tilt).min(x - tilt / 2),
                mid_y,
                (tilt + tilt / 2).abs().max(2),
                area.bottom() - mid_y - 8,
            ),
        ]);
    }
    rectangles.into_iter().flatten().collect()
}

fn shader_scan_rectangles(area: CaptureArea) -> Vec<Rectangle> {
    let mut rectangles = Vec::new();
    let mut y = area.y + 8;
    while y < area.bottom() - 8 {
        rectangles.push(rect_i32(area.x + 8, y, area.width.saturating_sub(16), 1));
        y += 12;
    }
    rectangles.into_iter().flatten().collect()
}

fn point(x: i32, y: i32) -> Point {
    Point {
        x: clamp_i16(x),
        y: clamp_i16(y),
    }
}

fn selection_corner_rectangles(area: CaptureArea) -> Vec<Rectangle> {
    let thickness = SELECTION_CORNER_THICKNESS.max(1);
    let half = thickness / 2;
    let length = SELECTION_CORNER_LENGTH
        .min(area.width.max(1))
        .min(area.height.max(1))
        .max(thickness);
    let left = area.x.saturating_sub(half);
    let top = area.y.saturating_sub(half);
    let right = area.right().saturating_sub(length).saturating_add(half);
    let bottom = area.bottom().saturating_sub(thickness).saturating_add(half);
    let edge_right = area.right().saturating_sub(thickness).saturating_add(half);
    let edge_bottom = area.bottom().saturating_sub(length).saturating_add(half);

    [
        rect_i32(left, top, length, thickness),
        rect_i32(left, top, thickness, length),
        rect_i32(right, top, length, thickness),
        rect_i32(edge_right, top, thickness, length),
        rect_i32(left, bottom, length, thickness),
        rect_i32(left, edge_bottom, thickness, length),
        rect_i32(right, bottom, length, thickness),
        rect_i32(edge_right, edge_bottom, thickness, length),
    ]
    .into_iter()
    .flatten()
    .collect()
}

fn selection_edge_meter_rectangles(area: CaptureArea) -> Vec<Rectangle> {
    let length = SELECTION_EDGE_METER_LENGTH
        .min(area.width.max(1))
        .min(area.height.max(1));
    let thickness = SELECTION_EDGE_METER_THICKNESS.max(1);
    let gap = BORDER_SHAPE_PADDING.saturating_sub(thickness).max(1);
    let center_x = area
        .x
        .saturating_add(area.width / 2)
        .saturating_sub(length / 2);
    let center_y = area
        .y
        .saturating_add(area.height / 2)
        .saturating_sub(length / 2);

    [
        rect_i32(center_x, area.y.saturating_sub(gap), length, thickness),
        rect_i32(
            center_x,
            area.bottom().saturating_add(gap),
            length,
            thickness,
        ),
        rect_i32(area.x.saturating_sub(gap), center_y, thickness, length),
        rect_i32(
            area.right().saturating_add(gap),
            center_y,
            thickness,
            length,
        ),
    ]
    .into_iter()
    .flatten()
    .collect()
}

#[cfg(test)]
fn help_label_position(screen_width: i32, screen_height: i32, label_len: usize) -> (i16, i16) {
    let width = label_width(label_len);
    let available_width = screen_width.saturating_sub(width);
    let max_x = available_width.max(0);
    let x = (available_width / 2).clamp(0, max_x);
    let max_y = screen_height
        .saturating_sub(LABEL_PADDING)
        .max(LABEL_TEXT_HEIGHT);
    let y = (screen_height / 2).clamp(LABEL_TEXT_HEIGHT, max_y);

    (clamp_i16(x), clamp_i16(y))
}

#[cfg(test)]
fn shortcut_label_position(
    area: CaptureArea,
    screen_width: i32,
    screen_height: i32,
    label_len: usize,
) -> (i16, i16) {
    let width = label_width(label_len);
    let available_width = screen_width.saturating_sub(width);
    let max_x = available_width.max(0);
    let x = (available_width / 2).clamp(0, max_x);

    let bottom_y = screen_height
        .saturating_sub(LABEL_PADDING)
        .max(LABEL_TEXT_HEIGHT);
    let top_y = LABEL_TEXT_HEIGHT
        .saturating_add(LABEL_PADDING)
        .min(bottom_y);
    let dimension_label = format!("{}x{} px", area.width, area.height);
    let (_, dimension_y) = label_position(area, screen_width, screen_height, dimension_label.len());
    let bottom_overlaps_dimension = label_baselines_overlap(i32::from(dimension_y), bottom_y);
    let bottom_overlaps_selection = label_overlaps_area(x, bottom_y, width, area);
    let top_overlaps_selection = label_overlaps_area(x, top_y, width, area);
    let should_use_top = (bottom_overlaps_selection && !top_overlaps_selection)
        || (bottom_overlaps_dimension && (bottom_overlaps_selection || !top_overlaps_selection));
    let y = if should_use_top { top_y } else { bottom_y };

    (clamp_i16(x), clamp_i16(y))
}

#[cfg(test)]
fn label_baselines_overlap(first: i32, second: i32) -> bool {
    let first_top = first.saturating_sub(LABEL_TEXT_HEIGHT);
    let first_bottom = first.saturating_add(4);
    let second_top = second.saturating_sub(LABEL_TEXT_HEIGHT);
    let second_bottom = second.saturating_add(4);

    first_top <= second_bottom && second_top <= first_bottom
}

#[cfg(test)]
fn label_overlaps_area(x: i32, y: i32, width: i32, area: CaptureArea) -> bool {
    let left = x.saturating_sub(2);
    let right = x.saturating_add(width).saturating_add(2);
    let top = y.saturating_sub(LABEL_TEXT_HEIGHT);
    let bottom = y.saturating_add(4);

    left < area.right() && right > area.x && top < area.bottom() && bottom > area.y
}

#[cfg(test)]
fn label_position(
    area: CaptureArea,
    screen_width: i32,
    screen_height: i32,
    label_len: usize,
) -> (i16, i16) {
    let label_width = label_width(label_len);
    let max_x = screen_width
        .saturating_sub(label_width)
        .saturating_sub(LABEL_PADDING)
        .max(0);
    let x = area.x.saturating_add(LABEL_PADDING).clamp(0, max_x);

    let above = area.y.saturating_sub(LABEL_PADDING);
    let below = area
        .bottom()
        .saturating_add(LABEL_TEXT_HEIGHT)
        .saturating_add(LABEL_PADDING);
    let max_y = screen_height
        .saturating_sub(LABEL_PADDING)
        .max(LABEL_TEXT_HEIGHT);
    let y = if above >= LABEL_TEXT_HEIGHT {
        above
    } else {
        below.clamp(LABEL_TEXT_HEIGHT, max_y)
    };

    (clamp_i16(x), clamp_i16(y))
}

fn label_width(label_len: usize) -> i32 {
    i32::try_from(label_len)
        .unwrap_or(i32::MAX)
        .saturating_mul(LABEL_CHAR_WIDTH)
}

fn clamp_i16(value: i32) -> i16 {
    value.clamp(i32::from(i16::MIN), i32::from(i16::MAX)) as i16
}

fn update_overlay_shape(
    conn: &RustConnection,
    window: Window,
    screen_width: i32,
    screen_height: i32,
    area: Option<CaptureArea>,
    draw_mode: SelectorDrawMode,
    effect: VideoEffect,
) -> Result<()> {
    let rectangles =
        overlay_shape_rectangles_for_mode(screen_width, screen_height, area, draw_mode, effect);
    conn.shape_rectangles(
        SO::SET,
        SK::BOUNDING,
        ClipOrdering::UNSORTED,
        window,
        0,
        0,
        &rectangles,
    )?;
    let input_rectangles: [Rectangle; 0] = [];
    conn.shape_rectangles(
        SO::SET,
        SK::INPUT,
        ClipOrdering::UNSORTED,
        window,
        0,
        0,
        &input_rectangles,
    )?;
    Ok(())
}

#[cfg(test)]
fn overlay_shape_rectangles(
    screen_width: i32,
    screen_height: i32,
    area: Option<CaptureArea>,
) -> Vec<Rectangle> {
    overlay_shape_rectangles_for_mode(
        screen_width,
        screen_height,
        area,
        SelectorDrawMode::Full,
        VideoEffect::None,
    )
}

fn overlay_shape_rectangles_for_mode(
    screen_width: i32,
    screen_height: i32,
    area: Option<CaptureArea>,
    draw_mode: SelectorDrawMode,
    effect: VideoEffect,
) -> Vec<Rectangle> {
    let mut rectangles = shade_rectangles(screen_width, screen_height, area);
    let Some(area) = area else {
        return rectangles;
    };

    rectangles.extend(border_shape_rectangles(area, screen_width, screen_height));
    rectangles.extend(selection_corner_rectangles(area));
    rectangles.extend(selection_info_shape_rectangles(
        area,
        screen_width,
        screen_height,
    ));
    if draw_mode == SelectorDrawMode::Full {
        rectangles.extend(shader_effect_rectangles(area, effect));
        rectangles.extend(selection_grid_rectangles(area));
        rectangles.extend(move_affordance_rectangles(area));
        rectangles.extend(handle_rectangles(area));
        rectangles.extend(handle_shadow_rectangles(area));
        rectangles.extend(action_button_shape_rectangles(
            area,
            screen_width,
            screen_height,
        ));
    }

    rectangles
}

fn selection_info_shape_rectangles(
    area: CaptureArea,
    screen_width: i32,
    screen_height: i32,
) -> Vec<Rectangle> {
    let Some(panel) = selection_info_panel(area, screen_width, screen_height) else {
        return Vec::new();
    };
    vec![panel.shadow, panel.body]
}

#[cfg(test)]
fn overlay_input_rectangles(screen_width: i32, screen_height: i32) -> Vec<Rectangle> {
    rect_i32(0, 0, screen_width, screen_height)
        .into_iter()
        .collect()
}

fn shade_rectangles(
    screen_width: i32,
    screen_height: i32,
    area: Option<CaptureArea>,
) -> Vec<Rectangle> {
    let Some(area) = area else {
        return rect_i32(0, 0, screen_width, screen_height)
            .into_iter()
            .collect();
    };

    [
        rect_i32(0, 0, screen_width, area.y),
        rect_i32(
            0,
            area.bottom(),
            screen_width,
            screen_height - area.bottom(),
        ),
        rect_i32(0, area.y, area.x, area.height),
        rect_i32(
            area.right(),
            area.y,
            screen_width - area.right(),
            area.height,
        ),
    ]
    .into_iter()
    .flatten()
    .collect()
}

fn border_shape_rectangles(
    area: CaptureArea,
    screen_width: i32,
    screen_height: i32,
) -> Vec<Rectangle> {
    let pad = BORDER_SHAPE_PADDING;
    let padding = pad.saturating_mul(2);
    let thickness = padding.saturating_add(1);
    let padded_width = area.width.saturating_add(padding);
    let padded_height = area.height.saturating_add(padding);
    let left = area.x.saturating_sub(pad);
    let top = area.y.saturating_sub(pad);
    let right = area.right().saturating_sub(pad);
    let bottom = area.bottom().saturating_sub(pad);

    [
        clipped_rect_i32(
            left,
            top,
            padded_width,
            thickness,
            screen_width,
            screen_height,
        ),
        clipped_rect_i32(
            left,
            bottom,
            padded_width,
            thickness,
            screen_width,
            screen_height,
        ),
        clipped_rect_i32(
            left,
            top,
            thickness,
            padded_height,
            screen_width,
            screen_height,
        ),
        clipped_rect_i32(
            right,
            top,
            thickness,
            padded_height,
            screen_width,
            screen_height,
        ),
    ]
    .into_iter()
    .flatten()
    .collect()
}

fn action_button_hit(
    point: (i16, i16),
    area: CaptureArea,
    screen_width: i32,
    screen_height: i32,
) -> Option<ActionButton> {
    action_button_rectangles(area, screen_width, screen_height)
        .find(|(_, rectangle)| rectangle_contains(*rectangle, point))
        .map(|(button, _)| button)
}

fn action_control_surface_hit(
    point: (i16, i16),
    area: CaptureArea,
    screen_width: i32,
    screen_height: i32,
) -> bool {
    action_button_shape_rectangles(area, screen_width, screen_height)
        .into_iter()
        .any(|rectangle| rectangle_contains(rectangle, point))
}

fn action_button_rectangles(
    area: CaptureArea,
    screen_width: i32,
    screen_height: i32,
) -> impl Iterator<Item = (ActionButton, Rectangle)> {
    let (confirm_width, cancel_width) = action_button_widths(screen_width);
    let total_width = confirm_width
        .saturating_add(ACTION_BUTTON_GAP)
        .saturating_add(cancel_width);
    let center_x = area.x.saturating_add(area.width / 2);
    let max_x = screen_width.saturating_sub(total_width).max(0);
    let x = center_x.saturating_sub(total_width / 2).clamp(0, max_x);
    let y = action_button_y(area, screen_height);

    [
        rect_i32(x, y, confirm_width, ACTION_BUTTON_HEIGHT)
            .map(|rect| (ActionButton::Confirm, rect)),
        rect_i32(
            x.saturating_add(confirm_width)
                .saturating_add(ACTION_BUTTON_GAP),
            y,
            cancel_width,
            ACTION_BUTTON_HEIGHT,
        )
        .map(|rect| (ActionButton::Cancel, rect)),
    ]
    .into_iter()
    .flatten()
}

fn action_button_widths(screen_width: i32) -> (i32, i32) {
    let confirm_width = action_button_intrinsic_width(ActionButton::Confirm);
    let cancel_width = action_button_intrinsic_width(ActionButton::Cancel);
    let total_width = confirm_width
        .saturating_add(ACTION_BUTTON_GAP)
        .saturating_add(cancel_width);
    if total_width <= screen_width {
        return (confirm_width, cancel_width);
    }

    let available = screen_width.saturating_sub(ACTION_BUTTON_GAP).max(2);
    let confirm_width = (available.saturating_mul(2) / 3).max(1);
    let cancel_width = available.saturating_sub(confirm_width).max(1);
    (confirm_width, cancel_width)
}

fn action_button_shape_rectangles(
    area: CaptureArea,
    screen_width: i32,
    screen_height: i32,
) -> Vec<Rectangle> {
    let mut rectangles = Vec::new();
    if let Some(panel) = action_button_panel_rectangle(area, screen_width, screen_height) {
        if let Some(shadow) = offset_rectangle(panel, 0, ACTION_BUTTON_PANEL_SHADOW_OFFSET) {
            rectangles.push(shadow);
        }
        rectangles.push(panel);
    }
    for (_, rect) in action_button_rectangles(area, screen_width, screen_height) {
        if let Some(shadow) = offset_rectangle(rect, 0, ACTION_BUTTON_SHADOW_OFFSET) {
            rectangles.push(shadow);
        }
        rectangles.push(rect);
    }
    rectangles
}

fn action_button_panel_rectangle(
    area: CaptureArea,
    screen_width: i32,
    screen_height: i32,
) -> Option<Rectangle> {
    let buttons: Vec<_> = action_button_rectangles(area, screen_width, screen_height)
        .map(|(_, rectangle)| rectangle)
        .collect();
    let first = *buttons.first()?;
    let (left, top, right, bottom) = buttons.iter().fold(
        (
            i32::from(first.x),
            i32::from(first.y),
            i32::from(first.x).saturating_add(i32::from(first.width)),
            i32::from(first.y).saturating_add(i32::from(first.height)),
        ),
        |(left, top, right, bottom), rectangle| {
            let rect_left = i32::from(rectangle.x);
            let rect_top = i32::from(rectangle.y);
            let rect_right = rect_left.saturating_add(i32::from(rectangle.width));
            let rect_bottom = rect_top.saturating_add(i32::from(rectangle.height));
            (
                left.min(rect_left),
                top.min(rect_top),
                right.max(rect_right),
                bottom.max(rect_bottom),
            )
        },
    );

    clipped_rect_i32(
        left.saturating_sub(ACTION_BUTTON_PANEL_PADDING_X),
        top.saturating_sub(
            ACTION_BUTTON_PANEL_PADDING_Y.saturating_add(ACTION_BUTTON_PANEL_HEADER_HEIGHT),
        ),
        right
            .saturating_sub(left)
            .saturating_add(ACTION_BUTTON_PANEL_PADDING_X.saturating_mul(2)),
        bottom
            .saturating_sub(top)
            .saturating_add(ACTION_BUTTON_PANEL_PADDING_Y.saturating_mul(2))
            .saturating_add(ACTION_BUTTON_PANEL_HEADER_HEIGHT),
        screen_width,
        screen_height,
    )
}

fn action_button_y(area: CaptureArea, screen_height: i32) -> i32 {
    let clearance = (HANDLE_SIZE / 2).saturating_add(ACTION_RAIL_GAP);
    let max_y = screen_height.saturating_sub(ACTION_BUTTON_HEIGHT).max(0);
    let min_below = area.bottom().saturating_add(clearance);
    let below = area
        .bottom()
        .saturating_add(clearance)
        .saturating_add(ACTION_BUTTON_PANEL_PADDING_Y);
    if min_below <= max_y {
        return below.min(max_y);
    }

    area.y
        .saturating_sub(clearance)
        .saturating_sub(ACTION_BUTTON_PANEL_PADDING_Y)
        .saturating_sub(ACTION_BUTTON_HEIGHT)
        .clamp(0, max_y)
}

fn action_button_label(button: ActionButton, button_width: i32) -> &'static str {
    let available = button_width.saturating_sub(ACTION_BUTTON_PADDING_X);
    match button {
        ActionButton::Confirm if label_width(CONFIRM_TEXT.len()) <= available => CONFIRM_TEXT,
        ActionButton::Confirm if label_width(CONFIRM_TEXT_COMPACT.len()) <= available => {
            CONFIRM_TEXT_COMPACT
        }
        ActionButton::Confirm => CONFIRM_TEXT_TINY,
        ActionButton::Cancel if label_width(CANCEL_TEXT.len()) <= available => CANCEL_TEXT,
        ActionButton::Cancel => CANCEL_TEXT_TINY,
    }
}

fn action_button_intrinsic_width(button: ActionButton) -> i32 {
    let (label_len, min_width) = match button {
        ActionButton::Confirm => (CONFIRM_TEXT.len(), CONFIRM_BUTTON_MIN_WIDTH),
        ActionButton::Cancel => (CANCEL_TEXT.len(), CANCEL_BUTTON_MIN_WIDTH),
    };
    label_width(label_len)
        .saturating_add(ACTION_BUTTON_PADDING_X.saturating_mul(2))
        .max(min_width)
}

#[cfg(test)]
fn label_box_width(label_len: usize) -> i32 {
    label_width(label_len).saturating_add(LABEL_BOX_PADDING_X.saturating_mul(2))
}

#[cfg(test)]
fn label_box_height() -> i32 {
    LABEL_TEXT_HEIGHT.saturating_add(LABEL_BOX_PADDING_Y.saturating_mul(2))
}

fn rect_i32(x: i32, y: i32, width: i32, height: i32) -> Option<Rectangle> {
    if width <= 0 || height <= 0 {
        return None;
    }

    Some(Rectangle {
        x: i16::try_from(x).ok()?,
        y: i16::try_from(y).ok()?,
        width: u16::try_from(width).ok()?,
        height: u16::try_from(height).ok()?,
    })
}

fn clipped_rect_i32(
    x: i32,
    y: i32,
    width: i32,
    height: i32,
    screen_width: i32,
    screen_height: i32,
) -> Option<Rectangle> {
    if width <= 0 || height <= 0 {
        return None;
    }

    let left = x.clamp(0, screen_width.max(0));
    let top = y.clamp(0, screen_height.max(0));
    let right = x.saturating_add(width).clamp(0, screen_width.max(0));
    let bottom = y.saturating_add(height).clamp(0, screen_height.max(0));

    rect_i32(left, top, right - left, bottom - top)
}

fn offset_rectangle(rectangle: Rectangle, dx: i32, dy: i32) -> Option<Rectangle> {
    rect_i32(
        i32::from(rectangle.x).saturating_add(dx),
        i32::from(rectangle.y).saturating_add(dy),
        i32::from(rectangle.width),
        i32::from(rectangle.height),
    )
}

#[cfg(test)]
fn area_from_points(start: (i16, i16), end: (i16, i16)) -> Result<Option<CaptureArea>> {
    area_from_i32_points(
        (i32::from(start.0), i32::from(start.1)),
        (i32::from(end.0), i32::from(end.1)),
    )
}

fn area_from_screen_points(
    start: (i16, i16),
    end: (i16, i16),
    screen_width: i32,
    screen_height: i32,
) -> Result<Option<CaptureArea>> {
    area_from_i32_points(
        clamp_point_to_screen(start, screen_width, screen_height),
        clamp_point_to_screen(end, screen_width, screen_height),
    )
}

fn clamp_point_to_screen(point: (i16, i16), screen_width: i32, screen_height: i32) -> (i32, i32) {
    (
        i32::from(point.0).clamp(0, screen_width.max(0)),
        i32::from(point.1).clamp(0, screen_height.max(0)),
    )
}

fn area_from_i32_points(start: (i32, i32), end: (i32, i32)) -> Result<Option<CaptureArea>> {
    let x1 = start.0.min(end.0);
    let y1 = start.1.min(end.1);
    let x2 = start.0.max(end.0);
    let y2 = start.1.max(end.1);
    let width = x2 - x1;
    let height = y2 - y1;

    if width == 0 || height == 0 {
        return Ok(None);
    }

    CaptureArea::new(x1, y1, width, height).map(Some)
}

fn rectangle_from_area(area: CaptureArea) -> Option<Rectangle> {
    Some(Rectangle {
        x: i16::try_from(area.x).ok()?,
        y: i16::try_from(area.y).ok()?,
        width: u16::try_from(area.width).ok()?,
        height: u16::try_from(area.height).ok()?,
    })
}

fn handle_rectangles(area: CaptureArea) -> Vec<Rectangle> {
    handle_rectangles_with_handles(area)
        .into_iter()
        .map(|(_, rectangle)| rectangle)
        .collect()
}

fn handle_shadow_rectangles(area: CaptureArea) -> Vec<Rectangle> {
    handle_rectangles_with_handles(area)
        .into_iter()
        .filter_map(|(_, rectangle)| offset_rectangle(rectangle, 2, 2))
        .collect()
}

fn handle_rectangles_with_handles(area: CaptureArea) -> [(Handle, Rectangle); 8] {
    let half = HANDLE_SIZE / 2;
    let center_x = area.x + area.width / 2;
    let center_y = area.y + area.height / 2;
    [
        (Handle::TopLeft, handle_rectangle(area.x, area.y, half)),
        (
            Handle::TopRight,
            handle_rectangle(area.right(), area.y, half),
        ),
        (
            Handle::BottomRight,
            handle_rectangle(area.right(), area.bottom(), half),
        ),
        (
            Handle::BottomLeft,
            handle_rectangle(area.x, area.bottom(), half),
        ),
        (Handle::Top, handle_rectangle(center_x, area.y, half)),
        (
            Handle::Right,
            handle_rectangle(area.right(), center_y, half),
        ),
        (
            Handle::Bottom,
            handle_rectangle(center_x, area.bottom(), half),
        ),
        (Handle::Left, handle_rectangle(area.x, center_y, half)),
    ]
}

fn handle_rectangle(x: i32, y: i32, half: i32) -> Rectangle {
    Rectangle {
        x: clamp_i16(x - half),
        y: clamp_i16(y - half),
        width: u16::try_from(HANDLE_SIZE).unwrap_or(u16::MAX),
        height: u16::try_from(HANDLE_SIZE).unwrap_or(u16::MAX),
    }
}

#[cfg(debug_assertions)]
fn spawn_autodrag_if_requested(root: Window, window: Window) {
    let Some(raw) = std::env::var_os(AUTODRAG_ENV) else {
        return;
    };
    let raw = raw.to_string_lossy().into_owned();
    let area = match raw.parse::<CaptureArea>() {
        Ok(area) => area,
        Err(err) => {
            eprintln!("warning: ignoring invalid {AUTODRAG_ENV}={raw:?}: {err:#}");
            return;
        }
    };

    thread::spawn(move || {
        thread::sleep(Duration::from_millis(250));
        if let Err(err) = autodrag(root, window, area) {
            eprintln!("warning: automated selector drag failed: {err:#}");
        }
    });
}

#[cfg(debug_assertions)]
fn autodrag(root: Window, _window: Window, area: CaptureArea) -> Result<()> {
    use x11rb::protocol::{
        xproto::{BUTTON_PRESS_EVENT, BUTTON_RELEASE_EVENT, MOTION_NOTIFY_EVENT},
        xtest::ConnectionExt as XTestConnectionExt,
    };

    let (conn, _) = connect(None).context("failed to connect to X11 display for autodrag")?;
    let start_x = i16::try_from(area.x).context("autodrag x is outside X11 coordinate range")?;
    let start_y = i16::try_from(area.y).context("autodrag y is outside X11 coordinate range")?;
    let end_x = i16::try_from(area.x + area.width)
        .context("autodrag right edge is outside X11 coordinate range")?;
    let end_y = i16::try_from(area.y + area.height)
        .context("autodrag bottom edge is outside X11 coordinate range")?;
    conn.xtest_fake_input(
        MOTION_NOTIFY_EVENT,
        0,
        CURRENT_TIME,
        root,
        start_x,
        start_y,
        0,
    )?;
    conn.flush()?;
    thread::sleep(Duration::from_millis(100));

    conn.xtest_fake_input(
        BUTTON_PRESS_EVENT,
        1,
        CURRENT_TIME,
        root,
        start_x,
        start_y,
        0,
    )?;
    conn.flush()?;
    thread::sleep(Duration::from_millis(100));

    conn.xtest_fake_input(MOTION_NOTIFY_EVENT, 0, CURRENT_TIME, root, end_x, end_y, 0)?;
    conn.flush()?;
    thread::sleep(Duration::from_millis(100));

    conn.xtest_fake_input(BUTTON_RELEASE_EVENT, 1, CURRENT_TIME, root, end_x, end_y, 0)?;
    conn.flush()?;

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn area_from_drag_points_normalizes_direction() {
        assert_eq!(
            area_from_points((300, 200), (100, 50)).unwrap(),
            Some(CaptureArea {
                x: 100,
                y: 50,
                width: 200,
                height: 150,
            })
        );
    }

    #[test]
    fn area_from_drag_points_rejects_click_without_drag() {
        assert_eq!(area_from_points((100, 50), (100, 50)).unwrap(), None);
    }

    #[test]
    fn button_mapping_distinguishes_primary_and_cancel() {
        assert!(is_primary_button_detail(1));
        assert!(!is_primary_button_detail(3));
        assert!(is_cancel_button_detail(3));
        assert!(!is_cancel_button_detail(1));
    }

    #[test]
    fn area_from_screen_points_clamps_to_screen_bounds() {
        assert_eq!(
            area_from_screen_points((-10, -20), (150, 90), 100, 80).unwrap(),
            Some(CaptureArea {
                x: 0,
                y: 0,
                width: 100,
                height: 80,
            })
        );
    }

    #[test]
    fn update_drag_clamps_new_selection_to_screen_bounds() {
        assert_eq!(
            update_drag(Drag::New { start: (50, 40) }, (150, 90), 100, 80).unwrap(),
            Some(CaptureArea {
                x: 50,
                y: 40,
                width: 50,
                height: 40,
            })
        );
    }

    #[test]
    fn handle_rectangles_are_centered_on_corners_and_edges() {
        let handles = handle_rectangles(CaptureArea::new(20, 10, 30, 40).unwrap());

        assert_eq!(handles.len(), 8);
        assert_eq!(rect_tuple(handles[0]), (11, 1, 18, 18));
        assert_eq!(rect_tuple(handles[1]), (41, 1, 18, 18));
        assert_eq!(rect_tuple(handles[2]), (41, 41, 18, 18));
        assert_eq!(rect_tuple(handles[3]), (11, 41, 18, 18));
        assert_eq!(rect_tuple(handles[4]), (26, 1, 18, 18));
        assert_eq!(rect_tuple(handles[5]), (41, 21, 18, 18));
        assert_eq!(rect_tuple(handles[6]), (26, 41, 18, 18));
        assert_eq!(rect_tuple(handles[7]), (11, 21, 18, 18));
    }

    #[test]
    fn hit_test_detects_handles_inside_and_outside() {
        let area = CaptureArea::new(20, 10, 30, 40).unwrap();

        assert_eq!(hit_test((20, 10), area), Hit::Handle(Handle::TopLeft));
        assert_eq!(hit_test((35, 25), area), Hit::Inside);
        assert_eq!(hit_test((80, 70), area), Hit::Outside);
    }

    #[test]
    fn hit_test_keeps_area_edges_resizable() {
        let area = CaptureArea::new(20, 10, 30, 40).unwrap();

        assert_eq!(hit_test((50, 18), area), Hit::Handle(Handle::TopRight));
        assert_eq!(hit_test((35, 50), area), Hit::Handle(Handle::Bottom));
    }

    #[test]
    fn rectangle_contains_treats_right_and_bottom_as_exclusive() {
        let rectangle = Rectangle {
            x: 10,
            y: 20,
            width: 30,
            height: 40,
        };

        assert!(rectangle_contains(rectangle, (39, 59)));
        assert!(!rectangle_contains(rectangle, (40, 59)));
        assert!(!rectangle_contains(rectangle, (39, 60)));
    }

    #[test]
    fn clipped_rect_saturates_edges_before_clamping() {
        assert_eq!(
            rect_tuple(clipped_rect_i32(90, 90, i32::MAX, i32::MAX, 100, 100).unwrap()),
            (90, 90, 10, 10)
        );
    }

    #[test]
    fn hit_test_detects_edge_handles() {
        let area = CaptureArea::new(20, 10, 30, 40).unwrap();

        assert_eq!(hit_test((35, 10), area), Hit::Handle(Handle::Top));
        assert_eq!(hit_test((50, 30), area), Hit::Handle(Handle::Right));
        assert_eq!(hit_test((35, 50), area), Hit::Handle(Handle::Bottom));
        assert_eq!(hit_test((20, 30), area), Hit::Handle(Handle::Left));
    }

    #[test]
    fn cursor_kind_tracks_selection_hit_target() {
        let area = CaptureArea::new(20, 10, 30, 40).unwrap();

        assert_eq!(
            cursor_kind_for_point((5, 5), None, 800, 600),
            CursorKind::Crosshair
        );
        assert_eq!(
            cursor_kind_for_point((300, 70), Some(area), 800, 600),
            CursorKind::Crosshair
        );
        assert_eq!(
            cursor_kind_for_point((35, 25), Some(area), 800, 600),
            CursorKind::Move
        );
        assert_eq!(
            cursor_kind_for_point((50, 50), Some(area), 800, 600),
            CursorKind::Resize(Handle::BottomRight)
        );
        assert_eq!(
            cursor_kind_for_point((35, 10), Some(area), 800, 600),
            CursorKind::Resize(Handle::Top)
        );
    }

    #[test]
    fn cursor_kind_tracks_action_controls() {
        let area = CaptureArea::new(100, 100, 300, 200).unwrap();
        let confirm = action_button_rectangles(area, 800, 600)
            .find(|(button, _)| *button == ActionButton::Confirm)
            .map(|(_, rectangle)| rectangle)
            .unwrap();
        let panel = action_button_panel_rectangle(area, 800, 600).unwrap();
        let button_point = (confirm.x + 1, confirm.y + 1);
        let panel_point = (panel.x + 1, panel.y + 1);

        assert_eq!(
            cursor_kind_for_point(button_point, Some(area), 800, 600),
            CursorKind::Pointer
        );
        assert_eq!(action_button_hit(panel_point, area, 800, 600), None);
        assert_eq!(
            cursor_kind_for_point(panel_point, Some(area), 800, 600),
            CursorKind::Default
        );
    }

    #[test]
    fn cursor_kind_stays_locked_to_active_drag() {
        let area = CaptureArea::new(20, 10, 30, 40).unwrap();

        assert_eq!(
            cursor_kind_for_state(
                (90, 70),
                Some(area),
                Some(Drag::Resize {
                    handle: Handle::TopLeft,
                    original: area,
                }),
                800,
                600,
            ),
            CursorKind::Resize(Handle::TopLeft)
        );
        assert_eq!(
            cursor_kind_for_state(
                (90, 70),
                Some(area),
                Some(Drag::Move {
                    start: (35, 25),
                    original: area,
                }),
                800,
                600,
            ),
            CursorKind::Move
        );
    }

    #[test]
    fn move_area_clamps_to_screen() {
        assert_eq!(
            move_area(CaptureArea::new(20, 10, 30, 40).unwrap(), 100, 100, 100, 80),
            CaptureArea {
                x: 70,
                y: 40,
                width: 30,
                height: 40,
            }
        );
    }

    #[test]
    fn move_area_saturates_extreme_delta_before_clamping() {
        assert_eq!(
            move_area(
                CaptureArea::new(i32::MAX - 20, 10, 10, 10).unwrap(),
                i32::MAX,
                i32::MIN,
                i32::MAX,
                100,
            ),
            CaptureArea {
                x: i32::MAX - 10,
                y: 0,
                width: 10,
                height: 10,
            }
        );
    }

    #[test]
    fn resize_area_by_delta_clamps_to_screen() {
        assert_eq!(
            resize_area_by_delta(CaptureArea::new(80, 60, 30, 40).unwrap(), 100, 100, 100, 80)
                .unwrap(),
            CaptureArea {
                x: 80,
                y: 60,
                width: 20,
                height: 20,
            }
        );
    }

    #[test]
    fn resize_area_by_delta_saturates_extreme_delta_before_clamping() {
        assert_eq!(
            resize_area_by_delta(
                CaptureArea::new(0, 0, i32::MAX - 10, 10).unwrap(),
                i32::MAX,
                i32::MIN,
                i32::MAX,
                100,
            )
            .unwrap(),
            CaptureArea {
                x: 0,
                y: 0,
                width: i32::MAX,
                height: 1,
            }
        );
    }

    #[test]
    fn resize_interval_saturates_extreme_endpoints() {
        assert_eq!(
            resize_interval(i32::MAX, i32::MIN, i32::MAX, true),
            (i32::MIN, i32::MAX)
        );
        assert_eq!(
            resize_interval(i32::MIN, i32::MAX, i32::MAX, false),
            (i32::MIN, i32::MAX)
        );
        assert_eq!(
            resize_interval(i32::MAX, i32::MAX, i32::MAX, false),
            (i32::MAX - 1, 1)
        );
    }

    #[test]
    fn resize_area_from_corner_normalizes_crossed_edges() {
        assert_eq!(
            resize_area(
                CaptureArea::new(20, 10, 30, 40).unwrap(),
                Handle::TopLeft,
                (80, 70),
                100,
                80,
            )
            .unwrap(),
            CaptureArea {
                x: 50,
                y: 50,
                width: 30,
                height: 20,
            }
        );
    }

    #[test]
    fn resize_area_from_edge_keeps_opposite_edge_fixed() {
        assert_eq!(
            resize_area(
                CaptureArea::new(20, 10, 30, 40).unwrap(),
                Handle::Left,
                (10, 30),
                100,
                80,
            )
            .unwrap(),
            CaptureArea {
                x: 10,
                y: 10,
                width: 40,
                height: 40,
            }
        );
        assert_eq!(
            resize_area(
                CaptureArea::new(20, 10, 30, 40).unwrap(),
                Handle::Bottom,
                (35, 70),
                100,
                80,
            )
            .unwrap(),
            CaptureArea {
                x: 20,
                y: 10,
                width: 30,
                height: 60,
            }
        );
    }

    #[test]
    fn resize_area_collapses_corner_to_one_pixel_instead_of_erroring() {
        assert_eq!(
            resize_area(
                CaptureArea::new(20, 10, 30, 40).unwrap(),
                Handle::TopLeft,
                (50, 50),
                100,
                80,
            )
            .unwrap(),
            CaptureArea {
                x: 49,
                y: 49,
                width: 1,
                height: 1,
            }
        );
        assert_eq!(
            resize_area(
                CaptureArea::new(20, 10, 30, 40).unwrap(),
                Handle::BottomRight,
                (20, 10),
                100,
                80,
            )
            .unwrap(),
            CaptureArea {
                x: 20,
                y: 10,
                width: 1,
                height: 1,
            }
        );
    }

    #[test]
    fn resize_area_collapses_edge_to_one_pixel_instead_of_erroring() {
        assert_eq!(
            resize_area(
                CaptureArea::new(20, 10, 30, 40).unwrap(),
                Handle::Left,
                (50, 30),
                100,
                80,
            )
            .unwrap(),
            CaptureArea {
                x: 49,
                y: 10,
                width: 1,
                height: 40,
            }
        );
        assert_eq!(
            resize_area(
                CaptureArea::new(20, 10, 30, 40).unwrap(),
                Handle::Top,
                (35, 50),
                100,
                80,
            )
            .unwrap(),
            CaptureArea {
                x: 20,
                y: 49,
                width: 30,
                height: 1,
            }
        );
    }

    #[test]
    fn update_drag_creates_new_selection() {
        assert_eq!(
            update_drag(Drag::New { start: (10, 20) }, (50, 70), 100, 100).unwrap(),
            Some(CaptureArea {
                x: 10,
                y: 20,
                width: 40,
                height: 50,
            })
        );
    }

    #[test]
    fn key_action_maps_arrows_to_move() {
        assert_eq!(
            key_action_from_keysyms(&[XK_RIGHT], KeyButMask::default()),
            KeyAction::Move { dx: 1, dy: 0 }
        );
        assert_eq!(
            key_action_from_keysyms(&[XK_UP], KeyButMask::CONTROL),
            KeyAction::Move { dx: 0, dy: -10 }
        );
    }

    #[test]
    fn key_action_maps_shift_arrows_to_resize() {
        assert_eq!(
            key_action_from_keysyms(&[XK_LEFT], KeyButMask::SHIFT),
            KeyAction::Resize { dw: -1, dh: 0 }
        );
        assert_eq!(
            key_action_from_keysyms(&[XK_DOWN], KeyButMask::SHIFT | KeyButMask::CONTROL),
            KeyAction::Resize { dw: 0, dh: 10 }
        );
    }

    #[test]
    fn key_action_maps_confirm_cancel_and_reset() {
        assert_eq!(
            key_action_from_keysyms(&[XK_RETURN], KeyButMask::default()),
            KeyAction::Confirm
        );
        assert_eq!(
            key_action_from_keysyms(&[XK_ESCAPE], KeyButMask::default()),
            KeyAction::Cancel
        );
        assert_eq!(
            key_action_from_keysyms(&[XK_DELETE], KeyButMask::default()),
            KeyAction::Reset
        );
    }

    #[test]
    fn action_buttons_are_clickable_after_selection() {
        let area = CaptureArea::new(100, 100, 300, 200).unwrap();
        let buttons: Vec<_> = action_button_rectangles(area, 800, 600).collect();

        assert_eq!(buttons.len(), 2);
        for (button, rectangle) in buttons {
            let point = (rectangle.x + 1, rectangle.y + 1);
            assert_eq!(action_button_hit(point, area, 800, 600), Some(button));
        }
    }

    #[test]
    fn action_buttons_have_comfortable_targets() {
        let area = CaptureArea::new(100, 100, 300, 200).unwrap();
        let buttons: Vec<_> = action_button_rectangles(area, 800, 600).collect();

        let confirm = buttons
            .iter()
            .find(|(button, _)| *button == ActionButton::Confirm)
            .map(|(_, rectangle)| *rectangle)
            .unwrap();
        let cancel = buttons
            .iter()
            .find(|(button, _)| *button == ActionButton::Cancel)
            .map(|(_, rectangle)| *rectangle)
            .unwrap();

        assert!(confirm.width >= u16::try_from(CONFIRM_BUTTON_MIN_WIDTH).unwrap());
        assert!(cancel.width >= u16::try_from(CANCEL_BUTTON_MIN_WIDTH).unwrap());
        assert!(confirm.height >= 40);
        assert!(cancel.height >= 40);
    }

    #[test]
    fn action_buttons_fit_narrow_screens() {
        let area = CaptureArea::new(20, 10, 60, 50).unwrap();
        let buttons: Vec<_> = action_button_rectangles(area, 140, 120).collect();

        assert_eq!(buttons.len(), 2);
        for (button, rectangle) in buttons {
            let right = i32::from(rectangle.x).saturating_add(i32::from(rectangle.width));
            assert!(i32::from(rectangle.x) >= 0);
            assert!(right <= 140);
            assert_eq!(
                action_button_hit((rectangle.x + 1, rectangle.y + 1), area, 140, 120),
                Some(button)
            );
        }
    }

    #[test]
    fn action_button_labels_compact_for_narrow_targets() {
        assert_eq!(
            action_button_label(ActionButton::Confirm, 164),
            CONFIRM_TEXT
        );
        assert_eq!(
            action_button_label(ActionButton::Confirm, 110),
            CONFIRM_TEXT_COMPACT
        );
        assert_eq!(
            action_button_label(ActionButton::Confirm, 42),
            CONFIRM_TEXT_TINY
        );
        assert_eq!(action_button_label(ActionButton::Cancel, 96), CANCEL_TEXT);
        assert_eq!(
            action_button_label(ActionButton::Cancel, 42),
            CANCEL_TEXT_TINY
        );
    }

    #[test]
    fn selection_info_panel_prefers_above_the_frame() {
        let area = CaptureArea::new(100, 100, 300, 200).unwrap();
        let panel = selection_info_panel(area, 800, 600).unwrap();

        assert!(i32::from(panel.body.y) + i32::from(panel.body.height) < area.y);
        assert!(panel.text.contains("300 x 200"));
        assert!(panel.text.contains("x 100, y 100"));
    }

    #[test]
    fn selection_info_panel_moves_below_near_top_edge() {
        let area = CaptureArea::new(10, 0, 300, 200).unwrap();
        let panel = selection_info_panel(area, 320, 240).unwrap();
        let right = i32::from(panel.body.x) + i32::from(panel.body.width);
        let bottom = i32::from(panel.body.y) + i32::from(panel.body.height);

        assert!(i32::from(panel.body.x) >= 0);
        assert!(i32::from(panel.body.y) > area.bottom());
        assert!(right <= 320);
        assert!(bottom <= 240);
    }

    #[test]
    fn action_buttons_move_above_selection_when_bottom_is_full() {
        let area = CaptureArea::new(100, 550, 300, 40).unwrap();
        let buttons: Vec<_> = action_button_rectangles(area, 800, 600).collect();

        assert!(
            buttons
                .iter()
                .all(|(_, rectangle)| i32::from(rectangle.y) < area.y)
        );
    }

    #[test]
    fn action_panel_stays_clear_of_resize_handles() {
        let area = CaptureArea::new(100, 100, 300, 200).unwrap();
        let panel = action_button_panel_rectangle(area, 800, 600).unwrap();
        let clearance = (HANDLE_SIZE / 2).saturating_add(ACTION_RAIL_GAP);

        assert!(i32::from(panel.y) >= area.bottom().saturating_add(clearance));
    }

    #[test]
    fn primary_press_ignores_action_panel_padding() {
        let area = CaptureArea::new(100, 100, 300, 200).unwrap();
        let panel = action_button_panel_rectangle(area, 800, 600).unwrap();
        let panel_point = (panel.x + 1, panel.y + 1);
        let outside_point = (10, 10);

        assert_eq!(action_button_hit(panel_point, area, 800, 600), None);
        assert!(action_control_surface_hit(panel_point, area, 800, 600));
        assert_eq!(
            drag_for_primary_press(panel_point, Some(area), 800, 600),
            None
        );
        assert_eq!(
            drag_for_primary_press(outside_point, Some(area), 800, 600),
            Some(Drag::New {
                start: outside_point
            })
        );
    }

    #[test]
    fn confirm_on_release_mode_accepts_selection_immediately() {
        let area = CaptureArea::new(10, 20, 160, 120).unwrap();

        assert_eq!(
            selection_on_release(SelectorMode::ConfirmOnRelease, Some(area)),
            Some(area)
        );
        assert_eq!(
            selection_on_release(SelectorMode::Editable, Some(area)),
            None
        );
        assert_eq!(
            selection_on_release(SelectorMode::ConfirmOnRelease, None),
            None
        );
    }

    #[test]
    fn existing_selection_is_clamped_to_current_screen() {
        let area = CaptureArea::new(700, 500, 400, 300).unwrap();

        assert_eq!(
            move_area(area, 0, 0, 800, 600),
            CaptureArea {
                x: 400,
                y: 300,
                width: 400,
                height: 300,
            }
        );
    }

    #[test]
    fn label_position_prefers_above_selection() {
        let (x, y) = label_position(
            CaptureArea::new(100, 100, 300, 200).unwrap(),
            1920,
            1080,
            10,
        );

        assert_eq!(x, 112);
        assert_eq!(y, 88);
    }

    #[test]
    fn label_position_stays_visible_near_top_left() {
        let (x, y) = label_position(CaptureArea::new(0, 0, 300, 200).unwrap(), 320, 240, 10);

        assert_eq!(x, 12);
        assert!(y >= 14);
    }

    #[test]
    fn label_position_stays_visible_near_right_edge() {
        let (x, _) = label_position(CaptureArea::new(280, 100, 30, 30).unwrap(), 320, 240, 10);

        assert!(x <= 232);
    }

    #[test]
    fn help_label_position_is_centered() {
        let (x, y) = help_label_position(1000, 500, HELP_TEXT.len());

        assert_eq!(i32::from(x), (1000 - label_width(HELP_TEXT.len())) / 2);
        assert_eq!(y, 250);
    }

    #[test]
    fn help_label_position_stays_visible_on_tiny_screen() {
        let (x, y) = help_label_position(40, 20, HELP_TEXT.len());

        assert_eq!(x, 0);
        assert_eq!(y, 14);
    }

    #[test]
    fn label_width_saturates_large_lengths() {
        assert_eq!(
            label_width((i32::MAX as usize / LABEL_CHAR_WIDTH as usize) + 1),
            i32::MAX
        );
        assert_eq!(label_width(usize::MAX), i32::MAX);
    }

    #[test]
    fn label_box_dimensions_saturate() {
        assert_eq!(label_box_width(usize::MAX), i32::MAX);
        assert_eq!(
            label_box_height(),
            LABEL_TEXT_HEIGHT + LABEL_BOX_PADDING_Y * 2
        );
    }

    #[test]
    fn label_positions_tolerate_saturated_label_widths() {
        let tiny_area = CaptureArea::new(0, 0, 40, 20).unwrap();

        assert_eq!(help_label_position(40, 20, usize::MAX), (0, 14));
        assert_eq!(
            shortcut_label_position(tiny_area, 40, 20, usize::MAX),
            (0, 14)
        );
        assert_eq!(
            label_position(
                CaptureArea::new(i32::MAX - 1, i32::MAX - 1, 1, 1).unwrap(),
                i32::MAX,
                i32::MAX,
                usize::MAX,
            ),
            (0, i16::MAX)
        );
    }

    #[test]
    fn label_overlap_saturates_extreme_geometry() {
        assert!(label_baselines_overlap(i32::MAX, i32::MAX));
        assert!(label_overlaps_area(
            0,
            LABEL_TEXT_HEIGHT,
            i32::MAX,
            CaptureArea::new(100, 0, 1, 1).unwrap(),
        ));
    }

    #[test]
    fn shortcut_label_position_uses_bottom_when_dimension_is_above() {
        let (x, y) = shortcut_label_position(
            CaptureArea::new(100, 100, 300, 200).unwrap(),
            1000,
            500,
            SHORTCUT_TEXT.len(),
        );

        assert_eq!(x, 296);
        assert_eq!(y, 488);
    }

    #[test]
    fn shortcut_label_position_uses_top_when_dimension_is_at_bottom() {
        let (x, y) = shortcut_label_position(
            CaptureArea::new(0, 0, 1000, 500).unwrap(),
            1000,
            500,
            SHORTCUT_TEXT.len(),
        );

        assert_eq!(x, 296);
        assert_eq!(y, 26);
    }

    #[test]
    fn shortcut_label_position_uses_bottom_when_top_would_cover_selection() {
        let (x, y) = shortcut_label_position(
            CaptureArea::new(20, 10, 30, 40).unwrap(),
            100,
            80,
            SHORTCUT_TEXT.len(),
        );

        assert_eq!(x, 0);
        assert_eq!(y, 68);
    }

    #[test]
    fn shortcut_label_position_uses_top_when_bottom_would_cover_selection() {
        let (x, y) = shortcut_label_position(
            CaptureArea::new(20, 50, 30, 25).unwrap(),
            100,
            80,
            SHORTCUT_TEXT.len(),
        );

        assert_eq!(x, 0);
        assert_eq!(y, 26);
    }

    #[test]
    fn shortcut_label_position_stays_visible_on_tiny_screen() {
        let (x, y) = shortcut_label_position(
            CaptureArea::new(0, 0, 40, 20).unwrap(),
            40,
            20,
            SHORTCUT_TEXT.len(),
        );

        assert_eq!(x, 0);
        assert_eq!(y, 14);
    }

    #[test]
    fn shade_rectangles_cut_out_selection() {
        let rectangles = shade_rectangles(
            100,
            80,
            Some(CaptureArea {
                x: 20,
                y: 10,
                width: 30,
                height: 40,
            }),
        );

        assert_eq!(rectangles.len(), 4);
        assert_eq!(rect_tuple(rectangles[0]), (0, 0, 100, 10));
        assert_eq!(rect_tuple(rectangles[1]), (0, 50, 100, 30));
        assert_eq!(rect_tuple(rectangles[2]), (0, 10, 20, 40));
        assert_eq!(rect_tuple(rectangles[3]), (50, 10, 50, 40));
    }

    #[test]
    fn shade_rectangles_skip_zero_sized_edges() {
        let rectangles = shade_rectangles(
            100,
            80,
            Some(CaptureArea {
                x: 0,
                y: 0,
                width: 100,
                height: 40,
            }),
        );

        assert_eq!(rectangles.len(), 1);
        assert_eq!(rect_tuple(rectangles[0]), (0, 40, 100, 40));
    }

    #[test]
    fn overlay_shape_keeps_selection_clear_and_decorations_visible() {
        let area = CaptureArea {
            x: 20,
            y: 10,
            width: 60,
            height: 50,
        };
        let rectangles = overlay_shape_rectangles(140, 120, Some(area));

        assert!(rectangles_contain_point(&rectangles, (0, 0)));
        assert!(rectangles_contain_point(&rectangles, (20, 10)));
        assert!(rectangles_contain_point(&rectangles, (40, 35)));
        assert!(rectangles_contain_point(&rectangles, (52, 72)));
        assert!(!rectangles_contain_point(&rectangles, (55, 36)));
    }

    #[test]
    fn overlay_input_covers_clear_selection_for_drag_move() {
        let rectangles = overlay_input_rectangles(140, 120);

        assert!(rectangles_contain_point(&rectangles, (55, 36)));
        assert!(rectangles_contain_point(&rectangles, (0, 0)));
    }

    #[test]
    fn dragging_shape_skips_heavy_controls() {
        let area = CaptureArea::new(20, 10, 60, 50).unwrap();
        let full = overlay_shape_rectangles_for_mode(
            140,
            120,
            Some(area),
            SelectorDrawMode::Full,
            VideoEffect::None,
        );
        let dragging = overlay_shape_rectangles_for_mode(
            140,
            120,
            Some(area),
            SelectorDrawMode::Dragging,
            VideoEffect::None,
        );

        assert!(full.len() > dragging.len());
        assert!(rectangles_contain_point(&full, (40, 35)));
        assert!(!rectangles_contain_point(&dragging, (40, 35)));
    }

    #[test]
    fn border_shape_saturates_extreme_padded_dimensions() {
        let rectangles =
            border_shape_rectangles(CaptureArea::new(0, 0, i32::MAX, 1).unwrap(), 100, 10);

        assert_eq!(rectangles.len(), 3);
        assert!(rectangles.iter().all(|rectangle| rectangle.width > 0));
        assert!(rectangles.iter().all(|rectangle| rectangle.height > 0));
        assert!(rectangles_contain_point(&rectangles, (0, 0)));
    }

    #[test]
    fn overlay_shape_keeps_full_screen_label_visible() {
        let rectangles = overlay_shape_rectangles(
            100,
            80,
            Some(CaptureArea {
                x: 0,
                y: 0,
                width: 100,
                height: 80,
            }),
        );

        assert!(rectangles_contain_point(&rectangles, (8, 72)));
        assert!(rectangles_contain_point(&rectangles, (90, 22)));
        assert!(!rectangles_contain_point(&rectangles, (90, 60)));
    }

    #[test]
    fn overlay_shape_exposes_full_screen_action_panel() {
        let rectangles = overlay_shape_rectangles(
            300,
            200,
            Some(CaptureArea {
                x: 0,
                y: 0,
                width: 300,
                height: 200,
            }),
        );

        assert!(rectangles_contain_point(&rectangles, (34, 30)));
    }

    #[test]
    fn selection_grid_adds_subtle_interior_guides() {
        let rectangles = selection_grid_rectangles(CaptureArea::new(20, 10, 60, 50).unwrap());

        assert_eq!(rectangles.len(), 4);
        assert!(rectangles_contain_point(&rectangles, (40, 35)));
        assert!(rectangles_contain_point(&rectangles, (55, 26)));
        assert!(!rectangles_contain_point(&rectangles, (55, 36)));
    }

    #[test]
    fn move_affordance_is_small_and_skips_tiny_crops() {
        let rectangles = move_affordance_rectangles(CaptureArea::new(20, 10, 160, 120).unwrap());

        assert_eq!(rectangles.len(), 4);
        assert!(rectangles.iter().all(|rectangle| {
            i32::from(rectangle.width) <= MOVE_AFFORDANCE_LENGTH
                || i32::from(rectangle.height) <= MOVE_AFFORDANCE_LENGTH
        }));
        assert!(move_affordance_rectangles(CaptureArea::new(20, 10, 80, 60).unwrap()).is_empty());
    }

    fn rect_tuple(rect: Rectangle) -> (i16, i16, u16, u16) {
        (rect.x, rect.y, rect.width, rect.height)
    }

    fn rectangles_contain_point(rectangles: &[Rectangle], point: (i16, i16)) -> bool {
        rectangles
            .iter()
            .any(|rectangle| rectangle_contains(*rectangle, point))
    }
}
