//! Presentation of the consent dialog: shared layout, hit-testing and the
//! element pass. State and reply channels live in the parent module.

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

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

use super::ConfirmDialog;

pub(super) const DIALOG_BG: [u8; 4] = [30, 30, 30, 245];
pub(super) const OVERLAY_BG: [u8; 4] = [0, 0, 0, 160];
pub(super) const ALLOW_BG: [u8; 4] = [60, 100, 180, 255];
pub(super) const DENY_BG: [u8; 4] = [80, 80, 80, 255];
const TEXT_COLOR: [u8; 4] = [255, 255, 255, 255];
const LABEL_COLOR: [u8; 4] = [200, 200, 200, 255];

const DIALOG_WIDTH: i32 = 380;
const DIALOG_HEIGHT: i32 = 200;
const BUTTON_HEIGHT: i32 = 44;
const OPTION_HEIGHT: i32 = 28;
const PADDING: i32 = 16;

/// Dialog geometry, computed once so hit-testing and rendering can never
/// disagree about where the buttons are.
struct ConfirmLayout {
    dialog: Rectangle<i32, Logical>,
    allow: Rectangle<i32, Logical>,
    deny: Rectangle<i32, Logical>,
    /// One slot per source option (screencast output picker); empty when the
    /// request offers no choice.
    options: Vec<Rectangle<i32, Logical>>,
}

impl ConfirmLayout {
    fn compute(canvas: &Canvas, n_options: usize, two_buttons: bool) -> Self {
        let size = canvas.size();
        let dw = if canvas.is_mobile() { size.w - 32 } else { DIALOG_WIDTH };
        let dx = (size.w - dw) / 2;
        let dy = (size.h - DIALOG_HEIGHT) / 2;
        let btn_y = dy + DIALOG_HEIGHT - BUTTON_HEIGHT - PADDING;
        let half_w = (dw - 3 * PADDING) / 2;
        let allow_w = if two_buttons { half_w } else { dw - 2 * PADDING };
        let deny_w = if two_buttons { half_w } else { 0 };

        let mut options = Vec::new();
        if n_options >= 2 {
            let opt_y = btn_y - OPTION_HEIGHT - 10;
            let n = n_options as i32;
            let opt_w = (dw - PADDING * (n + 1)) / n;
            for i in 0..n {
                options.push(Rectangle::new(
                    Point::from((dx + PADDING + i * (opt_w + PADDING), opt_y)),
                    Size::from((opt_w, OPTION_HEIGHT)),
                ));
            }
        }

        Self {
            dialog: Rectangle::new(Point::from((dx, dy)), Size::from((dw, DIALOG_HEIGHT))),
            allow: Rectangle::new(Point::from((dx + PADDING, btn_y)), Size::from((allow_w, BUTTON_HEIGHT))),
            deny: Rectangle::new(Point::from((dx + dw / 2 + PADDING / 2, btn_y)), Size::from((deny_w, BUTTON_HEIGHT))),
            options,
        }
    }
}

impl ConfirmDialog {
    /// Hit-test a tap: `Some(true)` = Allow, `Some(false)` = Deny, `None` = the
    /// tap hit an option or missed (the modal still swallows it).
    pub fn handle_tap(&mut self, canvas: &Canvas, point: Point<f64, Logical>) -> Option<bool> {
        if !self.visible {
            return None;
        }
        let l = ConfirmLayout::compute(canvas, self.front_option_count(), self.front_has_deny());
        if l.allow.to_f64().contains(point) {
            return Some(true);
        }
        if l.deny.size.w > 0 && l.deny.to_f64().contains(point) {
            return Some(false);
        }
        if let Some(i) = l.options.iter().position(|r| r.to_f64().contains(point)) {
            self.selected = i;
            self.dirty = true;
        }
        None
    }

    fn update_textures(
        &mut self,
        renderer: &mut GlesRenderer,
        canvas: &Canvas,
        text_renderer: &mut TextRenderer,
    ) {
        let scale = canvas.scale();
        if !self.dirty && (self.last_scale - scale).abs() < 0.01 {
            return;
        }
        self.dirty = false;
        self.last_scale = scale;

        let title_font = crate::text::text_px(16.0, scale).round();
        let msg_font = crate::text::text_px(12.0, scale).round();
        let btn_font = crate::text::text_px(14.0, scale).round();

        let Some(req) = self.queue.front() else { return };

        let (buf, w, h) = text_renderer.render_line(&req.title, title_font, TEXT_COLOR);
        self.title_texture = (w > 0 && h > 0).then(|| RenderTexture::new(
            Texture::from_buffer(renderer, scale, &buf, w as i32, h as i32, false),
        ));

        {
            let (buf, w, h) = text_renderer.render_line(&req.message, msg_font, LABEL_COLOR);
            self.message_texture = (w > 0 && h > 0).then(|| RenderTexture::new(
                Texture::from_buffer(renderer, scale, &buf, w as i32, h as i32, false),
            ));

            let opt_font = crate::text::text_px(11.0, scale).round();
            self.option_labels.clear();
            for name in &req.options {
                let (buf, w, h) = text_renderer.render_line(name, opt_font, TEXT_COLOR);
                if w > 0 && h > 0 {
                    self.option_labels.push(RenderTexture::new(
                        Texture::from_buffer(renderer, scale, &buf, w as i32, h as i32, false),
                    ));
                }
            }
        }

        let (buf, w, h) = text_renderer.render_line(&req.allow_label, btn_font, TEXT_COLOR);
        self.allow_label = (w > 0 && h > 0).then(|| RenderTexture::new(
            Texture::from_buffer(renderer, scale, &buf, w as i32, h as i32, false),
        ));
        self.deny_label = req.deny_label.as_deref().and_then(|label| {
            let (buf, w, h) = text_renderer.render_line(label, btn_font, TEXT_COLOR);
            (w > 0 && h > 0).then(|| RenderTexture::new(
                Texture::from_buffer(renderer, scale, &buf, w as i32, h as i32, false),
            ))
        });
    }

