use std::error::Error;

use halley_capit::CaptureCrop;
use halley_config::RuntimeTuning;
use halley_ipc::CaptureMode;
use smithay::{
    backend::renderer::{
        Color32F, Texture,
        gles::{GlesFrame, Uniform},
    },
    utils::{Buffer, Physical, Rectangle, Transform},
};

use crate::compositor::root::Halley;
use crate::render::draw_primitives::{draw_outline_rect, draw_rect, draw_ring};
use crate::render::shadow::draw_shadow_rect;
use crate::render::state::RenderState;
use crate::render::{
    screenshot_menu_background_color, screenshot_menu_highlight_color,
    screenshot_menu_icon_texture, screenshot_menu_inactive_highlight_color,
    screenshot_menu_item_fill_color,
};

use super::OverlayView;

const BORDER_THICKNESS: i32 = 2;
const HANDLE_SIZE: i32 = 12;
const DIM_COLOR: Color32F = Color32F::new(0.0, 0.0, 0.0, 0.40);
const SCREEN_DIM_COLOR: Color32F = Color32F::new(0.0, 0.0, 0.0, 0.28);
const WINDOW_CAPTURE_FILL: Color32F = Color32F::new(0.45, 0.45, 0.45, 0.34);
const WINDOW_CAPTURE_OUTLINE: Color32F = Color32F::new(0.72, 0.72, 0.72, 0.78);
const DASH_LEN: i32 = 10;
const GAP_LEN: i32 = 6;
const MENU_BAR_W: i32 = 420;
const MENU_BAR_H: i32 = 80;
const MENU_SLOT_W: i32 = MENU_BAR_W / 3;
const MENU_PAD: i32 = 10;
const MENU_ICON_SIZE: i32 = 42;
const MENU_ITEM_BORDER_PX: f32 = 2.0;
const ACTIVE_BORDER_ALPHA: f32 = 1.0;
const INACTIVE_BORDER_ALPHA: f32 = 0.72;
const MENU_BAR_CORNER_RADIUS: f32 = 18.0;
const MENU_ITEM_CORNER_RADIUS: f32 = 12.0;

#[derive(Clone, Copy)]
pub(crate) enum ScreenshotMenuHit {
    Item(usize),
}

#[derive(Clone, Copy)]
struct RectLocal {
    x: i32,
    y: i32,
    w: i32,
    h: i32,
}

#[derive(Clone, Copy)]
struct ScreenshotMenuStyle {
    rounded: bool,
    outer_border_px: f32,
    item_border_px: f32,
    bar_corner_radius: f32,
    item_corner_radius: f32,
}

fn resolve_screenshot_menu_style(tuning: &RuntimeTuning) -> ScreenshotMenuStyle {
    let visuals = super::resolve_overlay_visuals(tuning);
    ScreenshotMenuStyle {
        rounded: visuals.rounded,
        outer_border_px: visuals.border_px,
        item_border_px: MENU_ITEM_BORDER_PX,
        bar_corner_radius: if visuals.rounded {
            MENU_BAR_CORNER_RADIUS
        } else {
            0.0
        },
        item_corner_radius: if visuals.rounded {
            MENU_ITEM_CORNER_RADIUS
        } else {
            0.0
        },
    }
}

fn draw_rect_border(
    frame: &mut GlesFrame<'_, '_>,
    rect: Rectangle<i32, Physical>,
    border_px: i32,
    color: Color32F,
    damage: Rectangle<i32, Physical>,
) -> Result<(), Box<dyn Error>> {
    let border_px = border_px
        .max(0)
        .min((rect.size.w / 2).max(0))
        .min((rect.size.h / 2).max(0));
    if border_px <= 0 {
        return Ok(());
    }
    draw_rect(
        frame,
        rect.loc.x,
        rect.loc.y,
        rect.size.w,
        border_px,
        color,
        damage,
    )?;
    draw_rect(
        frame,
        rect.loc.x,
        rect.loc.y + rect.size.h - border_px,
        rect.size.w,
        border_px,
        color,
        damage,
    )?;
    draw_rect(
        frame,
        rect.loc.x,
        rect.loc.y,
        border_px,
        rect.size.h,
        color,
        damage,
    )?;
    draw_rect(
        frame,
        rect.loc.x + rect.size.w - border_px,
        rect.loc.y,
        border_px,
        rect.size.h,
        color,
        damage,
    )?;
    Ok(())
}

