use std::{
    cell::{Cell, RefCell},
    env,
    f64::consts::{FRAC_PI_2, PI},
    io::Read,
    path::{Path, PathBuf},
    process::{Child, Command, Stdio},
    rc::Rc,
    sync::mpsc,
    thread,
    time::{Duration, Instant},
};

#[cfg(unix)]
use std::os::unix::process::CommandExt;

use adw::prelude::*;
use anyhow::{Context, Result, ensure};
use gtk::{gio, glib};
use ocula::{pipeline::CaptureArea, selector};
use x11rb::{
    COPY_DEPTH_FROM_PARENT, connect,
    connection::Connection,
    protocol::{
        shape::{ConnectionExt as ShapeConnectionExt, SK, SO},
        xproto::{
            AtomEnum, ClipOrdering, Colormap, ConfigureWindowAux, ConnectionExt as XConnectionExt,
            CreateWindowAux, ImageFormat, ImageOrder, PropMode, Rectangle, Screen, StackMode,
            Visualid, Visualtype, Window, WindowClass,
        },
    },
    rust_connection::RustConnection,
    wrapper::ConnectionExt as _,
};

const APP_ID: &str = "dev.ocula.Ocula";
const LOG_CHANNEL_CAPACITY: usize = 256;
const LOG_SCROLLBACK_LINES: i32 = 500;
const LOG_SCROLLBACK_CHARS: i32 = 80_000;
const LOG_DRAIN_LINES_PER_TICK: usize = 64;
const LOG_READ_CHUNK_BYTES: usize = 1024;
const LOG_LINE_MAX_BYTES: usize = 4096;
const PREVIEW_CAPTURE_MAX_PIXELS: i64 = 8_294_400;
const CROP_OVERLAY_THICKNESS: i32 = 8;
const CROP_OVERLAY_EDGE_THICKNESS: i32 = 2;
const CROP_OVERLAY_OPACITY: u32 = 0xd800_0000;
const STOP_GRACE: Duration = Duration::from_secs(15);
const FORCE_KILL_GRACE: Duration = Duration::from_secs(5);

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode {
    Screen,
    Window,
    Area,
}

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

struct PreviewSnapshot {
    surface: gtk::cairo::ImageSurface,
    width: i32,
    height: i32,
}

#[derive(Debug, Clone, Copy)]
struct PreviewEffectStyle {
    name: &'static str,
    primary: (f64, f64, f64),
    secondary: (f64, f64, f64),
    tertiary: (f64, f64, f64),
}

struct CropOverlay {
    conn: RustConnection,
    windows: Vec<Window>,
    selected: SelectedArea,
}

impl Drop for CropOverlay {
    fn drop(&mut self) {
        destroy_crop_overlay_windows(&self.conn, &self.windows);
    }
}

#[derive(Clone)]
struct Ui {
    window: adw::ApplicationWindow,
    controls_card: gtk::Box,
    screen_button: gtk::ToggleButton,
    window_button: gtk::ToggleButton,
    area_button: gtk::ToggleButton,
    audio_dropdown: gtk::DropDown,
    hide_pointer_switch: gtk::Switch,
    area_row: gtk::Box,
    select_area_button: gtk::Button,
    area_label: gtk::Label,
    area_preview: gtk::DrawingArea,
    format_dropdown: gtk::DropDown,
    encoder_dropdown: gtk::DropDown,
    effect_dropdown: gtk::DropDown,
    output_entry: gtk::Entry,
    record_button: gtk::Button,
    status_label: gtk::Label,
    log_buffer: gtk::TextBuffer,
    selected_area: Rc<RefCell<Option<SelectedArea>>>,
    preview_snapshot: Rc<RefCell<Option<PreviewSnapshot>>>,
    crop_overlay: Rc<RefCell<Option<CropOverlay>>>,
    crop_overlay_failed: Rc<Cell<Option<SelectedArea>>>,
    child: Rc<RefCell<Option<Child>>>,
    stop_started_at: Rc<RefCell<Option<Instant>>>,
    force_stop_started_at: Rc<RefCell<Option<Instant>>>,
    kill_sent: Rc<Cell<bool>>,
    close_after_child_exit: Rc<Cell<bool>>,
    window_destroyed: Rc<Cell<bool>>,
    log_tx: mpsc::SyncSender<String>,
    log_rx: Rc<RefCell<mpsc::Receiver<String>>>,
}

fn main() -> glib::ExitCode {
    if version_requested(env::args().skip(1)) {
        println!("ocula {}", env!("CARGO_PKG_VERSION"));
        return glib::ExitCode::SUCCESS;
    }

    let app = adw::Application::builder().application_id(APP_ID).build();
    app.connect_activate(build_ui);
    app.run()
}

fn version_requested<I, S>(args: I) -> bool
where
    I: IntoIterator<Item = S>,
    S: AsRef<str>,
{
    args.into_iter()
        .any(|arg| matches!(arg.as_ref(), "--version" | "-V"))
}

fn build_ui(app: &adw::Application) {
    install_css();

    let window = adw::ApplicationWindow::builder()
        .application(app)
        .title("Ocula")
        .default_width(480)
        .default_height(620)
        .build();
    window.add_css_class("ocula-window");

    let toolbar = adw::ToolbarView::new();
    let header = adw::HeaderBar::new();
    header.set_title_widget(Some(&adw::WindowTitle::new("Ocula", "Safe screen capture")));
    toolbar.add_top_bar(&header);

    let root = gtk::Box::new(gtk::Orientation::Vertical, 12);
    root.add_css_class("ocula-shell");
    root.set_margin_top(18);
    root.set_margin_bottom(18);
    root.set_margin_start(18);
    root.set_margin_end(18);
    toolbar.set_content(Some(&root));
    window.set_content(Some(&toolbar));

    let hero = gtk::Box::new(gtk::Orientation::Vertical, 6);
    hero.add_css_class("ocula-hero");
    let title = gtk::Label::new(Some("Record without babysitting files"));
    title.add_css_class("title-1");
    title.set_xalign(0.0);
    let subtitle = gtk::Label::new(Some(
        "Pick what to capture, choose MKV or MP4, and Ocula writes a safe file.",
    ));
    subtitle.add_css_class("dim-label");
    subtitle.set_wrap(true);
    subtitle.set_xalign(0.0);
    hero.append(&title);
    hero.append(&subtitle);
    root.append(&hero);

    let controls = card();
    root.append(&controls);

    let mode_label = section_label("Capture");
    controls.append(&mode_label);

    let mode_box = gtk::Box::new(gtk::Orientation::Horizontal, 8);
    mode_box.add_css_class("linked");
    let screen_button = mode_button("Screen");
    let window_button = mode_button("Window");
    let area_button = mode_button("Area");
    window_button.set_group(Some(&screen_button));
    area_button.set_group(Some(&screen_button));
    screen_button.set_active(true);
    mode_box.append(&screen_button);
    mode_box.append(&window_button);
    mode_box.append(&area_button);
    controls.append(&mode_box);

    let area_row = gtk::Box::new(gtk::Orientation::Horizontal, 12);
    area_row.add_css_class("ocula-area-row");
    area_row.set_valign(gtk::Align::Center);
    let area_label = gtk::Label::new(Some("No area selected"));
    area_label.set_xalign(0.0);
    area_label.set_hexpand(true);
    area_label.add_css_class("dim-label");
    let select_area_button = gtk::Button::with_label("Pick Crop");
    select_area_button.add_css_class("ocula-area-select-button");
    area_row.append(&area_label);
    area_row.append(&select_area_button);
    area_row.set_visible(false);
    controls.append(&area_row);

    let selected_area = Rc::new(RefCell::new(None));
    let preview_snapshot = Rc::new(RefCell::new(None));
    let crop_overlay = Rc::new(RefCell::new(None));
    let crop_overlay_failed = Rc::new(Cell::new(None));
    let area_preview = gtk::DrawingArea::builder()
        .height_request(164)
        .hexpand(true)
        .build();
    area_preview.add_css_class("ocula-area-preview");
    area_preview.set_focusable(true);
    area_preview.set_cursor_from_name(Some("pointer"));
    area_preview.set_visible(false);
    controls.append(&area_preview);

    let audio_dropdown = gtk::DropDown::from_strings(&[
        "No audio",
        "Microphone",
        "System audio",
        "Microphone + system",
    ]);
    audio_dropdown.set_hexpand(true);
    controls.append(&setting_row("Audio", &audio_dropdown));

    let hide_pointer_switch = gtk::Switch::builder().valign(gtk::Align::Center).build();
    controls.append(&setting_row("Hide Pointer", &hide_pointer_switch));

    let format_dropdown = gtk::DropDown::from_strings(&[
        "MKV - fastest/safest",
        "MP4 - compatible",
        "GIF - short/no audio",
    ]);
    format_dropdown.set_selected(1);
    format_dropdown.set_hexpand(true);
    controls.append(&setting_row("Format", &format_dropdown));

    let encoder_dropdown =
        gtk::DropDown::from_strings(&["Auto encoder", "Prefer GPU H.264", "Software H.264"]);
    encoder_dropdown.set_hexpand(true);
    controls.append(&setting_row("Encoder", &encoder_dropdown));

    let effect_dropdown = gtk::DropDown::from_strings(&[
        "No effect",
        "Neon edges",
        "Heatmap glow",
        "Glitch prism",
        "Water ripple",
        "Glass refraction",
        "CRT warp",
        "Hologram",
        "Vapor wave",
        "Fracture glass",
        "Prism glass",
        "Aurora veil",
        "Kaleidoscope",
    ]);
    effect_dropdown.set_hexpand(true);
    controls.append(&setting_row("GLSL Effect", &effect_dropdown));
    install_area_preview_draw(
        &area_preview,
        Rc::clone(&selected_area),
        Rc::clone(&preview_snapshot),
        effect_dropdown.clone(),
    );
    install_area_preview_animation(&area_preview);

    let output_box = gtk::Box::new(gtk::Orientation::Horizontal, 8);
    let output_entry = gtk::Entry::builder()
        .hexpand(true)
        .placeholder_text(default_output_hint(OutputFormatChoice::Mp4))
        .build();
    let browse_button = gtk::Button::with_label("Choose...");
    output_box.append(&output_entry);
    output_box.append(&browse_button);
    controls.append(&setting_row("Output", &output_box));

    let record_button = gtk::Button::with_label("Start Recording");
    record_button.add_css_class("suggested-action");
    record_button.add_css_class("ocula-record-button");
    root.append(&record_button);

    let status_label = gtk::Label::new(Some("Ready"));
    status_label.add_css_class("dim-label");
    status_label.set_xalign(0.0);
    root.append(&status_label);

    let log_buffer = gtk::TextBuffer::new(None);
    let log_view = gtk::TextView::builder()
        .buffer(&log_buffer)
        .editable(false)
        .cursor_visible(false)
        .monospace(true)
        .wrap_mode(gtk::WrapMode::WordChar)
        .build();
    log_view.add_css_class("ocula-log");
    let log_scroller = gtk::ScrolledWindow::builder()
        .min_content_height(132)
        .vexpand(true)
        .child(&log_view)
        .build();
    root.append(&log_scroller);

    let (log_tx, log_rx) = mpsc::sync_channel(LOG_CHANNEL_CAPACITY);
    let ui = Ui {
        window: window.clone(),
        controls_card: controls.clone(),
        screen_button,
        window_button,
        area_button,
        audio_dropdown,
        hide_pointer_switch,
        area_row,
        select_area_button,
        area_label,
        area_preview,
        format_dropdown,
        encoder_dropdown,
        effect_dropdown,
        output_entry,
        record_button,
        status_label,
        log_buffer,
        selected_area,
        preview_snapshot,
        crop_overlay,
        crop_overlay_failed,
        child: Rc::new(RefCell::new(None)),
        stop_started_at: Rc::new(RefCell::new(None)),
        force_stop_started_at: Rc::new(RefCell::new(None)),
        kill_sent: Rc::new(Cell::new(false)),
        close_after_child_exit: Rc::new(Cell::new(false)),
        window_destroyed: Rc::new(Cell::new(false)),
        log_tx,
        log_rx: Rc::new(RefCell::new(log_rx)),
    };

    connect_mode_buttons(&ui);
    connect_area_selector(&ui);
    connect_area_preview_selector(&ui);
    connect_format_picker(&ui);
    connect_effect_picker(&ui);
    connect_output_chooser(&ui, &browse_button);
    connect_record_button(&ui);
    connect_window_shutdown(&ui);
    install_child_poll(&ui);
    update_area_controls(&ui);
    update_format_controls(&ui);

    window.present();
}

