use super::ContentPage;
use super::article_list_mode::ArticleListMode;
use crate::app::App;
use crate::article_list::{ArticleList, ArticleListLoadQueue, ArticleListModel};
use glib::{Binding, Properties, clone, subclass};
use gtk4::{
    Accessible, Box, Buildable, CompositeTemplate, ConstraintTarget, SearchBar, SearchEntry, Stack,
    StackTransitionType, ToggleButton, Widget, prelude::*, subclass::prelude::*,
};
use libadwaita::ViewStack;
use news_flash::models::ArticleID;
use std::cell::{Cell, RefCell};

mod imp {
    use super::*;

    #[derive(Debug, Default, CompositeTemplate, Properties)]
    #[properties(wrapper_type = super::ArticleListColumn)]
    #[template(file = "data/resources/ui_templates/article_list/column.blp")]
    pub struct ArticleListColumn {
        #[template_child]
        pub sync_stack: TemplateChild<Stack>,
        #[template_child]
        pub mark_all_read_stack: TemplateChild<Stack>,

        #[template_child]
        pub search_button: TemplateChild<ToggleButton>,
        #[template_child]
        pub search_entry: TemplateChild<SearchEntry>,
        #[template_child]
        pub search_bar: TemplateChild<SearchBar>,
        #[template_child]
        pub view_switcher_stack: TemplateChild<ViewStack>,
        #[template_child]
        pub article_list: TemplateChild<ArticleList>,

