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

use crate::{
    backend::{self, wayland::WaylandUpdate},
    fl,
};
use cctk::sctk::reexports::calloop;
use cosmic::{
    Element, Task, app,
    applet::{
        menu_button, padded_control,
        token::subscription::{TokenRequest, TokenUpdate, activation_token_subscription},
    },
    cctk::sctk::reexports::calloop::channel,
    cosmic_config::{self, CosmicConfigEntry},
    cosmic_theme::{CosmicPalette, Spacing, ThemeBuilder},
    iced::{
        Length, Subscription,
        platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup},
        window,
    },
    surface,
    theme::{self, CosmicTheme},
    widget::{Column, divider, text, toggler},
};

use cosmic_settings_a11y_manager_subscription::{
    self as cosmic_a11y_manager, AccessibilityEvent, AccessibilityRequest, ColorFilter,
};
use cosmic_settings_accessibility_subscription::{self as accessibility};
use std::sync::LazyLock;
use tokio::sync::mpsc::UnboundedSender;

pub fn run() -> cosmic::iced::Result {
    cosmic::applet::run::<CosmicA11yApplet>(())
}

#[derive(Clone, Default)]
struct CosmicA11yApplet {
    core: cosmic::app::Core,
    high_contrast: Option<bool>,
    reader_enabled: bool,
    magnifier_enabled: bool,
    inverted_colors_enabled: bool,
    popup: Option<window::Id>,
    dbus_sender: Option<UnboundedSender<accessibility::Request>>,
    wayland_sender: Option<calloop::channel::Sender<AccessibilityRequest>>,
    wayland_protocol_version: Option<u32>,
    token_tx: Option<channel::Sender<TokenRequest>>,
    screen_filter_active: bool,
}

#[derive(Debug, Clone)]
enum Message {
    TogglePopup,
    CloseRequested(window::Id),
    HighContrastEnabled(bool),
    ScreenReaderEnabled(bool),
    MagnifierEnabled(bool),
    InvertedColorsEnabled(bool),
    FilterColorsEnabled(bool),
    Token(TokenUpdate),
    OpenSettings,
    DBusUpdate(accessibility::Response),
    WaylandUpdate(WaylandUpdate),
    Surface(surface::Action),
}

impl cosmic::Application for CosmicA11yApplet {
    type Message = Message;
    type Executor = cosmic::SingleThreadExecutor;
    type Flags = ();
    const APP_ID: &'static str = "com.system76.CosmicAppletA11y";

    fn init(core: cosmic::app::Core, _flags: Self::Flags) -> (Self, app::Task<Self::Message>) {
        (
            Self {
                core,
                token_tx: None,

                ..Default::default()
            },
            Task::none(),
        )
    }

    fn core(&self) -> &cosmic::app::Core {
        &self.core
    }

    fn core_mut(&mut self) -> &mut cosmic::app::Core {
        &mut self.core
    }