fn install_css() {
    let provider = gtk::CssProvider::new();
    provider.load_from_string(
        ".ocula-window { background: alpha(@window_bg_color, 0.92); }\n\
         .ocula-shell { background: linear-gradient(145deg, alpha(@accent_color, 0.08), alpha(@window_bg_color, 0.00) 42%, alpha(#7b2cff, 0.08)); }\n\
         .ocula-hero { padding: 0 2px 2px 2px; }\n\
         .ocula-card { padding: 16px; border-radius: 20px; background: alpha(@card_bg_color, 0.62); }\n\
         .ocula-card { border: 1px solid alpha(@accent_color, 0.34); box-shadow: 0 18px 46px rgba(0,0,0,0.22); }\n\
         .ocula-card.ocula-area-ready { border-color: alpha(#00e0a4, 0.74); background: alpha(#00e0a4, 0.08); }\n\
         .ocula-card.ocula-recording { border-color: #ff4f58; background: alpha(#ff4f58, 0.12); }\n\
         .ocula-section-label { font-weight: 760; letter-spacing: 0.04em; opacity: 0.86; }\n\
         .ocula-area-row { padding: 8px 10px; border-radius: 15px; background: alpha(@view_bg_color, 0.46); border: 1px solid alpha(@accent_color, 0.22); }\n\
         .ocula-area-select-button { padding: 7px 13px; border-radius: 999px; font-weight: 760; background: linear-gradient(135deg, alpha(@accent_color, 0.30), alpha(#7b2cff, 0.18)); border: 1px solid alpha(@accent_color, 0.36); box-shadow: 0 8px 20px alpha(@accent_color, 0.18); }\n\
         .ocula-area-select-button.ocula-crop-ready { background: linear-gradient(135deg, alpha(#00e0a4, 0.34), alpha(@accent_color, 0.16)); border-color: alpha(#00e0a4, 0.54); box-shadow: 0 8px 22px alpha(#00e0a4, 0.18); }\n\
         .ocula-record-button { padding: 14px; border-radius: 999px; font-weight: 800; box-shadow: 0 12px 28px alpha(@accent_color, 0.22); }\n\
         .ocula-record-button.ocula-recording { background: #ff4f58; color: white; box-shadow: 0 12px 32px alpha(#ff4f58, 0.34); }\n\
         .ocula-area-selected { color: #00e0a4; font-weight: 760; }\n\
         .ocula-area-preview { border-radius: 18px; background: linear-gradient(135deg, alpha(#06111f, 0.88), alpha(#10152b, 0.78), alpha(#250c32, 0.74)); border: 1px solid alpha(#7df9ff, 0.30); box-shadow: inset 0 1px alpha(#ffffff, 0.12), 0 14px 34px rgba(0,0,0,0.24); }\n\
         .ocula-mode-button { padding: 8px 12px; font-weight: 700; }\n\
         .ocula-log { padding: 9px; font-size: 12px; background: alpha(@view_bg_color, 0.54); border-radius: 13px; border: 1px solid alpha(@accent_color, 0.12); }",
    );

    if let Some(display) = gtk::gdk::Display::default() {
        gtk::style_context_add_provider_for_display(
            &display,
            &provider,
            gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
        );
    }
}

fn card() -> gtk::Box {
    let card = gtk::Box::new(gtk::Orientation::Vertical, 10);
    card.add_css_class("ocula-card");
    card
}

fn section_label(text: &str) -> gtk::Label {
    let label = gtk::Label::new(Some(text));
    label.set_xalign(0.0);
    label.add_css_class("ocula-section-label");
    label
}

fn mode_button(text: &str) -> gtk::ToggleButton {
    let button = gtk::ToggleButton::with_label(text);
    button.set_hexpand(true);
    button.add_css_class("ocula-mode-button");
    button
}

fn setting_row(label: &str, child: &impl IsA<gtk::Widget>) -> gtk::Box {
    let row = gtk::Box::new(gtk::Orientation::Horizontal, 12);
    row.set_valign(gtk::Align::Center);

    let label = gtk::Label::new(Some(label));
    label.set_xalign(0.0);
    label.set_hexpand(true);
    row.append(&label);
    row.append(child);
    row
}

fn install_area_preview_draw(
    preview: &gtk::DrawingArea,
    selected_area: Rc<RefCell<Option<SelectedArea>>>,
    preview_snapshot: Rc<RefCell<Option<PreviewSnapshot>>>,
    effect_dropdown: gtk::DropDown,
) {
    let started_at = Instant::now();
    preview.set_draw_func(move |_, cr, width, height| {
        let selected = *selected_area.borrow();
        let snapshot = preview_snapshot.borrow();
        draw_area_preview(
            cr,
            width,
            height,
            selected,
            snapshot.as_ref(),
            effect_dropdown.selected(),
            started_at.elapsed().as_secs_f64(),
        );
    });
}

fn install_area_preview_animation(preview: &gtk::DrawingArea) {
    let preview = preview.clone();
    glib::timeout_add_local(Duration::from_millis(50), move || {
        if preview.is_visible() {
            preview.queue_draw();
        }
        glib::ControlFlow::Continue
    });
}

fn draw_area_preview(
    cr: &gtk::cairo::Context,
    width: i32,
    height: i32,
    selected: Option<SelectedArea>,
    snapshot: Option<&PreviewSnapshot>,
    effect: u32,
    phase: f64,
) {
    let width = f64::from(width.max(1));
    let height = f64::from(height.max(1));
    let style = preview_effect_style(effect);
    let bg = gtk::cairo::LinearGradient::new(0.0, 0.0, width, height);
    bg.add_color_stop_rgba(0.0, 0.02, 0.04, 0.08, 0.96);
    bg.add_color_stop_rgba(
        0.52,
        style.primary.0 * 0.12,
        style.primary.1 * 0.12,
        (style.primary.2 * 0.26).max(0.10),
        0.90,
    );
    bg.add_color_stop_rgba(
        1.0,
        style.secondary.0 * 0.18,
        style.secondary.1 * 0.08,
        style.secondary.2 * 0.18,
        0.94,
    );
    cr.rectangle(0.0, 0.0, width, height);
    let _ = cr.set_source(&bg);
    let _ = cr.fill();

    draw_preview_grid(cr, width, height, phase, style);
    draw_preview_energy_orbs(cr, width, height, phase, style);
    draw_preview_stage_marks(cr, width, height, phase, style);

    let Some(selected) = selected else {
        draw_preview_empty_state(cr, width, height, effect, phase, style);
        return;
    };

    let has_snapshot = snapshot.is_some();
    let Some(rect) = snapshot
        .and_then(|snapshot| preview_crop_rect(snapshot.width, snapshot.height, width, height))
        .or_else(|| preview_selection_rect(selected, width, height))
    else {
        return;
    };
    draw_preview_aura(cr, rect, phase, style);
    draw_preview_card(cr, rect, phase, style);
    if let Some(snapshot) = snapshot {
        draw_preview_snapshot(cr, rect, snapshot);
    }
    if !has_snapshot || effect != 0 {
        draw_preview_effect_fill(cr, rect, effect, phase, style);
    }
    draw_preview_shader_overlay(cr, rect, effect, phase, style);
    draw_preview_border(cr, rect, phase, style);
    draw_preview_locator(cr, selected, width, height, style);
    draw_preview_chip(
        cr,
        14.0,
        14.0,
        &format!(
            "{}x{} at {},{}",
            selected.area.width, selected.area.height, selected.area.x, selected.area.y
        ),
        style.primary,
    );
    let effect_label = preview_effect_chip_text(effect, style);
    if width > 330.0 {
        draw_preview_chip(
            cr,
            width - preview_chip_width(&effect_label) - 14.0,
            14.0,
            &effect_label,
            style.secondary,
        );
    }
    let hint = if has_snapshot {
        "LIVE CROP  REFINE READY"
    } else {
        "CROP MAP  SNAPSHOT UNAVAILABLE"
    };
    draw_preview_chip(cr, 14.0, height - 34.0, hint, style.tertiary);
}

fn preview_effect_style(effect: u32) -> PreviewEffectStyle {
    match effect {
        1 => PreviewEffectStyle {
            name: "NEON",
            primary: (0.00, 0.92, 1.00),
            secondary: (1.00, 0.10, 0.84),
            tertiary: (0.00, 1.00, 0.68),
        },
        2 => PreviewEffectStyle {
            name: "HEATMAP",
            primary: (1.00, 0.78, 0.06),
            secondary: (1.00, 0.12, 0.00),
            tertiary: (0.18, 0.56, 1.00),
        },
        3 => PreviewEffectStyle {
            name: "GLITCH",
            primary: (0.05, 1.00, 0.82),
            secondary: (1.00, 0.10, 0.42),
            tertiary: (0.74, 0.90, 1.00),
        },
        4 => PreviewEffectStyle {
            name: "RIPPLE",
            primary: (0.18, 0.72, 1.00),
            secondary: (0.18, 1.00, 0.86),
            tertiary: (0.72, 0.92, 1.00),
        },
        5 => PreviewEffectStyle {
            name: "GLASS",
            primary: (0.78, 0.96, 1.00),
            secondary: (0.58, 0.72, 1.00),
            tertiary: (1.00, 1.00, 1.00),
        },
        6 => PreviewEffectStyle {
            name: "CRT",
            primary: (0.18, 1.00, 0.42),
            secondary: (0.00, 0.56, 0.24),
            tertiary: (0.70, 1.00, 0.76),
        },
        7 => PreviewEffectStyle {
            name: "HOLOGRAM",
            primary: (0.28, 0.94, 1.00),
            secondary: (1.00, 0.22, 0.94),
            tertiary: (0.84, 1.00, 1.00),
        },
        8 => PreviewEffectStyle {
            name: "VAPOR",
            primary: (0.26, 0.88, 1.00),
            secondary: (0.90, 0.08, 1.00),
            tertiary: (1.00, 0.58, 0.20),
        },
        9 => PreviewEffectStyle {
            name: "FRACTURE",
            primary: (0.76, 0.96, 1.00),
            secondary: (1.00, 0.16, 0.52),
            tertiary: (1.00, 1.00, 1.00),
        },
        10 => PreviewEffectStyle {
            name: "PRISM",
            primary: (1.00, 0.12, 0.18),
            secondary: (0.00, 0.82, 1.00),
            tertiary: (1.00, 0.88, 0.04),
        },
        11 => PreviewEffectStyle {
            name: "AURORA",
            primary: (0.06, 0.94, 0.54),
            secondary: (0.14, 0.68, 1.00),
            tertiary: (0.74, 0.10, 1.00),
        },
        12 => PreviewEffectStyle {
            name: "KALEIDO",
            primary: (0.00, 0.76, 1.00),
            secondary: (1.00, 0.18, 0.84),
            tertiary: (0.18, 1.00, 0.54),
        },
        _ => PreviewEffectStyle {
            name: "NONE",
            primary: (0.00, 0.88, 0.70),
            secondary: (0.48, 0.78, 1.00),
            tertiary: (0.72, 0.96, 1.00),
        },
    }
}

fn draw_preview_grid(
    cr: &gtk::cairo::Context,
    width: f64,
    height: f64,
    phase: f64,
    style: PreviewEffectStyle,
) {
    cr.set_line_width(1.0);
    set_preview_color(cr, style.tertiary, 0.12);
    let offset = (phase * 12.0) % 18.0;
    let mut x = -offset;
    while x < width {
        cr.move_to(x, 0.0);
        cr.line_to(x + height * 0.16, height);
        x += 18.0;
    }
    let mut y = offset;
    while y < height {
        cr.move_to(0.0, y);
        cr.line_to(width, y - width * 0.08);
        y += 18.0;
    }
    let _ = cr.stroke();
}

fn draw_preview_energy_orbs(
    cr: &gtk::cairo::Context,
    width: f64,
    height: f64,
    phase: f64,
    style: PreviewEffectStyle,
) {
    let _ = cr.save();
    let cx = width * (0.18 + 0.03 * phase.sin());
    let cy = height * (0.20 + 0.04 * (phase * 0.7).cos());
    let orb = gtk::cairo::RadialGradient::new(cx, cy, 4.0, cx, cy, width.max(height) * 0.42);
    orb.add_color_stop_rgba(
        0.0,
        style.secondary.0,
        style.secondary.1,
        style.secondary.2,
        0.24,
    );
    orb.add_color_stop_rgba(1.0, 0.0, 0.0, 0.0, 0.0);
    cr.rectangle(0.0, 0.0, width, height);
    let _ = cr.set_source(&orb);
    let _ = cr.fill();

    let cx = width * (0.78 + 0.02 * (phase * 0.5).cos());
    let cy = height * (0.72 + 0.03 * phase.sin());
    let orb = gtk::cairo::RadialGradient::new(cx, cy, 3.0, cx, cy, width.max(height) * 0.36);
    orb.add_color_stop_rgba(0.0, style.primary.0, style.primary.1, style.primary.2, 0.18);
    orb.add_color_stop_rgba(1.0, 0.0, 0.0, 0.0, 0.0);
    cr.rectangle(0.0, 0.0, width, height);
    let _ = cr.set_source(&orb);
    let _ = cr.fill();
    let _ = cr.restore();
}