fn draw_screenshot_menu_chip(
    frame: &mut GlesFrame<'_, '_>,
    render_state: &RenderState,
    rect: Rectangle<i32, Physical>,
    rounded: bool,
    corner_radius: f32,
    border_px: f32,
    fill_color: Color32F,
    border_color: Color32F,
    damage: Rectangle<i32, Physical>,
) -> Result<(), Box<dyn Error>> {
    if let (Some(texture), Some(program)) = (
        render_state.gpu.node_circle_texture.as_ref(),
        render_state.ui_rect_program(rounded),
    ) {
        let tex_size: smithay::utils::Size<i32, Buffer> = texture.size();
        let src = Rectangle::<f64, Buffer>::new(
            (0.0, 0.0).into(),
            (tex_size.w as f64, tex_size.h as f64).into(),
        );
        let border_px = border_px.max(0.0);
        let uniforms = [
            Uniform::new(
                "node_color",
                (
                    border_color.r(),
                    border_color.g(),
                    border_color.b(),
                    border_color.a(),
                ),
            ),
            Uniform::new(
                "fill_color",
                (
                    fill_color.r(),
                    fill_color.g(),
                    fill_color.b(),
                    fill_color.a(),
                ),
            ),
            Uniform::new("rect_size", (rect.size.w as f32, rect.size.h as f32)),
            Uniform::new(
                "inner_rect_size",
                (
                    (rect.size.w as f32 - border_px * 2.0).max(1.0),
                    (rect.size.h as f32 - border_px * 2.0).max(1.0),
                ),
            ),
            Uniform::new(
                "inner_rect_offset",
                (border_px.max(0.0), border_px.max(0.0)),
            ),
            Uniform::new("corner_radius", corner_radius),
            Uniform::new("inner_corner_radius", (corner_radius - border_px).max(0.0)),
            Uniform::new("border_px", border_px),
        ];

        frame.render_texture_from_to(
            texture,
            src,
            rect,
            &[damage],
            &[],
            Transform::Normal,
            1.0,
            Some(program),
            &uniforms,
        )?;
        return Ok(());
    }

    draw_rect(
        frame,
        rect.loc.x,
        rect.loc.y,
        rect.size.w,
        rect.size.h,
        fill_color,
        damage,
    )?;
    draw_rect_border(
        frame,
        rect,
        border_px.round().max(0.0) as i32,
        border_color,
        damage,
    )?;
    Ok(())
}

fn screenshot_menu_modes() -> [CaptureMode; 3] {
    [
        CaptureMode::Region,
        CaptureMode::Screen,
        CaptureMode::Window,
    ]
}

fn screenshot_menu_rect(index: usize, screen_w: i32, screen_h: i32) -> Rectangle<i32, Physical> {
    let start_x = (screen_w - MENU_BAR_W) / 2;
    let y = screen_h - MENU_BAR_H - 24;
    Rectangle::<i32, Physical>::new(
        (start_x + index as i32 * MENU_SLOT_W, y).into(),
        (MENU_SLOT_W, MENU_BAR_H).into(),
    )
}

fn screenshot_menu_bar_rect(screen_w: i32, screen_h: i32) -> Rectangle<i32, Physical> {
    Rectangle::<i32, Physical>::new(
        (((screen_w - MENU_BAR_W) / 2), screen_h - MENU_BAR_H - 24).into(),
        (MENU_BAR_W, MENU_BAR_H).into(),
    )
}

pub(crate) fn screenshot_menu_hit_test(
    screen_w: i32,
    screen_h: i32,
    sx: f32,
    sy: f32,
) -> Option<ScreenshotMenuHit> {
    for idx in 0..3 {
        let rect = screenshot_menu_rect(idx, screen_w, screen_h);
        if (sx.round() as i32) >= rect.loc.x
            && (sx.round() as i32) <= rect.loc.x + rect.size.w
            && (sy.round() as i32) >= rect.loc.y
            && (sy.round() as i32) <= rect.loc.y + rect.size.h
        {
            return Some(ScreenshotMenuHit::Item(idx));
        }
    }
    None
}