    fn update(&mut self, message: Self::Message) -> app::Task<Self::Message> {
        match message {
            Message::ScreenReaderEnabled(enabled) => {
                if let Some(tx) = &self.dbus_sender {
                    self.reader_enabled = enabled;
                    let _ = tx.send(accessibility::Request::ScreenReader(enabled));
                } else {
                    self.reader_enabled = false;
                }
            }
            Message::MagnifierEnabled(enabled) => {
                if let Some(tx) = &self.wayland_sender {
                    self.magnifier_enabled = enabled;
                    let _ = tx.send(AccessibilityRequest::Magnifier(enabled));
                } else {
                    self.magnifier_enabled = false;
                }
            }
            Message::InvertedColorsEnabled(enabled) => {
                if let Some(tx) = &self.wayland_sender {
                    self.inverted_colors_enabled = enabled;
                    let _ = tx.send(AccessibilityRequest::ScreenFilter {
                        inverted: enabled,
                        filter: None,
                    });
                } else {
                    self.inverted_colors_enabled = false;
                }
            }
            Message::FilterColorsEnabled(enabled) => {
                if let Some(sender) = self.wayland_sender.as_ref() {
                    self.screen_filter_active = enabled;
                    let _ = sender.send(AccessibilityRequest::ScreenFilter {
                        inverted: self.inverted_colors_enabled,
                        filter: enabled.then_some(ColorFilter::Unknown),
                    });
                }
            }
            Message::TogglePopup => {
                if let Some(p) = self.popup.take() {
                    return cosmic::surface::surface_task(cosmic::surface::action::destroy_popup(
                        p,
                    ));
                } else {
                    return cosmic::surface::surface_task(cosmic::surface::action::app_popup(
                        |_| Default::default(),
                        |app: &mut CosmicA11yApplet| {
                            let new_id = window::Id::unique();
                            app.popup.replace(new_id);

                            let popup_settings = app.core.applet.get_popup_settings(
                                app.core.main_window_id().unwrap(),
                                new_id,
                                Some((1, 1)),
                                None,
                                None,
                            );
                            popup_settings
                        },
                        None,
                    ));
                }
            }
            Message::CloseRequested(id) => {
                if Some(id) == self.popup {
                    self.popup = None;
                }
            }
            Message::HighContrastEnabled(enabled) => {
                if self.core.system_theme().cosmic().is_high_contrast == enabled
                    || self.high_contrast.is_some_and(|hc| hc == enabled)
                {
                    return Task::none();
                }

                self.high_contrast = Some(enabled);

                _ = std::thread::spawn(move || {
                    let set_hc = |is_dark: bool| {
                        let builder_config = if is_dark {
                            ThemeBuilder::dark_config()?
                        } else {
                            ThemeBuilder::light_config()?
                        };
                        let mut builder = match ThemeBuilder::get_entry(&builder_config) {
                            Ok(b) => b,
                            Err((errs, b)) => {
                                tracing::warn!("{errs:?}");
                                b
                            }
                        };

                        builder.palette = if is_dark {
                            if enabled {
                                CosmicPalette::HighContrastDark(builder.palette.inner())
                            } else {
                                CosmicPalette::Dark(builder.palette.inner())
                            }
                        } else if enabled {
                            CosmicPalette::HighContrastLight(builder.palette.inner())
                        } else {
                            CosmicPalette::Light(builder.palette.inner())
                        };
                        builder.write_entry(&builder_config)?;

                        let new_theme = builder.build();

                        let theme_config = if is_dark {
                            CosmicTheme::dark_config()?
                        } else {
                            CosmicTheme::light_config()?
                        };

                        new_theme.write_entry(&theme_config)?;

                        Result::<(), cosmic_config::Error>::Ok(())
                    };
                    if let Err(err) = set_hc(true) {
                        tracing::warn!("{err:?}");
                    }
                    if let Err(err) = set_hc(false) {
                        tracing::warn!("{err:?}");
                    }
                });
            }
            Message::OpenSettings => {
                let exec = "cosmic-settings accessibility".to_string();
                if let Some(tx) = self.token_tx.as_ref() {
                    let _ = tx.send(TokenRequest {
                        app_id: Self::APP_ID.to_string(),
                        exec,
                    });
                } else {
                    tracing::error!("Wayland tx is None");
                }
            }
            Message::Token(u) => match u {
                TokenUpdate::Init(tx) => {
                    self.token_tx = Some(tx);
                }
                TokenUpdate::Finished => {
                    self.token_tx = None;
                }
                TokenUpdate::ActivationToken { token, .. } => {
                    let mut cmd = std::process::Command::new("cosmic-settings");
                    cmd.arg("accessibility");
                    if let Some(token) = token {
                        cmd.env("XDG_ACTIVATION_TOKEN", &token);
                        cmd.env("DESKTOP_STARTUP_ID", &token);
                    }
                    tokio::spawn(cosmic::process::spawn(cmd));
                }
            },
            Message::DBusUpdate(update) => match update {
                accessibility::Response::Error(err) => {
                    tracing::error!("{err}");
                    let _ = self.dbus_sender.take();
                    self.reader_enabled = false;
                }
                accessibility::Response::ScreenReader(enabled) => {
                    self.reader_enabled = enabled;
                }
                accessibility::Response::Init(enabled, tx) => {
                    self.reader_enabled = enabled;
                    self.dbus_sender = Some(tx);
                }
                _ => (),
            },
            Message::WaylandUpdate(update) => match update {
                WaylandUpdate::Errored => {
                    tracing::error!("Wayland error");
                    let _ = self.wayland_sender.take();
                    self.wayland_protocol_version = None;
                    self.magnifier_enabled = false;
                    self.inverted_colors_enabled = false;
                }
                WaylandUpdate::State(AccessibilityEvent::Bound(ver)) => {
                    self.wayland_protocol_version = Some(ver);
                }
                WaylandUpdate::State(AccessibilityEvent::Magnifier(enabled)) => {
                    self.magnifier_enabled = enabled;
                }
                WaylandUpdate::State(AccessibilityEvent::ScreenFilter { inverted, .. }) => {
                    self.inverted_colors_enabled = inverted;
                }
                WaylandUpdate::State(AccessibilityEvent::Closed) => {
                    self.screen_filter_active = false;
                    self.wayland_sender = None;
                    self.wayland_protocol_version = None;
                }
                WaylandUpdate::Started(tx) => {
                    self.wayland_sender = Some(tx);
                }
            },
            Message::Surface(a) => {
                return cosmic::task::message(cosmic::Action::Cosmic(
                    cosmic::app::Action::Surface(a),
                ));
            }
        }
        Task::none()
    }