fn draw_preview_stage_marks(
    cr: &gtk::cairo::Context,
    width: f64,
    height: f64,
    phase: f64,
    style: PreviewEffectStyle,
) {
    let _ = cr.save();
    set_preview_color(cr, style.primary, 0.16);
    cr.set_line_width(1.0);
    let rail_y = height - 48.0;
    cr.move_to(18.0, rail_y);
    cr.line_to(width - 18.0, rail_y);
    let _ = cr.stroke();

    let pulse_x = 28.0 + ((phase * 54.0) % (width - 56.0).max(1.0));
    let sweep = gtk::cairo::LinearGradient::new(pulse_x - 36.0, rail_y, pulse_x + 36.0, rail_y);
    sweep.add_color_stop_rgba(0.0, style.primary.0, style.primary.1, style.primary.2, 0.0);
    sweep.add_color_stop_rgba(0.5, style.primary.0, style.primary.1, style.primary.2, 0.32);
    sweep.add_color_stop_rgba(1.0, style.primary.0, style.primary.1, style.primary.2, 0.0);
    cr.rectangle(pulse_x - 36.0, rail_y - 1.0, 72.0, 2.0);
    let _ = cr.set_source(&sweep);
    let _ = cr.fill();

    for i in 0..7 {
        let x = 22.0 + f64::from(i) * 22.0;
        set_preview_color(cr, style.tertiary, 0.20);
        cr.rectangle(x, 15.0, 10.0, 1.0);
        let _ = cr.fill();
    }
    let _ = cr.restore();
}

fn draw_preview_empty_state(
    cr: &gtk::cairo::Context,
    width: f64,
    height: f64,
    effect: u32,
    phase: f64,
    style: PreviewEffectStyle,
) {
    let rect = PreviewRect {
        x: width * 0.12,
        y: height * 0.28,
        width: width * 0.76,
        height: height * 0.44,
    };
    draw_preview_aura(cr, rect, phase, style);
    draw_preview_card(cr, rect, phase, style);
    draw_preview_shader_overlay(cr, rect, 7, phase, style);
    draw_preview_border(cr, rect, phase, style);
    draw_preview_chip(
        cr,
        rect.x + 14.0,
        rect.y + rect.height / 2.0 - 12.0,
        "CLICK OR ENTER TO PICK CROP",
        style.tertiary,
    );
    draw_preview_chip(cr, 14.0, 14.0, "AREA MODE", style.primary);
    let effect_label = preview_effect_chip_text(effect, style);
    if width > 330.0 {
        draw_preview_chip(
            cr,
            width - preview_chip_width(&effect_label) - 14.0,
            14.0,
            &effect_label,
            style.secondary,
        );
    }
}

fn draw_preview_effect_fill(
    cr: &gtk::cairo::Context,
    rect: PreviewRect,
    effect: u32,
    phase: f64,
    style: PreviewEffectStyle,
) {
    let _ = cr.save();
    rounded_rectangle(cr, rect, 14.0);
    cr.clip();

    let gradient = gtk::cairo::LinearGradient::new(rect.x, rect.y, rect.right(), rect.bottom());
    match effect {
        1 => {
            gradient.add_color_stop_rgba(0.0, 0.00, 0.90, 1.00, 0.42);
            gradient.add_color_stop_rgba(1.0, 1.00, 0.00, 0.80, 0.42);
        }
        2 => {
            gradient.add_color_stop_rgba(0.0, 0.05, 0.18, 0.95, 0.42);
            gradient.add_color_stop_rgba(0.5, 1.00, 0.88, 0.05, 0.38);
            gradient.add_color_stop_rgba(1.0, 1.00, 0.10, 0.00, 0.42);
        }
        3 => {
            gradient.add_color_stop_rgba(0.0, 0.15, 1.00, 0.90, 0.38);
            gradient.add_color_stop_rgba(0.48, 1.00, 1.00, 1.00, 0.22);
            gradient.add_color_stop_rgba(1.0, 1.00, 0.08, 0.35, 0.40);
        }
        4 => {
            gradient.add_color_stop_rgba(0.0, 0.02, 0.35, 0.75, 0.44);
            gradient.add_color_stop_rgba(0.5, 0.22, 0.95, 1.00, 0.28);
            gradient.add_color_stop_rgba(1.0, 0.03, 0.10, 0.24, 0.48);
        }
        5 => {
            gradient.add_color_stop_rgba(0.0, 0.82, 0.95, 1.00, 0.36);
            gradient.add_color_stop_rgba(0.5, 1.00, 1.00, 1.00, 0.20);
            gradient.add_color_stop_rgba(1.0, 0.60, 0.72, 1.00, 0.32);
        }
        6 => {
            gradient.add_color_stop_rgba(0.0, 0.05, 0.45, 0.20, 0.44);
            gradient.add_color_stop_rgba(1.0, 0.04, 0.95, 0.58, 0.30);
        }
        7 => {
            gradient.add_color_stop_rgba(0.0, 0.00, 0.90, 1.00, 0.36);
            gradient.add_color_stop_rgba(0.5, 1.00, 0.12, 0.95, 0.28);
            gradient.add_color_stop_rgba(1.0, 0.85, 1.00, 1.00, 0.30);
        }
        8 => {
            gradient.add_color_stop_rgba(0.0, 0.08, 0.02, 0.22, 0.42);
            gradient.add_color_stop_rgba(0.5, 0.28, 0.95, 1.00, 0.30);
            gradient.add_color_stop_rgba(1.0, 0.70, 0.00, 0.88, 0.36);
        }
        9 => {
            gradient.add_color_stop_rgba(0.0, 0.72, 0.96, 1.00, 0.30);
            gradient.add_color_stop_rgba(0.5, 1.00, 1.00, 1.00, 0.22);
            gradient.add_color_stop_rgba(1.0, 1.00, 0.10, 0.55, 0.34);
        }
        10 => {
            gradient.add_color_stop_rgba(0.0, 1.00, 0.10, 0.16, 0.28);
            gradient.add_color_stop_rgba(0.33, 1.00, 0.82, 0.02, 0.24);
            gradient.add_color_stop_rgba(0.66, 0.00, 0.82, 1.00, 0.28);
            gradient.add_color_stop_rgba(1.0, 0.90, 0.12, 1.00, 0.30);
        }
        11 => {
            gradient.add_color_stop_rgba(0.0, 0.04, 0.90, 0.42, 0.32);
            gradient.add_color_stop_rgba(0.45, 0.10, 0.86, 1.00, 0.24);
            gradient.add_color_stop_rgba(1.0, 0.62, 0.05, 1.00, 0.30);
        }
        12 => {
            gradient.add_color_stop_rgba(0.0, 0.00, 0.72, 1.00, 0.30);
            gradient.add_color_stop_rgba(0.50, 1.00, 0.20, 0.88, 0.26);
            gradient.add_color_stop_rgba(1.0, 0.18, 1.00, 0.50, 0.30);
        }
        _ => {
            gradient.add_color_stop_rgba(
                0.0,
                style.primary.0,
                style.primary.1,
                style.primary.2,
                0.28,
            );
            gradient.add_color_stop_rgba(
                0.54,
                style.tertiary.0,
                style.tertiary.1,
                style.tertiary.2,
                0.16,
            );
            gradient.add_color_stop_rgba(
                1.0,
                style.secondary.0,
                style.secondary.1,
                style.secondary.2,
                0.28,
            );
        }
    }

    cr.rectangle(rect.x, rect.y, rect.width, rect.height);
    let _ = cr.set_source(&gradient);
    let _ = cr.fill();

    cr.set_source_rgba(1.0, 1.0, 1.0, 0.18);
    cr.set_line_width(1.0);
    let mut y = rect.y + ((phase * 32.0) % 11.0);
    while y < rect.bottom() {
        cr.move_to(rect.x, y);
        cr.line_to(rect.right(), y + (phase * 2.0).sin() * 2.0);
        y += 11.0;
    }
    let _ = cr.stroke();
    let _ = cr.restore();
}

fn draw_preview_shader_overlay(
    cr: &gtk::cairo::Context,
    rect: PreviewRect,
    effect: u32,
    phase: f64,
    style: PreviewEffectStyle,
) {
    let _ = cr.save();
    rounded_rectangle(cr, rect, 14.0);
    cr.clip();

    let shimmer = gtk::cairo::LinearGradient::new(rect.x, rect.y, rect.right(), rect.bottom());
    shimmer.add_color_stop_rgba(0.0, 1.0, 1.0, 1.0, 0.16);
    shimmer.add_color_stop_rgba(
        0.34,
        style.primary.0,
        style.primary.1,
        style.primary.2,
        0.07,
    );
    shimmer.add_color_stop_rgba(
        0.66,
        style.secondary.0,
        style.secondary.1,
        style.secondary.2,
        0.09,
    );
    shimmer.add_color_stop_rgba(1.0, 1.0, 1.0, 1.0, 0.03);
    cr.rectangle(rect.x, rect.y, rect.width, rect.height);
    let _ = cr.set_source(&shimmer);
    let _ = cr.fill();

    cr.set_line_width(2.0);
    for i in 0..5 {
        let t = f64::from(i) / 5.0;
        let drift = ((phase * 0.55) + f64::from(i) * 0.37).sin() * 12.0;
        let x = rect.x + rect.width * t + drift;
        match (effect + i) % 3 {
            0 => set_preview_color(cr, style.primary, 0.16),
            1 => set_preview_color(cr, style.secondary, 0.13),
            _ => set_preview_color(cr, style.tertiary, 0.14),
        }
        cr.move_to(x, rect.y - 12.0);
        cr.line_to(x + rect.height * 0.32, rect.bottom() + 12.0);
        let _ = cr.stroke();
    }

    cr.set_line_width(1.0);
    cr.set_source_rgba(1.0, 1.0, 1.0, 0.10);
    let mut y = rect.y + ((phase * 28.0) % 8.0);
    while y < rect.bottom() {
        cr.move_to(rect.x, y);
        cr.line_to(rect.right(), y + (phase + y * 0.03).sin());
        y += 8.0;
    }
    let _ = cr.stroke();

    draw_preview_effect_signature(cr, rect, effect, phase, style);

    let _ = cr.restore();
}

fn draw_preview_effect_signature(
    cr: &gtk::cairo::Context,
    rect: PreviewRect,
    effect: u32,
    phase: f64,
    style: PreviewEffectStyle,
) {
    match effect {
        3 | 10 | 12 => draw_preview_prism_signature(cr, rect, phase, style),
        4 => draw_preview_ripple_signature(cr, rect, phase, style),
        6 => draw_preview_crt_signature(cr, rect, phase, style),
        9 => draw_preview_fracture_signature(cr, rect, phase, style),
        11 => draw_preview_aurora_signature(cr, rect, phase, style),
        _ => draw_preview_light_signature(cr, rect, phase, style),
    }
}

fn draw_preview_prism_signature(
    cr: &gtk::cairo::Context,
    rect: PreviewRect,
    phase: f64,
    style: PreviewEffectStyle,
) {
    cr.set_line_width(1.4);
    for i in 0..8 {
        let t = f64::from(i) / 7.0;
        let x = rect.x + rect.width * t;
        let offset = (phase * 18.0 + f64::from(i) * 9.0).sin() * 5.0;
        let color = match i % 3 {
            0 => style.primary,
            1 => style.secondary,
            _ => style.tertiary,
        };
        set_preview_color(cr, color, 0.22);
        cr.move_to(x + offset, rect.y + 8.0);
        cr.line_to(
            rect.x + rect.width * (1.0 - t * 0.08) + offset,
            rect.bottom() - 8.0,
        );
    }
    let _ = cr.stroke();
}

fn draw_preview_ripple_signature(
    cr: &gtk::cairo::Context,
    rect: PreviewRect,
    phase: f64,
    style: PreviewEffectStyle,
) {
    cr.set_line_width(1.2);
    for i in 0..5 {
        let y = rect.y + rect.height * (f64::from(i) + 0.5) / 5.0;
        set_preview_color(cr, style.primary, 0.20);
        let mut x = rect.x;
        cr.move_to(x, y);
        while x <= rect.right() {
            let wave = (phase * 2.0 + x * 0.035 + f64::from(i)).sin() * 4.0;
            cr.line_to(x, y + wave);
            x += 10.0;
        }
    }
    let _ = cr.stroke();
}