        #[property(
            name = "load-queue",
            type = ArticleListLoadQueue,
            get = |this: &ArticleListColumn| this.load_queue.get()
        )]
        #[template_child]
        pub load_queue: TemplateChild<ArticleListLoadQueue>,

        #[property(get, set = Self::set_mode, builder(ArticleListMode::All))]
        pub mode: Cell<ArticleListMode>,

        #[property(get, set, name = "show-sidebar")]
        pub show_sidebar: Cell<bool>,

        #[property(get, set = Self::set_search_term, name = "search-term")]
        pub search_term: RefCell<String>,

        #[property(get, set, name = "search-focused")]
        pub search_focused: Cell<bool>,

        #[property(get, set, nullable)]
        pub title: RefCell<Option<String>>,

        #[property(get, set, nullable)]
        pub subtitle: RefCell<Option<String>>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for ArticleListColumn {
        const NAME: &'static str = "ArticleListColumn";
        type ParentType = gtk4::Box;
        type Type = super::ArticleListColumn;

        fn class_init(klass: &mut Self::Class) {
            klass.bind_template();
            klass.bind_template_callbacks();
        }

        fn instance_init(obj: &subclass::InitializingObject<Self>) {
            obj.init_template();
        }
    }

    #[glib::derived_properties]
    impl ObjectImpl for ArticleListColumn {
        fn constructed(&self) {
            // workaround to get 'has_focus' of search entry
            let entry = self.search_entry.first_child().unwrap().next_sibling().unwrap();
            entry.connect_has_focus_notify(clone!(
                #[weak(rename_to = imp)]
                self,
                move |entry| {
                    imp.obj().set_search_focused(entry.has_focus());
                }
            ));

            self.search_bar.connect_entry(&*self.search_entry);
            self.obj()
                .bind_property("mode", &*self.view_switcher_stack, "visible-child-name")
                .bidirectional()
                .transform_to(Self::mode_to_stack_page)
                .transform_from(Self::stack_page_to_mode)
                .build();

            App::default()
                .bind_property("is-syncing", &*self.sync_stack, "visible-child-name")
                .transform_to(Self::bool_to_stack_page)
                .build();

            App::default()
                .bind_property("is-marking-all", &*self.mark_all_read_stack, "visible-child-name")
                .transform_to(Self::bool_to_stack_page)
                .build();

            self.parent_constructed();
        }
    }

    impl WidgetImpl for ArticleListColumn {}

    impl BoxImpl for ArticleListColumn {}

    #[gtk4::template_callbacks]
    impl ArticleListColumn {
        #[template_callback]
        fn on_new(&self, list: ArticleListModel) {
            self.article_list.update(list, true);
        }

        #[template_callback]
        fn on_update(&self, list: ArticleListModel) {
            self.article_list.update(list, false);
        }

        #[template_callback]
        fn on_extend(&self, list: ArticleListModel) {
            self.article_list.extend(list);
        }

        fn set_search_term(&self, new_search_term: String) {
            let current_search_term = self.search_term.replace(new_search_term.clone());
            let empty_search = new_search_term.is_empty();
            let update_article_list = current_search_term != new_search_term;

            if !empty_search {
                self.search_button.set_active(true);
            }

            if update_article_list {
                self.obj().new_list();
            }
        }

        fn mode_to_stack_page(_binding: &Binding, mode: ArticleListMode) -> Option<String> {
            let mode_str = match mode {
                ArticleListMode::All => "all",
                ArticleListMode::Unread => "unread",
                ArticleListMode::Marked => "marked",
            };
            Some(format!("{mode_str}_placeholder"))
        }

        fn stack_page_to_mode(_binding: &Binding, visible_child_name: &str) -> Option<ArticleListMode> {
            if visible_child_name.starts_with("all") {
                Some(ArticleListMode::All)
            } else if visible_child_name.starts_with("unread") {
                Some(ArticleListMode::Unread)
            } else if visible_child_name.starts_with("marked") {
                Some(ArticleListMode::Marked)
            } else {
                None
            }
        }

        fn bool_to_stack_page(_binding: &Binding, is_syncing: bool) -> Option<&str> {
            if is_syncing { Some("spinner") } else { Some("button") }
        }

        fn set_mode(&self, new_mode: ArticleListMode) {
            let old_mode = self.mode.get();
            self.mode.set(new_mode);

            let transition = self.calc_transition_type(&new_mode, &old_mode);
            self.article_list.set_transition(transition);
            self.load_queue.update_forced();

            let update_sidebar = match old_mode {
                ArticleListMode::All | ArticleListMode::Unread => matches!(new_mode, ArticleListMode::Marked),
                ArticleListMode::Marked => matches!(new_mode, ArticleListMode::All | ArticleListMode::Unread),
            };
            if update_sidebar {
                ContentPage::instance().update_sidebar();
            }
        }

        fn calc_transition_type(&self, new_mode: &ArticleListMode, old_mode: &ArticleListMode) -> StackTransitionType {
            match old_mode {
                ArticleListMode::All => match new_mode {
                    ArticleListMode::All => {}
                    ArticleListMode::Unread | ArticleListMode::Marked => return StackTransitionType::SlideLeft,
                },
                ArticleListMode::Unread => match new_mode {
                    ArticleListMode::All => return StackTransitionType::SlideRight,
                    ArticleListMode::Unread => {}
                    ArticleListMode::Marked => return StackTransitionType::SlideLeft,
                },
                ArticleListMode::Marked => match new_mode {
                    ArticleListMode::All | ArticleListMode::Unread => return StackTransitionType::SlideRight,
                    ArticleListMode::Marked => {}
                },
            }

            StackTransitionType::Crossfade
        }
    }
}

glib::wrapper! {
    pub struct ArticleListColumn(ObjectSubclass<imp::ArticleListColumn>)
        @extends Widget, Box,
        @implements Accessible, Buildable, ConstraintTarget;
}

impl Default for ArticleListColumn {
    fn default() -> Self {
        glib::Object::new::<Self>()
    }
}

impl ArticleListColumn {
    pub fn instance() -> Self {
        ContentPage::instance().imp().article_list_column.get()
    }

    pub fn update_list(&self) {
        self.imp().load_queue.update();
    }

    pub fn new_list(&self) {
        self.imp().load_queue.update_forced();
    }

    pub fn extend_list(&self) {
        self.imp().load_queue.extend();
    }

    pub fn set_custom_page_size(&self, page_size: Option<i64>) {
        self.imp().load_queue.set_custom_page_size(page_size);
    }

    pub fn set_custom_article_to_load(&self, article_id: Option<ArticleID>) {
        self.imp().load_queue.set_custom_article_to_load(article_id);
    }

    pub fn focus_search(&self) {
        let imp = self.imp();
        // shortcuts ignored when focues -> no need to hide seach bar on keybind (ESC still works)
        imp.search_button.set_active(true);
        imp.search_entry.grab_focus();
    }
}