    fn view(&self) -> Element<'_, Message> {
        self.core
            .applet
            .icon_button("preferences-desktop-accessibility-symbolic")
            .on_press_down(Message::TogglePopup)
            .into()
    }

    fn view_window(&self, _id: window::Id) -> Element<'_, Message> {
        let Spacing {
            space_xxs, space_s, ..
        } = theme::active().cosmic().spacing;

        let reader_toggle = padded_control(
            toggler(self.reader_enabled)
                .on_toggle(Message::ScreenReaderEnabled)
                .text_size(14)
                .width(Length::Fill)
                .label(fl!("screen-reader")),
        );
        let magnifier_toggle = padded_control(
            toggler(self.magnifier_enabled)
                .on_toggle(Message::MagnifierEnabled)
                .text_size(14)
                .width(Length::Fill)
                .label(fl!("magnifier")),
        );
        let invert_colors_toggle = padded_control(
            toggler(self.inverted_colors_enabled)
                .on_toggle(Message::InvertedColorsEnabled)
                .text_size(14)
                .width(Length::Fill)
                .label(fl!("invert-colors")),
        );

        let hc_colors_toggle = padded_control(
            toggler(
                self.high_contrast
                    .unwrap_or(self.core.system_theme().cosmic().is_high_contrast),
            )
            .on_toggle(Message::HighContrastEnabled)
            .label(fl!("high-contrast"))
            .text_size(14)
            .width(Length::Fill),
        );

        let filter_colors_toggle = padded_control(
            toggler(self.screen_filter_active)
                .on_toggle(Message::FilterColorsEnabled)
                .label(fl!("filter-colors"))
                .width(Length::Fill)
                .text_size(14),
        );

        let content_list = Column::with_capacity(5)
            .push(reader_toggle)
            .push_maybe(
                self.wayland_protocol_version
                    .is_some()
                    .then_some(magnifier_toggle),
            )
            .push_maybe(
                self.wayland_protocol_version
                    .is_some_and(|ver| ver >= 2)
                    .then_some(invert_colors_toggle),
            )
            .push_maybe(
                self.wayland_protocol_version
                    .is_some_and(|ver| ver >= 3)
                    .then_some(filter_colors_toggle),
            )
            .push(hc_colors_toggle)
            .push(padded_control(divider::horizontal::default()).padding([space_xxs, space_s]))
            .push(menu_button(text::body(fl!("settings"))).on_press(Message::OpenSettings))
            .padding([8, 0]);

        self.core.applet.popup_container(content_list).into()
    }

    fn subscription(&self) -> Subscription<Message> {
        Subscription::batch([
            accessibility::subscription().map(Message::DBusUpdate),
            backend::wayland::a11y_subscription().map(Message::WaylandUpdate),
            activation_token_subscription(0).map(Message::Token),
        ])
    }

    fn on_close_requested(&self, id: window::Id) -> Option<Message> {
        Some(Message::CloseRequested(id))
    }

    fn style(&self) -> Option<cosmic::iced::theme::Style> {
        Some(cosmic::applet::style())
    }
}