fn draw_preview_crt_signature(
    cr: &gtk::cairo::Context,
    rect: PreviewRect,
    phase: f64,
    style: PreviewEffectStyle,
) {
    set_preview_color(cr, style.primary, 0.18);
    let mut y = rect.y + ((phase * 18.0) % 4.0);
    while y < rect.bottom() {
        cr.rectangle(rect.x, y, rect.width, 1.0);
        y += 4.0;
    }
    let _ = cr.fill();
}

fn draw_preview_fracture_signature(
    cr: &gtk::cairo::Context,
    rect: PreviewRect,
    phase: f64,
    style: PreviewEffectStyle,
) {
    cr.set_line_width(1.0);
    for i in 0..7 {
        let anchor = rect.x + rect.width * (f64::from(i) + 1.0) / 8.0;
        let tilt = (phase + f64::from(i) * 0.9).sin() * 18.0;
        set_preview_color(
            cr,
            if i % 2 == 0 {
                style.tertiary
            } else {
                style.secondary
            },
            0.24,
        );
        cr.move_to(anchor, rect.y + 7.0);
        cr.line_to(anchor + tilt, rect.y + rect.height * 0.52);
        cr.line_to(anchor - tilt * 0.4, rect.bottom() - 7.0);
    }
    let _ = cr.stroke();
}

fn draw_preview_aurora_signature(
    cr: &gtk::cairo::Context,
    rect: PreviewRect,
    phase: f64,
    style: PreviewEffectStyle,
) {
    let band = gtk::cairo::LinearGradient::new(rect.x, rect.y, rect.right(), rect.bottom());
    band.add_color_stop_rgba(0.0, style.primary.0, style.primary.1, style.primary.2, 0.00);
    band.add_color_stop_rgba(
        0.42,
        style.primary.0,
        style.primary.1,
        style.primary.2,
        0.18,
    );
    band.add_color_stop_rgba(
        0.70,
        style.tertiary.0,
        style.tertiary.1,
        style.tertiary.2,
        0.15,
    );
    band.add_color_stop_rgba(
        1.0,
        style.secondary.0,
        style.secondary.1,
        style.secondary.2,
        0.00,
    );
    cr.rectangle(rect.x, rect.y + phase.sin() * 4.0, rect.width, rect.height);
    let _ = cr.set_source(&band);
    let _ = cr.fill();
}

fn draw_preview_light_signature(
    cr: &gtk::cairo::Context,
    rect: PreviewRect,
    phase: f64,
    style: PreviewEffectStyle,
) {
    let glint_x = rect.x + rect.width * (0.18 + 0.64 * ((phase * 0.32).sin() + 1.0) / 2.0);
    let glint = gtk::cairo::LinearGradient::new(glint_x - 28.0, rect.y, glint_x + 28.0, rect.y);
    glint.add_color_stop_rgba(
        0.0,
        style.tertiary.0,
        style.tertiary.1,
        style.tertiary.2,
        0.0,
    );
    glint.add_color_stop_rgba(
        0.5,
        style.tertiary.0,
        style.tertiary.1,
        style.tertiary.2,
        0.22,
    );
    glint.add_color_stop_rgba(
        1.0,
        style.tertiary.0,
        style.tertiary.1,
        style.tertiary.2,
        0.0,
    );
    cr.rectangle(glint_x - 28.0, rect.y, 56.0, rect.height);
    let _ = cr.set_source(&glint);
    let _ = cr.fill();
}

fn draw_preview_snapshot(cr: &gtk::cairo::Context, rect: PreviewRect, snapshot: &PreviewSnapshot) {
    let _ = cr.save();
    rounded_rectangle(cr, rect, 14.0);
    cr.clip();
    cr.translate(rect.x, rect.y);
    cr.scale(
        rect.width / f64::from(snapshot.width.max(1)),
        rect.height / f64::from(snapshot.height.max(1)),
    );
    let _ = cr.set_source_surface(&snapshot.surface, 0.0, 0.0);
    let _ = cr.paint();
    let _ = cr.restore();
}

fn draw_preview_aura(
    cr: &gtk::cairo::Context,
    rect: PreviewRect,
    phase: f64,
    style: PreviewEffectStyle,
) {
    let _ = cr.save();
    let cx = rect.x + rect.width / 2.0;
    let cy = rect.y + rect.height / 2.0;
    let radius = rect.width.max(rect.height) * (0.72 + 0.04 * phase.sin().abs());
    let aura = gtk::cairo::RadialGradient::new(cx, cy, 1.0, cx, cy, radius);
    aura.add_color_stop_rgba(0.0, style.primary.0, style.primary.1, style.primary.2, 0.24);
    aura.add_color_stop_rgba(
        0.55,
        style.secondary.0,
        style.secondary.1,
        style.secondary.2,
        0.09,
    );
    aura.add_color_stop_rgba(1.0, 0.0, 0.0, 0.0, 0.0);
    cr.rectangle(
        rect.x - 28.0,
        rect.y - 28.0,
        rect.width + 56.0,
        rect.height + 56.0,
    );
    let _ = cr.set_source(&aura);
    let _ = cr.fill();
    let _ = cr.restore();
}

fn draw_preview_card(
    cr: &gtk::cairo::Context,
    rect: PreviewRect,
    phase: f64,
    style: PreviewEffectStyle,
) {
    let _ = cr.save();
    rounded_rectangle(
        cr,
        PreviewRect {
            x: rect.x - 9.0,
            y: rect.y + 10.0,
            width: rect.width + 18.0,
            height: rect.height + 18.0,
        },
        17.0,
    );
    cr.set_source_rgba(0.0, 0.0, 0.0, 0.40);
    let _ = cr.fill();

    let glass = gtk::cairo::LinearGradient::new(rect.x, rect.y, rect.right(), rect.bottom());
    glass.add_color_stop_rgba(0.0, 1.0, 1.0, 1.0, 0.20);
    glass.add_color_stop_rgba(
        0.24,
        style.tertiary.0,
        style.tertiary.1,
        style.tertiary.2,
        0.10,
    );
    glass.add_color_stop_rgba(
        0.72,
        style.secondary.0 * 0.22,
        style.secondary.1 * 0.22,
        style.secondary.2 * 0.34,
        0.18,
    );
    glass.add_color_stop_rgba(1.0, 0.02, 0.05, 0.12, 0.50);
    rounded_rectangle(cr, rect, 14.0);
    let _ = cr.set_source(&glass);
    let _ = cr.fill();

    let sheen = gtk::cairo::LinearGradient::new(rect.x, rect.y, rect.right(), rect.y);
    sheen.add_color_stop_rgba(0.0, 1.0, 1.0, 1.0, 0.02);
    sheen.add_color_stop_rgba(0.50, 1.0, 1.0, 1.0, 0.18 + 0.04 * phase.sin().abs());
    sheen.add_color_stop_rgba(1.0, 1.0, 1.0, 1.0, 0.02);
    rounded_rectangle(cr, rect.inset(4.0), 10.0);
    let _ = cr.set_source(&sheen);
    let _ = cr.fill();
    let _ = cr.restore();
}

fn draw_preview_border(
    cr: &gtk::cairo::Context,
    rect: PreviewRect,
    phase: f64,
    style: PreviewEffectStyle,
) {
    rounded_rectangle(cr, rect, 14.0);
    cr.set_line_width(2.4);
    set_preview_color(cr, style.tertiary, 0.78);
    let _ = cr.stroke();

    rounded_rectangle(
        cr,
        PreviewRect {
            x: rect.x - 4.0,
            y: rect.y - 4.0,
            width: rect.width + 8.0,
            height: rect.height + 8.0,
        },
        17.0,
    );
    cr.set_line_width(1.0 + phase.sin().abs() * 1.8);
    set_preview_color(cr, style.secondary, 0.50);
    let _ = cr.stroke();

    rounded_rectangle(cr, rect.inset(4.0), 10.0);
    cr.set_line_width(1.0);
    set_preview_color(cr, style.primary, 0.42);
    let _ = cr.stroke();

    draw_preview_corner_brackets(cr, rect, phase, style);
}

fn draw_preview_locator(
    cr: &gtk::cairo::Context,
    selected: SelectedArea,
    width: f64,
    height: f64,
    style: PreviewEffectStyle,
) {
    if selected.screen_width <= 0 || selected.screen_height <= 0 || width < 120.0 || height < 86.0 {
        return;
    }

    let max_width = (width * 0.24).clamp(68.0, 92.0);
    let max_height = 46.0;
    let scale = (max_width / f64::from(selected.screen_width))
        .min(max_height / f64::from(selected.screen_height));
    let map_width = f64::from(selected.screen_width) * scale;
    let map_height = f64::from(selected.screen_height) * scale;
    let x = width - map_width - 14.0;
    let y = height - map_height - 14.0;

    let _ = cr.save();
    rounded_rectangle(
        cr,
        PreviewRect {
            x: x - 5.0,
            y: y - 5.0,
            width: map_width + 10.0,
            height: map_height + 10.0,
        },
        7.0,
    );
    cr.set_source_rgba(0.01, 0.03, 0.06, 0.58);
    let _ = cr.fill();
    cr.rectangle(x, y, map_width, map_height);
    cr.set_source_rgba(1.0, 1.0, 1.0, 0.16);
    let _ = cr.fill();
    cr.rectangle(x, y, map_width, map_height);
    cr.set_line_width(1.0);
    set_preview_color(cr, style.tertiary, 0.62);
    let _ = cr.stroke();

    cr.rectangle(
        x + f64::from(selected.area.x) * scale,
        y + f64::from(selected.area.y) * scale,
        f64::from(selected.area.width).max(1.0) * scale,
        f64::from(selected.area.height).max(1.0) * scale,
    );
    set_preview_color(cr, style.primary, 0.42);
    let _ = cr.fill_preserve();
    set_preview_color(cr, style.primary, 0.94);
    let _ = cr.stroke();
    let _ = cr.restore();
}

fn draw_preview_text(cr: &gtk::cairo::Context, x: f64, y: f64, text: &str) {
    cr.select_font_face(
        "Sans",
        gtk::cairo::FontSlant::Normal,
        gtk::cairo::FontWeight::Bold,
    );
    cr.set_font_size(12.0);
    cr.set_source_rgba(0.0, 0.0, 0.0, 0.48);
    cr.move_to(x + 1.0, y + 1.0);
    let _ = cr.show_text(text);
    cr.set_source_rgba(1.0, 1.0, 1.0, 0.88);
    cr.move_to(x, y);
    let _ = cr.show_text(text);
}

fn draw_preview_corner_brackets(
    cr: &gtk::cairo::Context,
    rect: PreviewRect,
    phase: f64,
    style: PreviewEffectStyle,
) {
    let _ = cr.save();
    let length = rect.width.min(rect.height).clamp(18.0, 34.0);
    let inset = 6.0;
    let pulse = 0.56 + 0.18 * phase.sin().abs();
    cr.set_line_width(2.6);
    set_preview_color(cr, style.primary, pulse);

    for (x, y, sx, sy) in [
        (rect.x + inset, rect.y + inset, 1.0, 1.0),
        (rect.right() - inset, rect.y + inset, -1.0, 1.0),
        (rect.right() - inset, rect.bottom() - inset, -1.0, -1.0),
        (rect.x + inset, rect.bottom() - inset, 1.0, -1.0),
    ] {
        cr.move_to(x, y + sy * length);
        cr.line_to(x, y);
        cr.line_to(x + sx * length, y);
    }
    let _ = cr.stroke();

    cr.set_line_width(1.0);
    set_preview_color(cr, style.tertiary, 0.44);
    for i in 0..4 {
        let t = f64::from(i) / 3.0;
        let x = rect.x + rect.width * t;
        cr.move_to(x, rect.y - 6.0);
        cr.line_to(x, rect.y - 2.0);
        cr.move_to(x, rect.bottom() + 2.0);
        cr.line_to(x, rect.bottom() + 6.0);
    }
    let _ = cr.stroke();
    let _ = cr.restore();
}

fn draw_preview_chip(
    cr: &gtk::cairo::Context,
    x: f64,
    y: f64,
    text: &str,
    accent: (f64, f64, f64),
) {
    let width = preview_chip_width(text);
    let height = 23.0;
    rounded_rectangle(
        cr,
        PreviewRect {
            x,
            y,
            width,
            height,
        },
        8.0,
    );
    cr.set_source_rgba(0.02, 0.04, 0.08, 0.66);
    let _ = cr.fill_preserve();
    cr.set_line_width(1.0);
    set_preview_color(cr, accent, 0.58);
    let _ = cr.stroke();

    rounded_rectangle(
        cr,
        PreviewRect {
            x: x + 5.0,
            y: y + 5.0,
            width: 4.0,
            height: height - 10.0,
        },
        2.0,
    );
    set_preview_color(cr, accent, 0.88);
    let _ = cr.fill();
    draw_preview_text(cr, x + 14.0, y + 16.0, text);
}

