// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only

use cosmic::{
    Application,
    applet::{menu_button, token::subscription::TokenRequest},
    cctk::sctk::reexports::calloop,
    iced,
    widget::icon::{self, IconFallback},
};
use std::path::{Path, PathBuf};

use crate::subscriptions::status_notifier_item::{IconUpdate, Layout, StatusNotifierItem};

#[derive(Clone, Debug)]
pub enum Msg {
    Layout(Result<Layout, String>),
    Icon(IconUpdate),
    Click(i32, bool),
    ClickToken(String),
}

pub struct State {
    pub item: StatusNotifierItem,
    layout: Option<Layout>,
    expanded: Option<i32>,
    // TODO handle icon with multiple sizes?
    icon_handle: icon::Handle,
    icon_theme_path: Option<PathBuf>,
    click_event: Option<(i32, bool)>,
}

impl State {
    pub fn new(item: StatusNotifierItem) -> (Self, iced::Task<Msg>) {
        (
            Self {
                item,
                layout: None,
                expanded: None,
                icon_handle: icon::from_name("application-default")
                    .prefer_svg(true)
                    .handle(),
                icon_theme_path: None,
                click_event: None,
            },
            iced::Task::none(),
        )
    }

    pub fn update(
        &mut self,
        message: Msg,
        menu_id: usize,
        token_tx: Option<&calloop::channel::Sender<TokenRequest>>,
    ) -> iced::Task<Msg> {
        match message {
            Msg::Layout(layout) => {
                match layout {
                    Ok(layout) => {
                        self.layout = Some(layout);
                    }
                    Err(err) => eprintln!("Error getting layout from icon: {err}"),
                }
                iced::Task::none()
            }
            Msg::Icon(update) => {
                let icon_name = update.name.unwrap_or_default();
                self.icon_theme_path = update.theme_path;

                // Use the icon pixmap if an icon was not defined by name.
                if icon_name.is_empty() {
                    let icon_pixmap = update.pixmap.and_then(|icons| icons
                        .into_iter()
                        .max_by_key(|i| (i.width, i.height))
                        .map(|mut i| {
                            if i.width <= 0 || i.height <= 0 || i.bytes.is_empty() {
                                // App sent invalid icon data during initialization - show placeholder until NewIcon signal
                                tracing::debug!("Skipping invalid icon: {}x{} with {} bytes, app may still be initializing",
                                        i.width, i.height, i.bytes.len());
                                return icon::from_name("dialog-question").symbolic(true).handle();
                            }
                            // Convert ARGB to RGBA
                            for pixel in i.bytes.chunks_exact_mut(4) {
                                pixel.rotate_left(1);
                            }
                            icon::from_raster_pixels(i.width as u32, i.height as u32, i.bytes)
                        }));

                    if let Some(icon) = icon_pixmap {
                        self.icon_handle = icon.clone();
                        return iced::Task::none();
                    }
                }

                // Load icon by path if the name is a path.
                self.icon_handle = if Path::new(&icon_name).exists() {
                    icon::from_path(Path::new(&icon_name).to_path_buf()).symbolic(true)
                } else {
                    let mut builder = icon::from_name(icon_name).prefer_svg(true).fallback(Some(
                        IconFallback::Names(vec![
                            "application-default".into(),
                            "application-x-executable".into(),
                        ]),
                    ));

                    if let Some(ref theme_path) = self.icon_theme_path {
                        builder = builder.with_extra_paths(vec![theme_path.clone()]);
                    }

                    builder.handle()
                };

                iced::Task::none()
            }
            Msg::Click(id, is_submenu) => {
                if let Some(token_tx) = token_tx {
                    _ = token_tx.send(TokenRequest {
                        app_id: super::app::App::APP_ID.to_string(),
                        exec: menu_id.to_string(),
                    });
                }
                self.click_event = Some((id, is_submenu));
                iced::Task::none()
            }
            Msg::ClickToken(token) => {
                let Some((id, is_submenu)) = self.click_event else {
                    return iced::Task::none();
                };
                let item_proxy = self.item.item_proxy().clone();

                let Some(menu_proxy) = self.item.menu_proxy().cloned() else {
                    return iced::Task::none();
                };
                tokio::spawn(async move {
                    let _ = item_proxy.provide_xdg_activation_token(token).await;
                    let _ = menu_proxy.event(id, "clicked", &0.into(), 0).await;
                });
                if is_submenu {
                    self.expanded = if self.expanded.is_none() {
                        Some(id)
                    } else {
                        None
                    };
                } else {
                    // TODO: Close menu?
                }
                iced::Task::none()
            }
        }
    }

