//! A small reusable modal: a title, a message and one or two buttons, used
//! by the portal to obtain user consent before a screenshot or a screencast
//! and by the power manager's battery notices. This module owns the request
//! queue and reply channels; `view` owns layout, hit-testing and rendering.

mod view;

use std::collections::VecDeque;

use crate::drawing::{RenderTexture, Texture};
use crate::portal_dbus::PortalReply;

/// Cap on queued consent requests, so a client cannot flood the dialog.
const MAX_CONFIRM_QUEUE: usize = 8;

#[derive(Debug)]
pub enum ConfirmKind {
    Screenshot(async_channel::Sender<PortalReply>),
    Screencast {
        /// The portal session that will own the cast.
        session: String,
        reply: async_channel::Sender<Option<u32>>,
    },
    PickColor(async_channel::Sender<PortalReply>),
    /// Informational; the single button only dismisses it.
    Notice,
    /// Battery suspend countdown: allow postpones, deny suspends now.
    BatteryCritical,
    /// Power key held with no shell to show a menu: allow powers off.
    PowerOff,
}

#[derive(Debug)]
pub struct ConfirmRequest {
    pub title: String,
    pub message: String,
    /// Source choices (screencast output picker); empty = no choice offered.
    pub options: Vec<String>,
    pub kind: ConfirmKind,
    pub allow_label: String,
    /// `None` gives the dialog a single full-width button.
    pub deny_label: Option<String>,
}

impl ConfirmRequest {
    pub fn portal(message: String, options: Vec<String>, kind: ConfirmKind) -> Self {
        Self {
            title: "Permission Request".into(),
            message,
            options,
            kind,
            allow_label: "Allow".into(),
            deny_label: Some("Deny".into()),
        }
    }

    pub fn notice(title: &str, message: String) -> Self {
        Self {
            title: title.into(),
            message,
            options: Vec::new(),
            kind: ConfirmKind::Notice,
            allow_label: "OK".into(),
            deny_label: None,
        }
    }

    pub fn power_off() -> Self {
        Self {
            title: "Power Off".into(),
            message: "Power off the device?".into(),
            options: Vec::new(),
            kind: ConfirmKind::PowerOff,
            allow_label: "Power off".into(),
            deny_label: Some("Cancel".into()),
        }
    }
}

#[derive(Debug)]
pub struct ConfirmDialog {
    pub visible: bool,
    queue: VecDeque<ConfirmRequest>,
    selected: usize,
    dirty: bool,
    last_scale: f64,

    overlay_bg: RenderTexture,
    dialog_bg: RenderTexture,
    allow_bg: RenderTexture,
    deny_bg: RenderTexture,
    title_texture: Option<RenderTexture>,
    message_texture: Option<RenderTexture>,
    allow_label: Option<RenderTexture>,
    deny_label: Option<RenderTexture>,
    option_labels: Vec<RenderTexture>,
}

impl ConfirmDialog {
    pub fn new() -> Self {
        Self {
            visible: false,
            queue: VecDeque::new(),
            selected: 0,
            dirty: true,
            last_scale: 0.0,
            overlay_bg: RenderTexture::new(Texture::from_color(view::OVERLAY_BG, false)),
            dialog_bg: RenderTexture::new(Texture::from_color(view::DIALOG_BG, true)),
            allow_bg: RenderTexture::new(Texture::from_color(view::ALLOW_BG, true)),
            deny_bg: RenderTexture::new(Texture::from_color(view::DENY_BG, true)),
            title_texture: None,
            message_texture: None,
            allow_label: None,
            deny_label: None,
            option_labels: Vec::new(),
        }
    }

    pub fn push(&mut self, req: ConfirmRequest) {
        // Drop past the cap: `req`'s reply Sender drops with it, so the caller
        // is cleanly denied rather than left waiting.
        if self.queue.len() >= MAX_CONFIRM_QUEUE {
            return;
        }
        if self.queue.is_empty() {
            self.selected = 0;
        }
        self.queue.push_back(req);
        self.visible = true;
        self.dirty = true;
    }

    /// Pop the frontmost request when it has been resolved, updating visibility.
    pub fn take_front(&mut self) -> Option<ConfirmRequest> {
        let req = self.queue.pop_front();
        self.visible = !self.queue.is_empty();
        self.selected = 0;
        self.dirty = true;
        req
    }

    /// The index into the front request's `options` the user picked.
    pub fn selected(&self) -> usize {
        self.selected
    }

    /// Drop every queued request of the given kind.
    pub fn remove_if(&mut self, pred: impl Fn(&ConfirmKind) -> bool) {
        let before = self.queue.len();
        self.queue.retain(|r| !pred(&r.kind));
        if self.queue.len() != before {
            self.visible = !self.queue.is_empty();
            self.selected = 0;
            self.dirty = true;
        }
    }

    fn front_has_deny(&self) -> bool {
        self.queue.front().is_some_and(|r| r.deny_label.is_some())
    }

    fn front_option_count(&self) -> usize {
        self.queue.front().map_or(0, |r| r.options.len())
    }
}