fn preview_chip_width(text: &str) -> f64 {
    (text.len() as f64 * 7.0 + 22.0).max(72.0)
}

fn preview_effect_chip_text(effect: u32, style: PreviewEffectStyle) -> String {
    if effect == 0 {
        "FX OFF".to_string()
    } else {
        format!("FX {}", style.name)
    }
}

fn set_preview_color(cr: &gtk::cairo::Context, color: (f64, f64, f64), alpha: f64) {
    cr.set_source_rgba(color.0, color.1, color.2, alpha);
}

fn rounded_rectangle(cr: &gtk::cairo::Context, rect: PreviewRect, radius: f64) {
    let radius = radius.min(rect.width / 2.0).min(rect.height / 2.0).max(0.0);
    cr.new_sub_path();
    cr.arc(
        rect.right() - radius,
        rect.y + radius,
        radius,
        -FRAC_PI_2,
        0.0,
    );
    cr.arc(
        rect.right() - radius,
        rect.bottom() - radius,
        radius,
        0.0,
        FRAC_PI_2,
    );
    cr.arc(
        rect.x + radius,
        rect.bottom() - radius,
        radius,
        FRAC_PI_2,
        PI,
    );
    cr.arc(rect.x + radius, rect.y + radius, radius, PI, PI + FRAC_PI_2);
    cr.close_path();
}

#[derive(Debug, Clone, Copy, PartialEq)]
struct PreviewRect {
    x: f64,
    y: f64,
    width: f64,
    height: f64,
}

impl PreviewRect {
    fn right(self) -> f64 {
        self.x + self.width
    }

    fn bottom(self) -> f64 {
        self.y + self.height
    }

    fn inset(self, amount: f64) -> Self {
        let amount = amount.max(0.0).min(self.width / 2.0).min(self.height / 2.0);
        Self {
            x: self.x + amount,
            y: self.y + amount,
            width: (self.width - amount * 2.0).max(1.0),
            height: (self.height - amount * 2.0).max(1.0),
        }
    }
}

fn preview_selection_rect(selected: SelectedArea, width: f64, height: f64) -> Option<PreviewRect> {
    if selected.screen_width <= 0 || selected.screen_height <= 0 || width <= 0.0 || height <= 0.0 {
        return None;
    }

    let margin = 14.0;
    let available_width = (width - margin * 2.0).max(1.0);
    let available_height = (height - margin * 2.0).max(1.0);
    let scale = (available_width / f64::from(selected.screen_width))
        .min(available_height / f64::from(selected.screen_height));
    let screen_width = f64::from(selected.screen_width) * scale;
    let screen_height = f64::from(selected.screen_height) * scale;
    let origin_x = (width - screen_width) / 2.0;
    let origin_y = (height - screen_height) / 2.0;

    Some(PreviewRect {
        x: origin_x + f64::from(selected.area.x) * scale,
        y: origin_y + f64::from(selected.area.y) * scale,
        width: f64::from(selected.area.width) * scale,
        height: f64::from(selected.area.height) * scale,
    })
}

fn preview_crop_rect(
    content_width: i32,
    content_height: i32,
    width: f64,
    height: f64,
) -> Option<PreviewRect> {
    if content_width <= 0 || content_height <= 0 || width <= 0.0 || height <= 0.0 {
        return None;
    }

    let margin = 16.0;
    let available_width = (width - margin * 2.0).max(1.0);
    let available_height = (height - margin * 2.0).max(1.0);
    let scale = (available_width / f64::from(content_width))
        .min(available_height / f64::from(content_height));
    let crop_width = f64::from(content_width) * scale;
    let crop_height = f64::from(content_height) * scale;

    Some(PreviewRect {
        x: (width - crop_width) / 2.0,
        y: (height - crop_height) / 2.0,
        width: crop_width,
        height: crop_height,
    })
}

fn capture_area_snapshot(selected: SelectedArea) -> Result<PreviewSnapshot> {
    let (conn, screen_num) = connect(None).context("failed to connect to X11 for area preview")?;
    let setup = conn.setup();
    let screen = &setup.roots[screen_num];
    let screen_width = i32::from(screen.width_in_pixels);
    let screen_height = i32::from(screen.height_in_pixels);
    let x = selected.area.x.clamp(0, screen_width.saturating_sub(1));
    let y = selected.area.y.clamp(0, screen_height.saturating_sub(1));
    let right = selected.area.right().min(screen_width).max(x);
    let bottom = selected.area.bottom().min(screen_height).max(y);
    let width = right - x;
    let height = bottom - y;

    ensure!(width > 0 && height > 0, "selected preview area is empty");
    ensure!(
        i64::from(width) * i64::from(height) <= PREVIEW_CAPTURE_MAX_PIXELS,
        "selected area is too large for a live preview"
    );

    let reply = conn
        .get_image(
            ImageFormat::Z_PIXMAP,
            screen.root,
            i16::try_from(x).context("preview x is outside X11 coordinate range")?,
            i16::try_from(y).context("preview y is outside X11 coordinate range")?,
            u16::try_from(width).context("preview width is outside X11 size range")?,
            u16::try_from(height).context("preview height is outside X11 size range")?,
            u32::MAX,
        )
        .context("failed to request X11 preview image")?
        .reply()
        .context("failed to read X11 preview image")?;
    let format = setup
        .pixmap_formats
        .iter()
        .find(|format| format.depth == reply.depth)
        .context("X11 preview format is unsupported")?;
    let visual = find_visual(screen, reply.visual)
        .or_else(|| find_visual(screen, screen.root_visual))
        .context("X11 preview visual is unsupported")?;
    let source_stride = x11_image_stride(width, format.bits_per_pixel, format.scanline_pad)
        .context("X11 preview stride is unsupported")?;
    let bytes_per_pixel = usize::from(format.bits_per_pixel).div_ceil(8);
    ensure!(
        bytes_per_pixel > 0 && bytes_per_pixel <= 4,
        "X11 preview pixel size is unsupported"
    );
    ensure!(
        reply.data.len() >= source_stride.saturating_mul(usize::try_from(height)?),
        "X11 preview image data is truncated"
    );

    let mut surface = gtk::cairo::ImageSurface::create(gtk::cairo::Format::Rgb24, width, height)
        .context("failed to allocate preview surface")?;
    let target_stride = usize::try_from(surface.stride()).context("preview stride is invalid")?;
    {
        let mut data = surface
            .data()
            .context("failed to access preview surface data")?;
        copy_x11_image_to_cairo(
            &reply.data,
            source_stride,
            bytes_per_pixel,
            setup.image_byte_order == ImageOrder::LSB_FIRST,
            visual,
            width,
            height,
            &mut data,
            target_stride,
        );
    }
    surface.mark_dirty();

    Ok(PreviewSnapshot {
        surface,
        width,
        height,
    })
}

fn find_visual(screen: &Screen, visual_id: Visualid) -> Option<Visualtype> {
    screen
        .allowed_depths
        .iter()
        .flat_map(|depth| depth.visuals.iter())
        .copied()
        .find(|visual| visual.visual_id == visual_id)
}

fn x11_image_stride(width: i32, bits_per_pixel: u8, scanline_pad: u8) -> Option<usize> {
    let width = usize::try_from(width).ok()?;
    let bits_per_pixel = usize::from(bits_per_pixel);
    let scanline_pad = usize::from(scanline_pad);
    if width == 0 || bits_per_pixel == 0 || scanline_pad == 0 {
        return None;
    }

    let bits = width.checked_mul(bits_per_pixel)?;
    let padded_bits = bits.checked_add(scanline_pad - 1)? / scanline_pad * scanline_pad;
    Some(padded_bits / 8)
}

#[allow(clippy::too_many_arguments)]
fn copy_x11_image_to_cairo(
    source: &[u8],
    source_stride: usize,
    bytes_per_pixel: usize,
    lsb_first: bool,
    visual: Visualtype,
    width: i32,
    height: i32,
    target: &mut [u8],
    target_stride: usize,
) {
    let width = usize::try_from(width).unwrap_or(0);
    let height = usize::try_from(height).unwrap_or(0);
    for y in 0..height {
        let source_row = y * source_stride;
        let target_row = y * target_stride;
        for x in 0..width {
            let source_offset = source_row + x * bytes_per_pixel;
            let target_offset = target_row + x * 4;
            let pixel = read_x11_pixel(source, source_offset, bytes_per_pixel, lsb_first);
            let red = extract_x11_channel(pixel, visual.red_mask);
            let green = extract_x11_channel(pixel, visual.green_mask);
            let blue = extract_x11_channel(pixel, visual.blue_mask);
            write_cairo_rgb24_pixel(target, target_offset, red, green, blue);
        }
    }
}

fn read_x11_pixel(source: &[u8], offset: usize, bytes_per_pixel: usize, lsb_first: bool) -> u32 {
    let mut pixel = 0_u32;
    for i in 0..bytes_per_pixel {
        let byte = u32::from(*source.get(offset + i).unwrap_or(&0));
        if lsb_first {
            pixel |= byte << (i * 8);
        } else {
            pixel = (pixel << 8) | byte;
        }
    }
    pixel
}

fn extract_x11_channel(pixel: u32, mask: u32) -> u8 {
    if mask == 0 {
        return 0;
    }

    let shift = mask.trailing_zeros();
    let bits = mask.count_ones();
    let value = (pixel & mask) >> shift;
    let max = if bits >= u32::BITS {
        u32::MAX
    } else {
        (1_u32 << bits).saturating_sub(1)
    }
    .max(1);
    ((value * 255 + max / 2) / max) as u8
}

fn write_cairo_rgb24_pixel(target: &mut [u8], offset: usize, red: u8, green: u8, blue: u8) {
    if cfg!(target_endian = "little") {
        target[offset] = blue;
        target[offset + 1] = green;
        target[offset + 2] = red;
        target[offset + 3] = 0;
    } else {
        target[offset] = 0;
        target[offset + 1] = red;
        target[offset + 2] = green;
        target[offset + 3] = blue;
    }
}

fn create_crop_overlay(selected: SelectedArea) -> Result<CropOverlay> {
    let (conn, screen_num) = connect(None).context("failed to connect to X11 for crop overlay")?;
    let screen = &conn.setup().roots[screen_num];
    let screen_width = i32::from(screen.width_in_pixels);
    let screen_height = i32::from(screen.height_in_pixels);
    let area = clamp_overlay_area(selected.area, screen_width, screen_height)
        .context("selected crop is outside the current X11 screen")?;
    let glow_rectangles =
        crop_overlay_rectangles(area, screen_width, screen_height, CROP_OVERLAY_THICKNESS);
    let edge_rectangles = crop_overlay_rectangles(
        area,
        screen_width,
        screen_height,
        CROP_OVERLAY_EDGE_THICKNESS,
    );
    ensure!(
        !glow_rectangles.is_empty(),
        "crop touches every screen edge, so no outside overlay can be shown"
    );

    let opacity_atom = conn
        .intern_atom(false, b"_NET_WM_WINDOW_OPACITY")?
        .reply()?
        .atom;
    let glow_pixel = x11_color_pixel(
        &conn,
        screen.default_colormap,
        b"#00d5ff",
        screen.white_pixel,
    );
    let edge_pixel = x11_color_pixel(
        &conn,
        screen.default_colormap,
        b"#00e0a4",
        screen.white_pixel,
    );
    let mut windows = Vec::with_capacity(2);
    let result = (|| -> Result<()> {
        windows.push(create_crop_overlay_window(
            &conn,
            screen,
            opacity_atom,
            &glow_rectangles,
            glow_pixel,
            b"Ocula Crop Outline",
        )?);

        if !edge_rectangles.is_empty() {
            windows.push(create_crop_overlay_window(
                &conn,
                screen,
                opacity_atom,
                &edge_rectangles,
                edge_pixel,
                b"Ocula Crop Edge",
            )?);
        }

        conn.flush()?;
        Ok(())
    })();
    if let Err(err) = result {
        destroy_crop_overlay_windows(&conn, &windows);
        return Err(err);
    }

    Ok(CropOverlay {
        conn,
        windows,
        selected,
    })
}