    pub fn name(&self) -> &str {
        self.item.name()
    }

    pub fn icon_handle(&self) -> &icon::Handle {
        &self.icon_handle
    }

    pub fn popup_view(&self) -> cosmic::Element<'_, Msg> {
        if let Some(layout) = self.layout.as_ref() {
            layout_view(layout, self.expanded)
        } else {
            iced::widget::text("").into()
        }
    }

    pub fn subscription(&self, is_open: bool) -> iced::Subscription<Msg> {
        let mut subscriptions = vec![self.item.icon_subscription().map(Msg::Icon)];

        if is_open {
            subscriptions.push(self.item.layout_subscription().map(Msg::Layout));
        }

        iced::Subscription::batch(subscriptions)
    }

    pub fn opened(&self) {
        let Some(menu_proxy) = self.item.menu_proxy().cloned() else {
            return;
        };
        tokio::spawn(async move {
            let _ = menu_proxy.event(0, "opened", &0i32.into(), 0).await;
            let _ = menu_proxy.about_to_show(0).await;
        });
    }

    pub fn closed(&self) {
        let Some(menu_proxy) = self.item.menu_proxy().cloned() else {
            return;
        };
        tokio::spawn(async move {
            let _ = menu_proxy.event(0, "closed", &0i32.into(), 0).await;
        });
    }
}

fn layout_view(layout: &Layout, expanded: Option<i32>) -> cosmic::Element<'_, Msg> {
    iced::widget::column(layout.children().iter().filter_map(|i| {
        if !i.visible() {
            None
        } else if i.type_() == Some("separator") {
            Some(iced::widget::rule::horizontal(2).into())
        } else if let Some(label) = i.label() {
            // Strip _ when not doubled
            // TODO: interpret as "access key"? And label with underline.
            let mut is_underscore = false;
            let label = label
                .chars()
                .filter(|c| {
                    let prev_is_underscore = is_underscore;
                    is_underscore = !is_underscore && *c == '_';
                    *c != '_' || prev_is_underscore
                })
                .collect::<String>();

            let is_submenu = i.children_display() == Some("submenu");
            let is_expanded = expanded == Some(i.id());

            let text = iced::widget::text(label).width(iced::Length::Fill);

            let mut children: Vec<cosmic::Element<_>> = vec![text.into()];
            if is_submenu {
                let icon = cosmic::widget::icon::from_name(if is_expanded {
                    "go-down-symbolic"
                } else {
                    "go-next-symbolic"
                })
                .size(14)
                .symbolic(true);
                children.push(icon.into());
            }
            if let Some(icon_data) = i.icon_data() {
                let handle = iced::widget::image::Handle::from_bytes(icon_data.to_vec());
                children.insert(0, iced::widget::Image::new(handle).into());
            } else if let Some(icon_name) = i.icon_name() {
                let icon = cosmic::widget::icon::from_name(icon_name)
                    .size(14)
                    .symbolic(true);
                children.insert(0, icon.into());
            }
            if i.toggle_state() == Some(1) {
                let icon = cosmic::widget::icon::from_name("emblem-ok-symbolic")
                    .size(14)
                    .symbolic(true);
                children.push(icon.into());
            }

            let mut button = row_button(children);
            if i.enabled() {
                button = button.on_press(Msg::Click(i.id(), is_submenu));
            }

            if is_submenu && is_expanded {
                Some(
                    iced::widget::column![
                        button,
                        // XXX nested
                        iced::widget::container(layout_view(i, None)).padding(iced::Padding {
                            left: 12.,
                            ..iced::Padding::ZERO
                        })
                    ]
                    .into(),
                )
            } else {
                Some(button.into())
            }
        } else {
            None
        }
    }))
    .into()
}

fn row_button(content: Vec<cosmic::Element<Msg>>) -> cosmic::widget::Button<Msg> {
    menu_button(
        iced::widget::Row::with_children(content)
            .spacing(8)
            .align_y(iced::Alignment::Center)
            .width(iced::Length::Fill),
    )
}
