use std::sync::{Arc, LazyLock, OnceLock};

use flume::{Sender, unbounded};
use gettextrs::gettext;
use gtk::{
    glib::{self, clone, object::ObjectExt, subclass::Signal},
    subclass::prelude::*,
};
use ksni::{Handle, MenuItem, TrayMethods, menu::StandardItem};
use tokio::sync::Mutex;
use tracing::error;

use crate::{
    app::{
        config::{APP_ID, APP_NAME},
        tray::config::ICON_FILE,
    },
    spawn_local,
};

#[derive(Default)]
pub struct Tray {
    handle: Arc<Mutex<Option<Handle<TrayIcon>>>>,
}

impl Tray {
    pub fn update(&self, state: bool) {
        let local_handle = self.handle.clone();
        tokio::spawn(async move {
            let handle_guard = local_handle.lock().await;
            if let Some(handle) = handle_guard.as_ref() {
                handle.update(|tray| tray.window_visible = state).await;
            }
        });
    }
}

#[glib::object_subclass]
impl ObjectSubclass for Tray {
    const NAME: &'static str = "Tray";
    type Type = super::Tray;
    type ParentType = glib::Object;
}

impl ObjectImpl for Tray {
    fn signals() -> &'static [Signal] {
        static SIGNALS: OnceLock<Vec<Signal>> = OnceLock::new();
        SIGNALS.get_or_init(|| {
            vec![
                Signal::builder("show").build(),
                Signal::builder("hide").build(),
                Signal::builder("quit").build(),
            ]
        })
    }

    fn constructed(&self) {
        self.parent_constructed();

        let (sender, receiver) = unbounded::<TrayEvent>();

        let tray_icon = TrayIcon {
            sender,
            window_visible: true,
        };

        let local_handle = self.handle.clone();
        tokio::spawn(async move {
            let mut handle_guard = local_handle.lock().await;
            *handle_guard = tray_icon
                .disable_dbus_name(true)
                .spawn()
                .await
                .inspect_err(|err| {
                    error!("Failed to create tray icon: {err}");
                })
                .ok()
        });

        spawn_local!(clone!(
            #[weak(rename_to = object)]
            self.obj(),
            async move {
                while let Ok(event) = receiver.recv_async().await {
                    match event {
                        TrayEvent::Show => object.emit_by_name::<()>("show", &[]),
                        TrayEvent::Hide => object.emit_by_name::<()>("hide", &[]),
                        TrayEvent::Quit => object.emit_by_name::<()>("quit", &[]),
                    }
                }
            }
        ));
    }
}

#[derive(Debug)]
pub enum TrayEvent {
    Show,
    Hide,
    Quit,
}

pub struct TrayIcon {
    sender: Sender<TrayEvent>,
    window_visible: bool,
}

impl ksni::Tray for TrayIcon {
    fn id(&self) -> String {
        APP_ID.into()
    }

    fn title(&self) -> String {
        APP_NAME.into()
    }

    fn icon_pixmap(&self) -> Vec<ksni::Icon> {
        static ICON: LazyLock<ksni::Icon> = LazyLock::new(|| {
            let (data, width, height) = load_image(ICON_FILE);

            ksni::Icon {
                width: width as i32,
                height: height as i32,
                data,
            }
        });

        vec![ICON.clone()]
    }

    fn menu(&self) -> Vec<MenuItem<Self>> {
        let sender_show = self.sender.clone();
        let sender_hide = self.sender.clone();
        let sender_quit = self.sender.clone();

        vec![
            StandardItem {
                label: gettext("Show"),
                visible: !self.window_visible,
                activate: Box::new(move |_| {
                    sender_show.send(TrayEvent::Show).ok();
                }),
                ..Default::default()
            }
            .into(),
            StandardItem {
                label: gettext("Hide"),
                visible: self.window_visible,
                activate: Box::new(move |_| {
                    sender_hide.send(TrayEvent::Hide).ok();
                }),
                ..Default::default()
            }
            .into(),
            StandardItem {
                label: gettext("Quit"),
                activate: Box::new(move |_| {
                    sender_quit.send(TrayEvent::Quit).ok();
                }),
                ..Default::default()
            }
            .into(),
        ]
    }
}

fn load_image(buffer: &[u8]) -> (Vec<u8>, u32, u32) {
    let image = image::load_from_memory(buffer)
        .expect("Failed to open icon path")
        .into_rgba8();

    let (width, height) = image.dimensions();
    let mut data = image.into_raw();

    for pixel in data.chunks_exact_mut(4) {
        pixel.rotate_right(1) // rgba to argb
    }

    (data, width, height)
}