fn create_crop_overlay_window(
    conn: &RustConnection,
    screen: &Screen,
    opacity_atom: u32,
    rectangles: &[Rectangle],
    color: u32,
    name: &[u8],
) -> Result<Window> {
    let window = conn.generate_id()?;
    let result = (|| -> Result<()> {
        conn.create_window(
            COPY_DEPTH_FROM_PARENT,
            window,
            screen.root,
            0,
            0,
            screen.width_in_pixels,
            screen.height_in_pixels,
            0,
            WindowClass::INPUT_OUTPUT,
            0,
            &CreateWindowAux::new()
                .background_pixel(color)
                .override_redirect(1),
        )?
        .check()?;
        conn.change_property32(
            PropMode::REPLACE,
            window,
            opacity_atom,
            AtomEnum::CARDINAL,
            &[CROP_OVERLAY_OPACITY],
        )?
        .check()?;
        conn.change_property8(
            PropMode::REPLACE,
            window,
            AtomEnum::WM_NAME,
            AtomEnum::STRING,
            name,
        )?
        .check()?;

        // Visible shape is only outside the crop; input shape is empty so clicks pass through.
        conn.shape_rectangles(
            SO::SET,
            SK::BOUNDING,
            ClipOrdering::UNSORTED,
            window,
            0,
            0,
            rectangles,
        )?
        .check()?;
        conn.shape_rectangles(
            SO::SET,
            SK::INPUT,
            ClipOrdering::UNSORTED,
            window,
            0,
            0,
            &[],
        )?
        .check()?;

        conn.map_window(window)?.check()?;
        conn.configure_window(
            window,
            &ConfigureWindowAux::new().stack_mode(StackMode::ABOVE),
        )?
        .check()?;
        Ok(())
    })();
    if let Err(err) = result {
        destroy_crop_overlay_windows(conn, &[window]);
        return Err(err);
    }

    Ok(window)
}

fn destroy_crop_overlay_windows(conn: &RustConnection, windows: &[Window]) {
    for window in windows {
        let _ = conn.destroy_window(*window);
    }
    let _ = conn.flush();
}

fn x11_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)
}

fn clamp_overlay_area(
    area: CaptureArea,
    screen_width: i32,
    screen_height: i32,
) -> Option<CaptureArea> {
    let left = area.x.clamp(0, screen_width.max(0));
    let top = area.y.clamp(0, screen_height.max(0));
    let right = area.right().clamp(0, screen_width.max(0));
    let bottom = area.bottom().clamp(0, screen_height.max(0));

    CaptureArea::new(left, top, right - left, bottom - top).ok()
}

fn crop_overlay_rectangles(
    area: CaptureArea,
    screen_width: i32,
    screen_height: i32,
    thickness: i32,
) -> Vec<Rectangle> {
    let thickness = thickness.max(1);
    let left = area.x;
    let top = area.y;
    let right = area.right();
    let bottom = area.bottom();

    [
        overlay_rect(
            left - thickness,
            top - thickness,
            area.width + thickness * 2,
            thickness,
            screen_width,
            screen_height,
        ),
        overlay_rect(
            left - thickness,
            bottom,
            area.width + thickness * 2,
            thickness,
            screen_width,
            screen_height,
        ),
        overlay_rect(
            left - thickness,
            top,
            thickness,
            area.height,
            screen_width,
            screen_height,
        ),
        overlay_rect(
            right,
            top,
            thickness,
            area.height,
            screen_width,
            screen_height,
        ),
    ]
    .into_iter()
    .flatten()
    .collect()
}

fn overlay_rect(
    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));
    if right <= left || bottom <= top {
        return None;
    }

    Some(Rectangle {
        x: i16::try_from(left).ok()?,
        y: i16::try_from(top).ok()?,
        width: u16::try_from(right - left).ok()?,
        height: u16::try_from(bottom - top).ok()?,
    })
}

fn connect_mode_buttons(ui: &Ui) {
    let ui_for_screen = ui.clone();
    ui.screen_button.clone().connect_toggled(move |button| {
        if button.is_active() {
            update_area_controls(&ui_for_screen);
        }
    });

    let ui_for_window = ui.clone();
    ui.window_button.clone().connect_toggled(move |button| {
        if button.is_active() {
            update_area_controls(&ui_for_window);
        }
    });

    let ui_for_area = ui.clone();
    ui.area_button.clone().connect_toggled(move |button| {
        if button.is_active() {
            update_area_controls(&ui_for_area);
        }
    });
}

fn connect_area_selector(ui: &Ui) {
    let ui = ui.clone();
    ui.select_area_button.clone().connect_clicked(move |_| {
        open_area_selector(&ui);
    });
}

fn connect_area_preview_selector(ui: &Ui) {
    let click = gtk::GestureClick::new();
    click.set_button(1);
    let ui_for_preview = ui.clone();
    click.connect_released(move |_, _, _, _| {
        if area_preview_can_open_selector(&ui_for_preview) {
            open_area_selector(&ui_for_preview);
        }
    });
    ui.area_preview.add_controller(click);

    let key = gtk::EventControllerKey::new();
    let ui_for_key = ui.clone();
    key.connect_key_pressed(move |_, key, _, _| {
        let activates = matches!(
            key,
            gtk::gdk::Key::Return | gtk::gdk::Key::KP_Enter | gtk::gdk::Key::space
        );
        if activates && area_preview_can_open_selector(&ui_for_key) {
            open_area_selector(&ui_for_key);
            glib::Propagation::Stop
        } else {
            glib::Propagation::Proceed
        }
    });
    ui.area_preview.add_controller(key);
}

fn area_preview_can_open_selector(ui: &Ui) -> bool {
    selected_mode(ui) == Mode::Area && ui.child.borrow().is_none()
}

fn open_area_selector(ui: &Ui) {
    let previous = *ui.selected_area.borrow();
    ui.crop_overlay.borrow_mut().take();
    ui.crop_overlay_failed.set(None);
    if let Some(selected) = previous {
        ui.status_label
            .set_text("Move or resize the area, then confirm");
        append_log(
            &ui.log_buffer,
            &format!(
                "Opening adjustable area selector at {}",
                area_label(selected)
            ),
        );
    } else {
        ui.status_label
            .set_text("Drag an area, refine it, then confirm");
        append_log(&ui.log_buffer, "Opening studio area selector");
    }
    ui.window.set_visible(false);

    while glib::MainContext::default().iteration(false) {}

    let selection = if let Some(selected) = previous {
        selector::adjust_area(selected.area)
    } else {
        selector::select_area()
    };

    match selection {
        Ok(selection) => {
            let selected = SelectedArea {
                area: selection.area,
                screen_width: selection.screen_width,
                screen_height: selection.screen_height,
            };
            match capture_area_snapshot(selected) {
                Ok(snapshot) => {
                    ui.preview_snapshot.replace(Some(snapshot));
                    append_log(&ui.log_buffer, "Captured selected-area preview");
                }
                Err(err) => {
                    ui.preview_snapshot.replace(None);
                    append_log(
                        &ui.log_buffer,
                        &format!("Live area preview unavailable: {err:#}"),
                    );
                }
            }
            ui.selected_area.replace(Some(selected));
            ui.area_preview.queue_draw();
            ui.status_label.set_text("Area selected");
            append_log(
                &ui.log_buffer,
                &format!("Selected {}", area_label(selected)),
            );
        }
        Err(err) => {
            ui.status_label.set_text("Area selection cancelled");
            append_log(&ui.log_buffer, &format!("Area selection failed: {err:#}"));
        }
    }

    ui.window.present();
    update_area_controls(ui);
}

fn update_area_controls(ui: &Ui) {
    let is_area = selected_mode(ui) == Mode::Area;
    let is_recording = ui.child.borrow().is_some();
    let has_area = ui.selected_area.borrow().is_some();
    ui.area_row.set_visible(is_area);
    ui.area_preview.set_visible(is_area);

    if let Some(selected) = *ui.selected_area.borrow() {
        ui.area_label.set_text(&area_label(selected));
        ui.area_label.remove_css_class("dim-label");
        ui.area_label.add_css_class("ocula-area-selected");
        ui.select_area_button.set_label("Refine Crop");
        ui.select_area_button.add_css_class("ocula-crop-ready");
        ui.area_preview.queue_draw();
    } else {
        ui.area_label.set_text("No area selected");
        ui.area_label.add_css_class("dim-label");
        ui.area_label.remove_css_class("ocula-area-selected");
        ui.select_area_button.set_label("Pick Crop");
        ui.select_area_button.remove_css_class("ocula-crop-ready");
    }

    let preview_tooltip = if is_recording {
        "Crop controls are locked while recording"
    } else if has_area {
        "Click or press Enter to refine the selected crop"
    } else {
        "Click or press Enter to pick a recording crop"
    };
    ui.area_preview.set_tooltip_text(Some(preview_tooltip));

    if is_area {
        ui.area_preview.queue_draw();
    }

    ui.controls_card.remove_css_class("ocula-area-ready");
    ui.controls_card.remove_css_class("ocula-recording");
    ui.record_button.remove_css_class("ocula-recording");

    if is_recording {
        ui.controls_card.add_css_class("ocula-recording");
        ui.record_button.add_css_class("ocula-recording");
    } else if is_area && has_area {
        ui.controls_card.add_css_class("ocula-area-ready");
    }

    sync_crop_overlay(ui);

    if !is_recording {
        ui.record_button.set_sensitive(!is_area || has_area);
        ui.select_area_button.set_sensitive(true);
    } else {
        ui.record_button.set_sensitive(true);
        ui.select_area_button.set_sensitive(false);
    }
}

fn sync_crop_overlay(ui: &Ui) {
    let desired = if selected_mode(ui) == Mode::Area {
        *ui.selected_area.borrow()
    } else {
        None
    };

    if let Some(selected) = desired {
        if ui
            .crop_overlay
            .borrow()
            .as_ref()
            .is_some_and(|overlay| overlay.selected == selected)
        {
            return;
        }

        ui.crop_overlay.borrow_mut().take();
        if ui.crop_overlay_failed.get() == Some(selected) {
            return;
        }

        match create_crop_overlay(selected) {
            Ok(overlay) => {
                ui.crop_overlay_failed.set(None);
                ui.crop_overlay.replace(Some(overlay));
            }
            Err(err) => {
                ui.crop_overlay_failed.set(Some(selected));
                append_log(
                    &ui.log_buffer,
                    &format!("Crop outline unavailable: {err:#}"),
                );
            }
        }
    } else {
        ui.crop_overlay.borrow_mut().take();
        ui.crop_overlay_failed.set(None);
    }
}

fn connect_format_picker(ui: &Ui) {
    let ui = ui.clone();
    ui.format_dropdown
        .clone()
        .connect_selected_notify(move |_| {
            update_format_controls(&ui);
            ui.output_entry
                .set_placeholder_text(Some(&default_output_hint(selected_output_format(&ui))));
            normalize_output_extension(&ui);
        });
}

fn connect_effect_picker(ui: &Ui) {
    let preview = ui.area_preview.clone();
    ui.effect_dropdown
        .clone()
        .connect_selected_notify(move |_| preview.queue_draw());
}

fn update_format_controls(ui: &Ui) {
    if selected_output_format(ui) == OutputFormatChoice::Gif {
        ui.audio_dropdown.set_selected(0);
        ui.audio_dropdown.set_sensitive(false);
        ui.encoder_dropdown.set_sensitive(false);
        ui.status_label
            .set_text("GIF records video only; audio is disabled");
    } else {
        ui.audio_dropdown.set_sensitive(true);
        ui.encoder_dropdown.set_sensitive(true);
        if ui.child.borrow().is_none() {
            ui.status_label.set_text("Ready");
        }
    }
}

fn normalize_output_extension(ui: &Ui) {
    let output = ui.output_entry.text().trim().to_string();
    if output.is_empty() {
        return;
    }

    let mut path = PathBuf::from(output);
    let extension = path.extension().and_then(|extension| extension.to_str());
    if extension.is_none()
        || extension.is_some_and(|extension| {
            ["mkv", "mp4", "gif"]
                .iter()
                .any(|known| extension.eq_ignore_ascii_case(known))
        })
    {
        path.set_extension(selected_output_format(ui).extension());
        ui.output_entry.set_text(&path.to_string_lossy());
    }
}

fn connect_output_chooser(ui: &Ui, browse_button: &gtk::Button) {
    let ui = ui.clone();
    browse_button.connect_clicked(move |_| {
        let ui = ui.clone();
        glib::spawn_future_local(async move {
            let folder = gio::File::for_path(default_output_dir());
            let dialog = gtk::FileDialog::builder()
                .title("Save Recording")
                .initial_folder(&folder)
                .initial_name(format!(
                    "Recording.{}",
                    selected_output_format(&ui).extension()
                ))
                .modal(true)
                .build();

            match dialog.save_future(Some(&ui.window)).await {
                Ok(file) => {
                    if let Some(path) = file.path() {
                        ui.output_entry.set_text(&path.to_string_lossy());
                    }
                }
                Err(err) if err.matches(gtk::DialogError::Dismissed) => {}
                Err(err) => append_log(&ui.log_buffer, &format!("Output chooser failed: {err}")),
            }
        });
    });
}