    pub fn textures(
        &mut self,
        textures: &mut Vec<ForgeElement>,
        canvas: &Canvas,
        renderer: &mut GlesRenderer,
        text_renderer: &mut TextRenderer,
    ) {
        if !self.visible {
            return;
        }
        self.update_textures(renderer, canvas, text_renderer);

        let scale = canvas.scale();
        let size = canvas.size();

        let l = ConfirmLayout::compute(canvas, self.front_option_count(), self.front_has_deny());
        let (dx, dy, dw) = (l.dialog.loc.x, l.dialog.loc.y, l.dialog.size.w);
        let btn_y = l.allow.loc.y;
        let allow_w = l.allow.size.w;
        let half_w = l.deny.size.w;

        // The element list is front-to-back: text first, then the buttons,
        // then the dialog panel, and the screen-dimming overlay behind it all.
        let mut text_y = dy + PADDING;

        if let Some(title) = &self.title_texture {
            let ts = title.size();
            let tx = dx + (dw - ts.w) / 2;
            let rect: Rectangle<i32, Logical> = Rectangle::new(Point::from((tx, text_y)), ts);
            let physical = rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, title.clone(), physical.loc, physical, None, scale);
            text_y += ts.h + 16;
        }

        if let Some(msg) = &self.message_texture {
            let ms = msg.size();
            let mx = dx + (dw - ms.w) / 2;
            let rect: Rectangle<i32, Logical> = Rectangle::new(Point::from((mx, text_y)), ms);
            let physical = rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, msg.clone(), physical.loc, physical, None, scale);
        }

        if let Some(label) = &self.allow_label {
            let ls = label.size();
            let lx = dx + PADDING + (allow_w - ls.w) / 2;
            let ly = btn_y + (BUTTON_HEIGHT - ls.h) / 2;
            let rect: Rectangle<i32, Logical> = Rectangle::new(Point::from((lx, ly)), ls);
            let physical = rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, label.clone(), physical.loc, physical, None, scale);
        }

        if let Some(label) = &self.deny_label {
            let ls = label.size();
            let lx = dx + dw / 2 + PADDING / 2 + (half_w - ls.w) / 2;
            let ly = btn_y + (BUTTON_HEIGHT - ls.h) / 2;
            let rect: Rectangle<i32, Logical> = Rectangle::new(Point::from((lx, ly)), ls);
            let physical = rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, label.clone(), physical.loc, physical, None, scale);
        }

        // Source options (screencast output picker): labels, then fills — the
        // selected one reuses the accent fill, the rest the neutral one.
        for (i, rect) in l.options.iter().enumerate() {
            if let Some(label) = self.option_labels.get(i) {
                let ls = label.size();
                let lx = rect.loc.x + (rect.size.w - ls.w) / 2;
                let ly = rect.loc.y + (rect.size.h - ls.h) / 2;
                let lrect: Rectangle<i32, Logical> = Rectangle::new(Point::from((lx, ly)), ls);
                let physical = lrect.to_physical_precise_round(scale);
                ForgeElement::add_element(textures, label.clone(), physical.loc, physical, None, scale);
            }
        }
        for (i, rect) in l.options.iter().enumerate() {
            let fill = if i == self.selected { &self.allow_bg } else { &self.deny_bg };
            let physical = rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, fill.clone(), physical.loc, physical, None, scale);
        }

        let physical_allow = l.allow.to_physical_precise_round(scale);
        ForgeElement::add_element(textures, self.allow_bg.clone(), physical_allow.loc, physical_allow, None, scale);

        if l.deny.size.w > 0 {
            let physical_deny = l.deny.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, self.deny_bg.clone(), physical_deny.loc, physical_deny, None, scale);
        }

        let physical_dialog = l.dialog.to_physical_precise_round(scale);
        ForgeElement::add_element(textures, self.dialog_bg.clone(), physical_dialog.loc, physical_dialog, None, scale);

        let overlay_rect: Rectangle<i32, Logical> = Rectangle::new(Point::from((0, 0)), size);
        let physical_overlay = overlay_rect.to_physical_precise_round(scale);
        ForgeElement::add_element(textures, self.overlay_bg.clone(), physical_overlay.loc, physical_overlay, None, scale);
    }
}
