use crate::channels::{AsyncSenderExt, BroadcastReceiverExt};
use crate::clients::swaync;
use crate::config::CommonConfig;
use crate::image::IconButton;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::{module_impl, spawn};
use gtk::prelude::*;
use gtk::{Align, Label, Overlay};
use serde::Deserialize;
use tokio::sync::mpsc::Receiver;
use tracing::error;

#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "extras", derive(schemars::JsonSchema))]
#[serde(default)]
pub struct NotificationsModule {
    /// Whether to show the current notification count.
    ///
    /// **Default**: `true`
    show_count: bool,

    /// SwayNC state icons.
    ///
    /// See [icons](#icons).
    icons: Icons,

    /// See [common options](module-level-options#common-options).
    #[serde(flatten)]
    pub common: Option<CommonConfig>,
}

impl Default for NotificationsModule {
    fn default() -> Self {
        Self {
            show_count: true,
            icons: Icons::default(),
            common: Some(CommonConfig::default()),
        }
    }
}

#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "extras", derive(schemars::JsonSchema))]
#[serde(default)]
struct Icons {
    /// Icon to show when the panel is closed, with no notifications.
    ///
    /// **Default**: `󰍥`
    closed_none: String,

    /// Icon to show when the panel is closed, with notifications.
    ///
    /// **Default**: `󱥂`
    closed_some: String,

    /// Icon to show when the panel is closed, with DnD enabled.
    /// Takes higher priority than count-based icons.
    ///
    /// **Default**: `󱅯`
    closed_dnd: String,

    /// Icon to show when the panel is open, with no notifications.
    ///
    /// **Default**: `󰍡`
    open_none: String,

    /// Icon to show when the panel is open, with notifications.
    ///
    /// **Default**: `󱥁`
    open_some: String,

    /// Icon to show when the panel is open, with DnD enabled.
    /// Takes higher priority than count-based icons.
    ///
    /// **Default**: `󱅮`
    open_dnd: String,
}

impl Default for Icons {
    fn default() -> Self {
        Self {
            closed_none: "󰍥".to_string(),
            closed_some: "󱥂".to_string(),
            closed_dnd: "󱅯".to_string(),
            open_none: "󰍡".to_string(),
            open_some: "󱥁".to_string(),
            open_dnd: "󱅮".to_string(),
        }
    }
}

impl Icons {
    fn icon(&self, value: swaync::Event) -> &str {
        match (value.cc_open, value.count > 0, value.dnd) {
            (true, _, true) => &self.open_dnd,
            (true, true, false) => &self.open_some,
            (true, false, false) => &self.open_none,
            (false, _, true) => &self.closed_dnd,
            (false, true, false) => &self.closed_some,
            (false, false, false) => &self.closed_none,
        }
        .as_str()
    }
}

#[derive(Debug, Clone, Copy)]
pub enum UiEvent {
    ToggleVisibility,
}

impl Module<Overlay> for NotificationsModule {
    type SendMessage = swaync::Event;
    type ReceiveMessage = UiEvent;

    module_impl!("notifications");

    fn spawn_controller(
        &self,
        _info: &ModuleInfo,
        context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
        mut rx: Receiver<Self::ReceiveMessage>,
    ) -> color_eyre::Result<()>
    where
        <Self as Module<Overlay>>::SendMessage: Clone,
    {
        let client = context.try_client::<swaync::Client>()?;

        {
            let client = client.clone();
            let mut rx = client.subscribe();
            let tx = context.tx.clone();

            spawn(async move {
                let initial_state = client.state().await;

                match initial_state {
                    Ok(ev) => tx.send_update(ev).await,
                    Err(err) => error!("{err:?}"),
                }

                while let Ok(ev) = rx.recv().await {
                    tx.send_update(ev).await;
                }
            });
        }

        let tx = context.tx.clone();
        spawn(async move {
            while let Some(event) = rx.recv().await {
                match event {
                    UiEvent::ToggleVisibility => {
                        client.toggle_visibility().await;
                        tx.send_expect(ModuleUpdateEvent::ClosePopup).await;
                    }
                }
            }
        });

        Ok(())
    }

    fn into_widget(
        self,
        context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
        _info: &ModuleInfo,
    ) -> color_eyre::Result<ModuleParts<Overlay>>
    where
        <Self as Module<Overlay>>::SendMessage: Clone,
    {
        let overlay = Overlay::new();
        let button = IconButton::new(
            &self.icons.closed_none,
            16,
            &context.ironbar.image_provider(),
        );
        button.add_css_class("button");
        overlay.set_child(Some(&*button));

        // TODO: make halign/valign configurable
        let label = Label::builder()
            .label("0")
            .halign(Align::End)
            .valign(Align::Start)
            .build();

        if self.show_count {
            label.add_css_class("count");
            label.set_can_target(false);
            overlay.add_overlay(&label);
        }

        let ctx = context.controller_tx.clone();
        button.connect_clicked(move |_| {
            ctx.send_spawn(UiEvent::ToggleVisibility);
        });

        context.subscribe().recv_glib(&button, move |button, ev| {
            let icon = self.icons.icon(ev);
            button.set_label(icon);

            label.set_label(&ev.count.to_string());
            label.set_visible(self.show_count && ev.count > 0);
        });

        Ok(ModuleParts {
            widget: overlay,
            popup: None,
        })
    }
}