fn connect_record_button(ui: &Ui) {
    let ui = ui.clone();
    ui.record_button.clone().connect_clicked(move |_| {
        if ui.child.borrow().is_some() {
            if ui.stop_started_at.borrow().is_some() {
                force_stop_recording(&ui);
            } else {
                request_graceful_stop(&ui, false);
            }
        } else {
            start_recording(&ui);
        }
    });
}

fn connect_window_shutdown(ui: &Ui) {
    let ui_for_close = ui.clone();
    ui.window.clone().connect_close_request(move |_| {
        if ui_for_close.child.borrow().is_some() {
            ui_for_close.close_after_child_exit.set(true);
            request_graceful_stop(&ui_for_close, true);
            glib::Propagation::Stop
        } else {
            glib::Propagation::Proceed
        }
    });

    let destroyed = Rc::clone(&ui.window_destroyed);
    ui.window.connect_destroy(move |_| {
        destroyed.set(true);
    });
}

fn install_child_poll(ui: &Ui) {
    let ui = ui.clone();
    glib::timeout_add_local(Duration::from_millis(100), move || {
        for _ in 0..LOG_DRAIN_LINES_PER_TICK {
            let Ok(line) = ui.log_rx.borrow().try_recv() else {
                break;
            };
            append_log(&ui.log_buffer, &line);
        }

        let status = {
            let mut child_ref = ui.child.borrow_mut();
            match child_ref.as_mut() {
                Some(child) => child.try_wait().transpose(),
                None => None,
            }
        };

        if let Some(result) = status {
            ui.child.borrow_mut().take();
            reset_stop_state(&ui);
            reset_record_button(&ui);

            match result {
                Ok(exit) if exit.success() => ui.status_label.set_text("Recording saved"),
                Ok(exit) => ui
                    .status_label
                    .set_text(&format!("Recorder exited with {exit}")),
                Err(err) => ui
                    .status_label
                    .set_text(&format!("Failed to inspect recorder: {err}")),
            }
            update_area_controls(&ui);
            if ui.close_after_child_exit.replace(false) {
                ui.window.close();
            }
        } else {
            enforce_stop_deadlines(&ui);
        }

        if ui.window_destroyed.get() && ui.child.borrow().is_none() {
            return glib::ControlFlow::Break;
        }

        glib::ControlFlow::Continue
    });
}

fn start_recording(ui: &Ui) {
    if selected_mode(ui) == Mode::Area && ui.selected_area.borrow().is_none() {
        ui.status_label.set_text("Select an area before recording");
        append_log(&ui.log_buffer, "Area mode requires selecting an area first");
        update_area_controls(ui);
        return;
    }

    normalize_output_extension(ui);
    let args = recorder_args(ui);
    let command_path = recorder_command_path();
    append_log(
        &ui.log_buffer,
        &format!("Starting: {} {}", command_path.display(), args.join(" ")),
    );

    let mut command = Command::new(&command_path);
    command
        .args(&args)
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped());
    configure_recorder_command(&mut command);

    match command.spawn() {
        Ok(mut child) => {
            if let Some(stdout) = child.stdout.take() {
                spawn_log_reader(stdout, ui.log_tx.clone());
            }
            if let Some(stderr) = child.stderr.take() {
                spawn_log_reader(stderr, ui.log_tx.clone());
            }

            ui.child.replace(Some(child));
            reset_stop_state(ui);
            ui.close_after_child_exit.set(false);
            ui.status_label
                .set_text("Recording... press Stop to finish safely");
            ui.record_button.set_label("Stop Recording");
            ui.record_button.set_sensitive(true);
            ui.record_button.remove_css_class("suggested-action");
            ui.record_button.add_css_class("destructive-action");
            update_area_controls(ui);
        }
        Err(err) => {
            let message = format!(
                "Could not start recorder `{}`: {err}",
                command_path.display()
            );
            ui.status_label.set_text("Could not start recorder");
            append_log(&ui.log_buffer, &message);
        }
    }
}

fn request_graceful_stop(ui: &Ui, close_after_stop: bool) {
    if ui.stop_started_at.borrow().is_some() {
        return;
    }

    let result = ui
        .child
        .borrow()
        .as_ref()
        .map(|child| interrupt_recorder_process(child.id()));

    match result {
        Some(Ok(())) => {
            ui.stop_started_at.replace(Some(Instant::now()));
            ui.close_after_child_exit.set(close_after_stop);
            if close_after_stop {
                ui.status_label
                    .set_text("Stopping safely before closing...");
            } else {
                ui.status_label
                    .set_text("Stopping safely... click Force Stop if it hangs");
            }
            ui.record_button.set_label("Force Stop");
            ui.record_button.set_sensitive(true);
        }
        Some(Err(err)) => append_log(&ui.log_buffer, &format!("Failed to stop recorder: {err}")),
        None => {}
    }
}

fn force_stop_recording(ui: &Ui) {
    if ui.force_stop_started_at.borrow().is_some() {
        return;
    }

    let result = ui
        .child
        .borrow()
        .as_ref()
        .map(|child| interrupt_recorder_process(child.id()));

    match result {
        Some(Ok(())) => {
            ui.force_stop_started_at.replace(Some(Instant::now()));
            ui.status_label
                .set_text("Force stop requested; waiting briefly...");
            ui.record_button.set_sensitive(false);
        }
        Some(Err(err)) => {
            append_log(
                &ui.log_buffer,
                &format!("Failed to force stop recorder: {err}"),
            );
            kill_recording_child(ui, "signal failed");
        }
        None => {}
    }
}

fn enforce_stop_deadlines(ui: &Ui) {
    let Some(stopped_at) = *ui.stop_started_at.borrow() else {
        return;
    };

    if ui.force_stop_started_at.borrow().is_none() && stopped_at.elapsed() >= STOP_GRACE {
        append_log(
            &ui.log_buffer,
            "Recorder did not stop in time; requesting force stop",
        );
        force_stop_recording(ui);
        return;
    }

    if let Some(force_started_at) = *ui.force_stop_started_at.borrow()
        && force_started_at.elapsed() >= FORCE_KILL_GRACE
    {
        kill_recording_child(ui, "force stop timed out");
    }
}

fn kill_recording_child(ui: &Ui, reason: &str) {
    if ui.kill_sent.replace(true) {
        return;
    }

    let result = ui.child.borrow_mut().as_mut().map(kill_recorder_process);
    match result {
        Some(Ok(())) => {
            ui.status_label
                .set_text("Recorder killed; waiting for exit...");
            append_log(
                &ui.log_buffer,
                &format!("Killed recorder process: {reason}"),
            );
        }
        Some(Err(err)) => append_log(&ui.log_buffer, &format!("Failed to kill recorder: {err}")),
        None => {}
    }
}

fn reset_stop_state(ui: &Ui) {
    ui.stop_started_at.replace(None);
    ui.force_stop_started_at.replace(None);
    ui.kill_sent.set(false);
}

fn reset_record_button(ui: &Ui) {
    ui.record_button.set_sensitive(true);
    ui.record_button.set_label("Start Recording");
    ui.record_button.add_css_class("suggested-action");
    ui.record_button.remove_css_class("destructive-action");
}

fn spawn_log_reader(reader: impl Read + Send + 'static, tx: mpsc::SyncSender<String>) {
    thread::spawn(move || {
        read_log_stream(reader, &tx);
    });
}

fn read_log_stream(mut reader: impl Read, tx: &mpsc::SyncSender<String>) {
    let mut buffer = [0_u8; LOG_READ_CHUNK_BYTES];
    let mut line = Vec::with_capacity(LOG_LINE_MAX_BYTES.min(LOG_READ_CHUNK_BYTES));
    let mut truncated = false;

    loop {
        match reader.read(&mut buffer) {
            Ok(0) => {
                flush_log_line(tx, &mut line, &mut truncated);
                break;
            }
            Ok(bytes_read) => {
                for &byte in &buffer[..bytes_read] {
                    if byte == b'\n' {
                        if !flush_log_line(tx, &mut line, &mut truncated) {
                            return;
                        }
                    } else if line.len() < LOG_LINE_MAX_BYTES {
                        line.push(byte);
                    } else {
                        truncated = true;
                    }
                }
            }
            Err(err) => {
                let _ = queue_log_line(tx, format!("Failed to read recorder output: {err}"));
                break;
            }
        }
    }
}

fn flush_log_line(tx: &mpsc::SyncSender<String>, line: &mut Vec<u8>, truncated: &mut bool) -> bool {
    if line.is_empty() && !*truncated {
        return true;
    }

    let mut text = String::from_utf8_lossy(line).into_owned();
    if *truncated {
        text.push_str(" ... [truncated]");
    }
    line.clear();
    *truncated = false;
    queue_log_line(tx, text)
}

fn queue_log_line(tx: &mpsc::SyncSender<String>, line: String) -> bool {
    match tx.try_send(line) {
        Ok(()) | Err(mpsc::TrySendError::Full(_)) => true,
        Err(mpsc::TrySendError::Disconnected(_)) => false,
    }
}

fn recorder_args(ui: &Ui) -> Vec<String> {
    let mode = selected_mode(ui);
    let mut args = vec!["--no-prompt".to_string(), "--mode".to_string()];
    args.push(
        match mode {
            Mode::Screen => "screen",
            Mode::Window => "window",
            Mode::Area => "area",
        }
        .to_string(),
    );

    if mode == Mode::Area
        && let Some(selected) = *ui.selected_area.borrow()
    {
        args.push("--area".to_string());
        args.push(capture_area_arg(selected.area));
        args.push("--area-screen".to_string());
        args.push(area_screen_arg(selected));
    }

    args.push("--audio".to_string());
    args.push(audio_arg(ui.audio_dropdown.selected()).to_string());

    args.push("--format".to_string());
    args.push(selected_output_format(ui).cli_arg().to_string());
    args.push("--h264-encoder".to_string());
    args.push(h264_encoder_arg(ui.encoder_dropdown.selected()).to_string());
    args.push("--effect".to_string());
    args.push(effect_arg(ui.effect_dropdown.selected()).to_string());

    if ui.hide_pointer_switch.is_active() {
        args.push("--hide-pointer".to_string());
    }

    let output = ui.output_entry.text().trim().to_string();
    if output.is_empty() {
        args.push("--dir".to_string());
        args.push(default_output_dir().to_string_lossy().into_owned());
    } else {
        args.push("--output".to_string());
        args.push(output);
    }

    args
}

fn capture_area_arg(area: CaptureArea) -> String {
    format!("{},{},{},{}", area.x, area.y, area.width, area.height)
}

fn area_screen_arg(selected: SelectedArea) -> String {
    format!("{}x{}", selected.screen_width, selected.screen_height)
}

fn area_label(selected: SelectedArea) -> String {
    format!(
        "{}x{} at {},{}",
        selected.area.width, selected.area.height, selected.area.x, selected.area.y
    )
}

fn selected_mode(ui: &Ui) -> Mode {
    if ui.window_button.is_active() {
        Mode::Window
    } else if ui.area_button.is_active() {
        Mode::Area
    } else {
        debug_assert!(ui.screen_button.is_active());
        Mode::Screen
    }
}

fn audio_arg(selected: u32) -> &'static str {
    match selected {
        1 => "mic",
        2 => "system",
        3 => "both",
        _ => "none",
    }
}

fn h264_encoder_arg(selected: u32) -> &'static str {
    match selected {
        1 => "hardware",
        2 => "software",
        _ => "auto",
    }
}

fn effect_arg(selected: u32) -> &'static str {
    match selected {
        1 => "neon",
        2 => "heatmap",
        3 => "glitch",
        4 => "ripple",
        5 => "glass",
        6 => "crt",
        7 => "holo",
        8 => "vapor",
        9 => "fracture",
        10 => "prism",
        11 => "aurora",
        12 => "kaleido",
        _ => "none",
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum OutputFormatChoice {
    Mkv,
    Mp4,
    Gif,
}

impl OutputFormatChoice {
    fn extension(self) -> &'static str {
        match self {
            Self::Mkv => "mkv",
            Self::Mp4 => "mp4",
            Self::Gif => "gif",
        }
    }

    fn cli_arg(self) -> &'static str {
        match self {
            Self::Mkv => "mkv",
            Self::Mp4 => "mp4",
            Self::Gif => "gif",
        }
    }
}