fn draw_screenshot_menu(
    frame: &mut GlesFrame<'_, '_>,
    st: &Halley,
    overlay: &OverlayView<'_>,
    screen_w: i32,
    screen_h: i32,
    damage: Rectangle<i32, Physical>,
) -> Result<(), Box<dyn Error>> {
    let (selected_idx, hovered_idx) = st
        .input
        .interaction_state
        .screenshot_session
        .as_ref()
        .map(|s| (s.menu_selected, s.menu_hovered))
        .unwrap_or((0, None));
    let background = screenshot_menu_background_color(overlay.tuning);
    let highlight = screenshot_menu_highlight_color(overlay.tuning);
    let item_fill = screenshot_menu_item_fill_color(overlay.tuning);
    let style = resolve_screenshot_menu_style(overlay.tuning);
    let bar_rect = screenshot_menu_bar_rect(screen_w, screen_h);

    draw_shadow_rect(
        frame,
        overlay.render_state,
        overlay.tuning.decorations.shadows.overlay,
        bar_rect,
        if style.rounded {
            style.bar_corner_radius
        } else {
            0.0
        },
        1.0,
        damage,
    )?;

    draw_screenshot_menu_chip(
        frame,
        overlay.render_state,
        bar_rect,
        style.rounded,
        style.bar_corner_radius,
        style.outer_border_px,
        color32f(background, 0.96),
        color32f(highlight, ACTIVE_BORDER_ALPHA),
        damage,
    )?;
    for (idx, mode) in screenshot_menu_modes().into_iter().enumerate() {
        let rect = screenshot_menu_rect(idx, screen_w, screen_h);
        let active = hovered_idx == Some(idx) || selected_idx == idx;
        let fill = if active {
            color32f(background, 0.96)
        } else {
            color32f(item_fill, 0.94)
        };
        let border = if active {
            color32f(highlight, ACTIVE_BORDER_ALPHA)
        } else {
            color32f(
                screenshot_menu_inactive_highlight_color(overlay.tuning),
                INACTIVE_BORDER_ALPHA,
            )
        };
        let item_rect = Rectangle::<i32, Physical>::new(
            (rect.loc.x + MENU_PAD, rect.loc.y + MENU_PAD).into(),
            (rect.size.w - MENU_PAD * 2, rect.size.h - MENU_PAD * 2).into(),
        );
        draw_screenshot_menu_chip(
            frame,
            overlay.render_state,
            item_rect,
            style.rounded,
            style.item_corner_radius,
            style.item_border_px,
            fill,
            border,
            damage,
        )?;
        if let Some(icon) = screenshot_menu_icon_texture(st, mode, active) {
            let dest = Rectangle::<i32, Physical>::new(
                (
                    rect.loc.x + (rect.size.w - MENU_ICON_SIZE) / 2,
                    rect.loc.y + (rect.size.h - MENU_ICON_SIZE) / 2,
                )
                    .into(),
                (MENU_ICON_SIZE, MENU_ICON_SIZE).into(),
            );
            let src = Rectangle::<f64, Buffer>::new(
                (0.0, 0.0).into(),
                (icon.width as f64, icon.height as f64).into(),
            );
            frame.render_texture_from_to(
                &icon.texture,
                src,
                dest,
                &[damage],
                &[],
                Transform::Normal,
                1.0,
                None,
                &[],
            )?;
        }
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use halley_config::{OverlayShape, RuntimeTuning};

    use super::{
        MENU_BAR_CORNER_RADIUS, MENU_ITEM_BORDER_PX, MENU_ITEM_CORNER_RADIUS,
        resolve_screenshot_menu_style,
    };

    #[test]
    fn screenshot_menu_style_follows_overlay_shape_radii_and_border_toggle() {
        let mut tuning = RuntimeTuning::default();
        tuning.decorations.border.size_px = 6;
        tuning.overlay_style.shape = OverlayShape::Rounded;
        tuning.overlay_style.borders = true;

        let style = resolve_screenshot_menu_style(&tuning);

        assert!(style.rounded);
        assert_eq!(style.outer_border_px, 6.0);
        assert_eq!(style.item_border_px, MENU_ITEM_BORDER_PX);
        assert_eq!(style.bar_corner_radius, MENU_BAR_CORNER_RADIUS);
        assert_eq!(style.item_corner_radius, MENU_ITEM_CORNER_RADIUS);

        tuning.overlay_style.shape = OverlayShape::Square;
        tuning.overlay_style.borders = false;

        let style = resolve_screenshot_menu_style(&tuning);

        assert!(!style.rounded);
        assert_eq!(style.outer_border_px, 0.0);
        assert_eq!(style.item_border_px, MENU_ITEM_BORDER_PX);
        assert_eq!(style.bar_corner_radius, 0.0);
        assert_eq!(style.item_corner_radius, 0.0);
    }
}

fn color32f(color: halley_config::DecorationBorderColor, alpha: f32) -> Color32F {
    Color32F::new(color.r, color.g, color.b, alpha)
}

fn to_local_rect(crop: CaptureCrop, offset_x: i32, offset_y: i32) -> RectLocal {
    RectLocal {
        x: crop.x - offset_x,
        y: crop.y - offset_y,
        w: crop.w,
        h: crop.h,
    }
}

fn draw_dashed_border(
    frame: &mut GlesFrame<'_, '_>,
    rect: RectLocal,
    color: Color32F,
    damage: Rectangle<i32, Physical>,
) -> Result<(), Box<dyn Error>> {
    let x0 = rect.x;
    let y0 = rect.y;
    let x1 = rect.x + rect.w;
    let y1 = rect.y + rect.h;

    let mut x = x0;
    while x < x1 {
        let seg = (x1 - x).min(DASH_LEN);
        draw_rect(frame, x, y0, seg, BORDER_THICKNESS, color, damage)?;
        draw_rect(
            frame,
            x,
            y1 - BORDER_THICKNESS,
            seg,
            BORDER_THICKNESS,
            color,
            damage,
        )?;
        x += DASH_LEN + GAP_LEN;
    }

    let mut y = y0;
    while y < y1 {
        let seg = (y1 - y).min(DASH_LEN);
        draw_rect(frame, x0, y, BORDER_THICKNESS, seg, color, damage)?;
        draw_rect(
            frame,
            x1 - BORDER_THICKNESS,
            y,
            BORDER_THICKNESS,
            seg,
            color,
            damage,
        )?;
        y += DASH_LEN + GAP_LEN;
    }

    Ok(())
}

fn draw_corner_handles(
    frame: &mut GlesFrame<'_, '_>,
    rect: RectLocal,
    color: Color32F,
    damage: Rectangle<i32, Physical>,
) -> Result<(), Box<dyn Error>> {
    let radius = (HANDLE_SIZE as f32) * 0.5;
    for (cx, cy) in [
        (rect.x as f32, rect.y as f32),
        ((rect.x + rect.w) as f32, rect.y as f32),
        (rect.x as f32, (rect.y + rect.h) as f32),
        ((rect.x + rect.w) as f32, (rect.y + rect.h) as f32),
    ] {
        draw_rect(
            frame,
            (cx - radius).round() as i32,
            (cy - radius).round() as i32,
            HANDLE_SIZE,
            HANDLE_SIZE,
            color,
            damage,
        )?;
        draw_ring(frame, cx, cy, radius, radius, color, damage)?;
    }
    Ok(())
}

fn draw_screenshot_selection_overlay(
    frame: &mut GlesFrame<'_, '_>,
    selection_rect: Option<CaptureCrop>,
    offset_x: i32,
    offset_y: i32,
    screen_w: i32,
    screen_h: i32,
    border_color: Color32F,
    dim_color: Color32F,
    damage: Rectangle<i32, Physical>,
) -> Result<(), Box<dyn Error>> {
    if let Some(rect) = selection_rect {
        let sel = to_local_rect(rect, offset_x, offset_y);
        let sel_right = sel.x + sel.w;
        let sel_bottom = sel.y + sel.h;
        let intersects = sel_right > 0 && sel.x < screen_w && sel_bottom > 0 && sel.y < screen_h;
        if intersects {
            let clip_x = sel.x.max(0);
            let clip_y = sel.y.max(0);
            let clip_w = (sel.x + sel.w).min(screen_w) - clip_x;
            let clip_h = (sel.y + sel.h).min(screen_h) - clip_y;
            if clip_y > 0 {
                draw_rect(frame, 0, 0, screen_w, clip_y, dim_color, damage)?;
            }
            if clip_x > 0 {
                draw_rect(frame, 0, clip_y, clip_x, clip_h, dim_color, damage)?;
            }
            let right_x = clip_x + clip_w;
            if right_x < screen_w {
                draw_rect(
                    frame,
                    right_x,
                    clip_y,
                    screen_w - right_x,
                    clip_h,
                    dim_color,
                    damage,
                )?;
            }
            let bottom_y = clip_y + clip_h;
            if bottom_y < screen_h {
                draw_rect(
                    frame,
                    0,
                    bottom_y,
                    screen_w,
                    screen_h - bottom_y,
                    dim_color,
                    damage,
                )?;
            }

            draw_dashed_border(frame, sel, border_color, damage)?;
            draw_corner_handles(frame, sel, border_color, damage)?;
        } else {
            draw_rect(frame, 0, 0, screen_w, screen_h, dim_color, damage)?;
        }
    } else {
        draw_rect(frame, 0, 0, screen_w, screen_h, dim_color, damage)?;
    }
    Ok(())
}

fn draw_screenshot_window_overlay(
    frame: &mut GlesFrame<'_, '_>,
    selection_rect: Option<CaptureCrop>,
    offset_x: i32,
    offset_y: i32,
    screen_w: i32,
    screen_h: i32,
    dim_color: Color32F,
    damage: Rectangle<i32, Physical>,
) -> Result<(), Box<dyn Error>> {
    if let Some(rect) = selection_rect {
        let sel = to_local_rect(rect, offset_x, offset_y);
        let sel_right = sel.x + sel.w;
        let sel_bottom = sel.y + sel.h;
        let intersects = sel_right > 0 && sel.x < screen_w && sel_bottom > 0 && sel.y < screen_h;
        if intersects {
            let clip_x = sel.x.max(0);
            let clip_y = sel.y.max(0);
            let clip_w = (sel.x + sel.w).min(screen_w) - clip_x;
            let clip_h = (sel.y + sel.h).min(screen_h) - clip_y;
            if clip_y > 0 {
                draw_rect(frame, 0, 0, screen_w, clip_y, dim_color, damage)?;
            }
            if clip_x > 0 {
                draw_rect(frame, 0, clip_y, clip_x, clip_h, dim_color, damage)?;
            }
            let right_x = clip_x + clip_w;
            if right_x < screen_w {
                draw_rect(
                    frame,
                    right_x,
                    clip_y,
                    screen_w - right_x,
                    clip_h,
                    dim_color,
                    damage,
                )?;
            }
            let bottom_y = clip_y + clip_h;
            if bottom_y < screen_h {
                draw_rect(
                    frame,
                    0,
                    bottom_y,
                    screen_w,
                    screen_h - bottom_y,
                    dim_color,
                    damage,
                )?;
            }

            draw_rect(
                frame,
                clip_x,
                clip_y,
                clip_w.max(1),
                clip_h.max(1),
                WINDOW_CAPTURE_FILL,
                damage,
            )?;
            draw_outline_rect(
                frame,
                clip_x,
                clip_y,
                clip_w.max(1),
                clip_h.max(1),
                WINDOW_CAPTURE_OUTLINE,
                damage,
            )?;
        } else {
            draw_rect(frame, 0, 0, screen_w, screen_h, dim_color, damage)?;
        }
    } else {
        draw_rect(frame, 0, 0, screen_w, screen_h, dim_color, damage)?;
    }
    Ok(())
}

pub(crate) fn draw_screenshot_overlay(
    frame: &mut GlesFrame<'_, '_>,
    st: &mut Halley,
    screen_w: i32,
    screen_h: i32,
    damage: Rectangle<i32, Physical>,
) -> Result<(), Box<dyn Error>> {
    let Some(session) = st.input.interaction_state.screenshot_session.as_ref() else {
        return Ok(());
    };
    let overlay = OverlayView::from_halley(st);
    let screenshot_highlight = color32f(
        screenshot_menu_highlight_color(overlay.tuning),
        ACTIVE_BORDER_ALPHA,
    );
    let Some(space) = overlay
        .monitor_state
        .monitors
        .get(overlay.monitor_state.current_monitor.as_str())
    else {
        return Ok(());
    };

    if session.mode == CaptureMode::Menu {
        if overlay.monitor_state.current_monitor != session.monitor {
            return Ok(());
        }
        return draw_screenshot_menu(frame, &*st, &overlay, screen_w, screen_h, damage);
    }

    match session.mode {
        CaptureMode::Region => {
            draw_screenshot_selection_overlay(
                frame,
                session.selection_rect,
                space.offset_x,
                space.offset_y,
                screen_w,
                screen_h,
                screenshot_highlight,
                DIM_COLOR,
                damage,
            )?;
        }
        CaptureMode::Screen => {
            let selected = overlay.monitor_state.current_monitor == session.monitor;
            if selected {
                draw_outline_rect(
                    frame,
                    0,
                    0,
                    screen_w,
                    screen_h,
                    screenshot_highlight,
                    damage,
                )?;
            } else {
                draw_rect(frame, 0, 0, screen_w, screen_h, SCREEN_DIM_COLOR, damage)?;
            }
        }
        CaptureMode::Window => {
            if overlay.monitor_state.current_monitor == session.monitor {
                draw_screenshot_window_overlay(
                    frame,
                    session.selection_rect,
                    space.offset_x,
                    space.offset_y,
                    screen_w,
                    screen_h,
                    Color32F::new(0.0, 0.0, 0.0, 0.18),
                    damage,
                )?;
            } else {
                draw_rect(
                    frame,
                    0,
                    0,
                    screen_w,
                    screen_h,
                    Color32F::new(0.0, 0.0, 0.0, 0.18),
                    damage,
                )?;
            }
        }
        CaptureMode::Menu => unreachable!(),
    }

    Ok(())
}