fn selected_output_format(ui: &Ui) -> OutputFormatChoice {
    match ui.format_dropdown.selected() {
        1 => OutputFormatChoice::Mp4,
        2 => OutputFormatChoice::Gif,
        _ => OutputFormatChoice::Mkv,
    }
}

fn recorder_command_path() -> PathBuf {
    if let Ok(current) = env::current_exe()
        && let Some(candidate) = sibling_recorder_path(&current)
        && candidate.exists()
    {
        return candidate;
    }

    PathBuf::from("oculo")
}

fn sibling_recorder_path(current: &Path) -> Option<PathBuf> {
    let file_name = current.file_name()?.to_str()?;
    if file_name == "ocula" {
        Some(current.with_file_name("oculo"))
    } else {
        None
    }
}

fn default_output_hint(format: OutputFormatChoice) -> String {
    format!(
        "Auto .{} in {}",
        format.extension(),
        default_output_dir().display()
    )
}

fn default_output_dir() -> PathBuf {
    glib::user_special_dir(glib::UserDirectory::Videos)
        .unwrap_or_else(glib::home_dir)
        .join("Ocula")
}

fn append_log(buffer: &gtk::TextBuffer, text: &str) {
    let mut iter = buffer.end_iter();
    buffer.insert(&mut iter, text);
    buffer.insert(&mut iter, "\n");
    trim_log_buffer(buffer);
}

fn trim_log_buffer(buffer: &gtk::TextBuffer) {
    while buffer.line_count() > LOG_SCROLLBACK_LINES {
        let mut start = buffer.start_iter();
        let Some(mut end) = buffer.iter_at_line(1) else {
            break;
        };
        buffer.delete(&mut start, &mut end);
    }

    let excess_chars = buffer.char_count() - LOG_SCROLLBACK_CHARS;
    if excess_chars > 0 {
        let mut start = buffer.start_iter();
        let mut end = buffer.iter_at_offset(excess_chars);
        buffer.delete(&mut start, &mut end);
    }
}

fn configure_recorder_command(command: &mut Command) {
    configure_recorder_process_group(command);
}

#[cfg(unix)]
fn configure_recorder_process_group(command: &mut Command) {
    unsafe {
        command.pre_exec(|| {
            let result = libc::setpgid(0, 0);
            if result == 0 {
                Ok(())
            } else {
                Err(std::io::Error::last_os_error())
            }
        });
    }
}

#[cfg(not(unix))]
fn configure_recorder_process_group(_command: &mut Command) {}

#[cfg(unix)]
fn interrupt_recorder_process(pid: u32) -> Result<(), std::io::Error> {
    signal_recorder_process_group(pid, libc::SIGINT)
}

#[cfg(not(unix))]
fn interrupt_recorder_process(_pid: u32) -> Result<(), std::io::Error> {
    Err(std::io::Error::new(
        std::io::ErrorKind::Unsupported,
        "safe stop from the GUI is only implemented on Unix",
    ))
}

#[cfg(unix)]
fn kill_recorder_process(child: &mut Child) -> Result<(), std::io::Error> {
    match signal_recorder_process_group(child.id(), libc::SIGKILL) {
        Ok(()) => Ok(()),
        Err(group_err) => child.kill().map_err(|child_err| {
            std::io::Error::new(
                child_err.kind(),
                format!(
                    "failed to kill recorder process group: {group_err}; failed to kill child: {child_err}"
                ),
            )
        }),
    }
}

#[cfg(not(unix))]
fn kill_recorder_process(child: &mut Child) -> Result<(), std::io::Error> {
    child.kill()
}

#[cfg(unix)]
fn signal_recorder_process_group(pid: u32, signal: libc::c_int) -> Result<(), std::io::Error> {
    let pid = pid_to_pid_t(pid)?;
    let result = unsafe { libc::kill(-pid, signal) };
    if result == 0 {
        Ok(())
    } else {
        Err(std::io::Error::last_os_error())
    }
}

#[cfg(unix)]
fn pid_to_pid_t(pid: u32) -> Result<libc::pid_t, std::io::Error> {
    let pid = libc::pid_t::try_from(pid).map_err(|_| {
        std::io::Error::new(
            std::io::ErrorKind::InvalidInput,
            format!("child pid {pid} does not fit pid_t"),
        )
    })?;
    if pid == 0 {
        return Err(std::io::Error::new(
            std::io::ErrorKind::InvalidInput,
            "child pid must not be zero",
        ));
    }

    Ok(pid)
}

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

    #[test]
    fn version_flag_is_handled_before_gtk() {
        assert!(version_requested(["--version"]));
        assert!(version_requested(["-V"]));
        assert!(version_requested(["--display", ":1", "--version"]));
        assert!(!version_requested(["--help"]));
    }

    #[test]
    fn audio_selection_maps_to_cli_args() {
        assert_eq!(audio_arg(0), "none");
        assert_eq!(audio_arg(1), "mic");
        assert_eq!(audio_arg(2), "system");
        assert_eq!(audio_arg(3), "both");
        assert_eq!(audio_arg(99), "none");
    }

    #[test]
    fn h264_encoder_selection_maps_to_cli_args() {
        assert_eq!(h264_encoder_arg(0), "auto");
        assert_eq!(h264_encoder_arg(1), "hardware");
        assert_eq!(h264_encoder_arg(2), "software");
        assert_eq!(h264_encoder_arg(99), "auto");
    }

    #[test]
    fn effect_selection_maps_to_cli_args() {
        assert_eq!(effect_arg(0), "none");
        assert_eq!(effect_arg(1), "neon");
        assert_eq!(effect_arg(2), "heatmap");
        assert_eq!(effect_arg(3), "glitch");
        assert_eq!(effect_arg(4), "ripple");
        assert_eq!(effect_arg(5), "glass");
        assert_eq!(effect_arg(6), "crt");
        assert_eq!(effect_arg(7), "holo");
        assert_eq!(effect_arg(8), "vapor");
        assert_eq!(effect_arg(9), "fracture");
        assert_eq!(effect_arg(10), "prism");
        assert_eq!(effect_arg(11), "aurora");
        assert_eq!(effect_arg(12), "kaleido");
        assert_eq!(effect_arg(99), "none");
    }

    #[test]
    fn x11_channel_extraction_scales_visual_masks() {
        assert_eq!(extract_x11_channel(0x00ff0000, 0x00ff0000), 255);
        assert_eq!(extract_x11_channel(0x00008000, 0x0000ff00), 128);
        assert_eq!(extract_x11_channel(0x0000001f, 0x0000001f), 255);
        assert_eq!(extract_x11_channel(0, 0), 0);
    }

    #[test]
    fn x11_stride_respects_scanline_padding() {
        assert_eq!(x11_image_stride(3, 24, 32), Some(12));
        assert_eq!(x11_image_stride(3, 32, 32), Some(12));
        assert_eq!(x11_image_stride(0, 32, 32), None);
    }

    #[test]
    fn crop_overlay_rectangles_stay_outside_crop() {
        let area = CaptureArea::new(20, 10, 60, 50).unwrap();
        let rectangles = crop_overlay_rectangles(area, 140, 120, 8);

        assert!(rectangles_contain_point(&rectangles, (50, 5)));
        assert!(rectangles_contain_point(&rectangles, (15, 30)));
        assert!(rectangles_contain_point(&rectangles, (80, 30)));
        assert!(rectangles_contain_point(&rectangles, (50, 60)));
        assert!(!rectangles_contain_point(&rectangles, (50, 30)));
    }

    #[test]
    fn crop_overlay_is_empty_for_full_screen_crop() {
        let area = CaptureArea::new(0, 0, 140, 120).unwrap();

        assert!(crop_overlay_rectangles(area, 140, 120, 8).is_empty());
    }

    #[test]
    fn output_format_choice_maps_to_extension_and_cli_arg() {
        assert_eq!(OutputFormatChoice::Mkv.extension(), "mkv");
        assert_eq!(OutputFormatChoice::Mkv.cli_arg(), "mkv");
        assert_eq!(OutputFormatChoice::Mp4.extension(), "mp4");
        assert_eq!(OutputFormatChoice::Mp4.cli_arg(), "mp4");
        assert_eq!(OutputFormatChoice::Gif.extension(), "gif");
        assert_eq!(OutputFormatChoice::Gif.cli_arg(), "gif");
    }

    #[test]
    fn sibling_recorder_removes_gui_suffix() {
        assert_eq!(
            sibling_recorder_path(Path::new("/tmp/ocula")).unwrap(),
            PathBuf::from("/tmp/oculo"),
        );
        assert!(sibling_recorder_path(Path::new("/tmp/oculo")).is_none());
    }

    #[test]
    fn selected_area_formats_cli_args_and_label() {
        let selected = SelectedArea {
            area: CaptureArea::new(10, 20, 160, 120).unwrap(),
            screen_width: 1920,
            screen_height: 1080,
        };

        assert_eq!(capture_area_arg(selected.area), "10,20,160,120");
        assert_eq!(area_screen_arg(selected), "1920x1080");
        assert_eq!(area_label(selected), "160x120 at 10,20");
    }

    #[test]
    fn preview_rect_preserves_selected_area_position_and_size() {
        let selected = SelectedArea {
            area: CaptureArea::new(100, 50, 400, 300).unwrap(),
            screen_width: 1000,
            screen_height: 500,
        };

        let rect = preview_selection_rect(selected, 528.0, 278.0).unwrap();

        assert!((rect.x - 64.0).abs() < 0.1);
        assert!((rect.y - 39.0).abs() < 0.1);
        assert!((rect.width - 200.0).abs() < 0.1);
        assert!((rect.height - 150.0).abs() < 0.1);
    }

    #[test]
    fn preview_rect_rejects_invalid_screen_size() {
        let selected = SelectedArea {
            area: CaptureArea::new(0, 0, 100, 100).unwrap(),
            screen_width: 0,
            screen_height: 500,
        };

        assert!(preview_selection_rect(selected, 320.0, 180.0).is_none());
    }

    #[test]
    fn preview_crop_rect_fills_available_card() {
        let rect = preview_crop_rect(160, 120, 320.0, 180.0).unwrap();

        assert!((rect.x - 61.3).abs() < 0.2);
        assert!((rect.y - 16.0).abs() < 0.1);
        assert!((rect.width - 197.3).abs() < 0.2);
        assert!((rect.height - 148.0).abs() < 0.1);
    }

    #[test]
    fn log_queue_drops_when_full_instead_of_blocking() {
        let (tx, rx) = mpsc::sync_channel(1);

        assert!(queue_log_line(&tx, "first".to_string()));
        assert!(queue_log_line(&tx, "dropped".to_string()));

        assert_eq!(rx.try_recv().unwrap(), "first");
        assert!(rx.try_recv().is_err());
    }

    #[test]
    fn log_queue_reports_disconnected_receiver() {
        let (tx, rx) = mpsc::sync_channel(1);
        drop(rx);

        assert!(!queue_log_line(&tx, "ignored".to_string()));
    }

    #[test]
    fn log_stream_reader_truncates_long_lines() {
        let (tx, rx) = mpsc::sync_channel(4);
        let input = vec![b'x'; LOG_LINE_MAX_BYTES + 128];

        read_log_stream(std::io::Cursor::new(input), &tx);

        let line = rx.try_recv().unwrap();
        assert!(line.len() <= LOG_LINE_MAX_BYTES + " ... [truncated]".len());
        assert!(line.ends_with(" ... [truncated]"));
        assert!(rx.try_recv().is_err());
    }

    #[test]
    fn log_stream_reader_splits_lines() {
        let (tx, rx) = mpsc::sync_channel(4);

        read_log_stream(std::io::Cursor::new(b"one\ntwo\n"), &tx);

        assert_eq!(rx.try_recv().unwrap(), "one");
        assert_eq!(rx.try_recv().unwrap(), "two");
        assert!(rx.try_recv().is_err());
    }

    #[cfg(unix)]
    #[test]
    fn pid_conversion_rejects_zero() {
        assert!(pid_to_pid_t(0).is_err());
    }

    #[cfg(unix)]
    #[test]
    fn pid_conversion_accepts_normal_pid() {
        assert_eq!(pid_to_pid_t(1).unwrap(), 1);
    }

    #[cfg(all(unix, target_pointer_width = "64"))]
    #[test]
    fn pid_conversion_rejects_out_of_range_pid() {
        assert!(pid_to_pid_t(i32::MAX as u32 + 1).is_err());
    }

    fn rectangles_contain_point(rectangles: &[Rectangle], point: (i16, i16)) -> bool {
        rectangles.iter().any(|rectangle| {
            point.0 >= rectangle.x
                && point.1 >= rectangle.y
                && point.0 < rectangle.x + rectangle.width as i16
                && point.1 < rectangle.y + rectangle.height as i16
        })
    }
}
