use gettextrs::*;
use crate::components::tag_pill::*;
use adw::prelude::*;

use glib::translate::IntoGlib;
use gtk::GridLayoutChild;
use relm4::{factory::FactoryVecDeque, gtk, prelude::{Controller, DynamicIndex}, Component, ComponentParts, ComponentSender, RelmWidgetExt};
#[cfg(feature = "smart-summary")]
use relm4::prelude::ComponentController;

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, glib::Enum)]
#[enum_type(name = "MicropubVisibility")]
pub enum Visibility {
    #[default]
    Public = 0,
    Private = 1,
}
impl std::fmt::Display for Visibility {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            Self::Public => "public",
            Self::Private => "private"
        })
    }
}

#[derive(Default, Debug, PartialEq, Eq)]
pub struct Post {
    pub name: Option<String>,
    pub summary: Option<String>,
    pub tags: Vec<String>,
    pub content: String,
    pub visibility: Visibility
}

impl From<Post> for microformats::types::Item {
    fn from(post: Post) -> Self {
        use microformats::types::{Item, Class, KnownClass, PropertyValue};
        let mut mf2 = Item::new(vec![Class::Known(KnownClass::Entry)]);

        if let Some(name) = post.name {
            mf2.properties.insert(
                "name".to_owned(), vec![PropertyValue::Plain(name)]
            );
        }

        if let Some(summary) = post.summary {
            mf2.properties.insert(
                "summary".to_owned(),
                vec![PropertyValue::Plain(summary)]
            );
        }

        if !post.tags.is_empty() {
            mf2.properties.insert(
                "category".to_string(),
                post.tags.into_iter().map(PropertyValue::Plain).collect()
            );
        }

        mf2.properties.insert(
            "visibility".to_string(),
            vec![PropertyValue::Plain(post.visibility.to_string())]
        );

        mf2.properties.insert(
            "content".to_string(),
            vec![PropertyValue::Plain(post.content)]
        );

        mf2
    }
}

#[tracker::track]
#[derive(Debug)]
pub(crate) struct PostEditor<E> {
    #[no_eq] smart_summary_busy_guard: Option<gtk::gio::ApplicationBusyGuard>,
    sending: bool,

    #[do_not_track] name_buffer: gtk::EntryBuffer,
    #[do_not_track] summary_buffer: gtk::EntryBuffer,
    #[do_not_track] content_buffer: gtk::TextBuffer,

    #[do_not_track] pending_tag_buffer: gtk::EntryBuffer,
    #[do_not_track] tags: relm4::factory::FactoryVecDeque<TagPill>,
    visibility: Visibility,

    #[do_not_track] wide_layout: gtk::GridLayout,

    #[cfg(feature = "smart-summary")]
    #[do_not_track] smart_summary: Controller<crate::components::SmartSummaryButton>,
    _err: std::marker::PhantomData<E>
}

impl<E> PostEditor<E> {
    fn busy_changed(&self) -> bool {
        self.changed(Self::sending() | Self::smart_summary_busy_guard())
    }
    fn busy(&self) -> bool {
        self.sending || self.smart_summary_busy_guard.is_some()
    }
}

#[derive(Debug)]
#[allow(private_interfaces)] // intentional
#[allow(clippy::manual_non_exhaustive)] // false positive
pub enum Input<E: std::error::Error + std::fmt::Debug + Send + 'static> {
    #[cfg(feature = "smart-summary")]
    #[doc(hidden)] SmartSummary(crate::components::smart_summary::Output),
    #[doc(hidden)] VisibilitySelected(Visibility),
    #[doc(hidden)] AddTagFromBuffer,
    #[doc(hidden)] RemoveTag(DynamicIndex),
    Submit,
    SubmitDone(glib::Uri),
    SubmitError(E)
}

#[relm4::component(pub)]
impl<E: std::error::Error + std::fmt::Debug + Send + 'static> Component for PostEditor<E> {
    #[cfg(feature = "smart-summary")]
    type Init = (soup::Session, Option<Post>);
    #[cfg(not(feature = "smart-summary"))]
    type Init = Option<Post>;
    type Output = Option<Post>;
    type Input = Input<E>;
    type CommandOutput = ();

    view! {
        #[root]
        #[name = "toast_overlay"]
        adw::ToastOverlay {
            #[name = "content_wrapper"]
            adw::BreakpointBin {
                set_width_request: 360,
                set_height_request: 200,

                gtk::ScrolledWindow {
                    #[name = "content"]
                    gtk::Box {
                        set_orientation: gtk::Orientation::Vertical,
                        set_spacing: 5,
                        set_margin_all: 5,

                        #[name = "name_label"]
                        gtk::Label {
                            set_markup: &gettext("Name"),
                            set_margin_horizontal: 10,
                            set_halign: gtk::Align::Start,
                            set_valign: gtk::Align::Center,
                        },
                        #[name = "name_field"]
                        gtk::Entry {
                            set_hexpand: true,
                            set_buffer: &model.name_buffer,
                            #[track = "model.changed(Self::sending())"]
                            set_sensitive: !model.sending,
                        },

                        #[name = "summary_label"]
                        gtk::Label {
                            set_markup: &gettext("Summary"),
                            set_margin_horizontal: 10,
                            set_halign: gtk::Align::Start,
                            set_valign: gtk::Align::Center,
                        },
                        #[name = "summary_field"]
                        gtk::Box {
                            set_orientation: gtk::Orientation::Horizontal,
                            add_css_class: "linked",

                            gtk::Entry {
                                set_hexpand: true,
                                set_buffer: &model.summary_buffer,
                                #[track = "model.busy_changed()"]
                                set_sensitive: !model.busy(),
                            },
                        },

                        #[name = "tag_label"]
                        gtk::Label {
                            set_markup: &gettext("Tags"),
                            set_margin_horizontal: 10,
                            set_halign: gtk::Align::Start,
                            set_valign: gtk::Align::Center,
                        },
                        #[name = "tag_holder"]
                        gtk::Box {
                            set_hexpand: true,
                            set_orientation: gtk::Orientation::Vertical,
                            set_spacing: 5,

                            gtk::Box {
                                add_css_class: "linked",
                                set_orientation: gtk::Orientation::Horizontal,

                                #[name = "pending_tag_entry"]
                                gtk::Entry {
                                    set_hexpand: true,
                                    set_width_request: 200,
                                    set_buffer: &model.pending_tag_buffer,
                                    connect_activate => Self::Input::AddTagFromBuffer,
                                    #[track = "model.changed(Self::sending())"]
                                    set_sensitive: !model.sending,
                                },
                                gtk::Button {
                                    set_icon_name: "list-add-symbolic",
                                    add_css_class: "suggested-action",
                                    connect_clicked => Self::Input::AddTagFromBuffer,
                                }
                            },
                        },

                        #[name = "tag_viewport"]
                        gtk::ScrolledWindow {
                            set_height_request: 32,
                            set_valign: gtk::Align::Center,

                            gtk::Viewport {
                                set_scroll_to_focus: true,
                                set_valign: gtk::Align::Center,

                                #[wrap(Some)]
                                set_child = model.tags.widget(),
                            }
                        },

                        #[name = "content_label"]
                        gtk::Label {
                            set_markup: &gettext("Content"),
                            set_halign: gtk::Align::Start,
                            set_valign: gtk::Align::Start,
                            set_margin_vertical: 10,
                            set_margin_horizontal: 10,
                        },

                        #[name = "content_textarea_wrapper"]
                        gtk::ScrolledWindow {
                            set_vexpand: true,
                            set_height_request: 200,
                            #[name = "content_textarea"]
                            gtk::TextView {
                                set_buffer: Some(&model.content_buffer),
                                set_hexpand: true,
                                #[iterate]
                                add_css_class: &["frame", "view"],

                                set_monospace: true,
                                set_wrap_mode: gtk::WrapMode::Word,
                                set_vscroll_policy: gtk::ScrollablePolicy::Natural,

                                set_left_margin: 8,
                                set_right_margin: 8,
                                set_top_margin: 8,
                                set_bottom_margin: 8,

                                #[track = "model.changed(Self::sending())"]
                                set_sensitive: !model.sending
                            }
                        },

                        #[name = "misc_prop_wrapper"]
                        gtk::Box {
                            set_hexpand: true,

                            gtk::FlowBox {
                                set_hexpand: false,
                                set_orientation: gtk::Orientation::Horizontal,
                                set_homogeneous: false,
                                set_column_spacing: 0,
                                set_min_children_per_line: 2,
                                set_max_children_per_line: 6,
                                set_selection_mode: gtk::SelectionMode::None,

                                append = &gtk::FlowBoxChild {
                                    set_halign: gtk::Align::Start,

                                    gtk::Box {
                                        set_spacing: 5,

                                        #[name = "visibility_label"]
                                        gtk::Label {
                                            set_markup: &gettext("Visibility"),
                                            set_halign: gtk::Align::Start,
                                            set_valign: gtk::Align::Start,
                                            set_margin_vertical: 10,
                                            set_margin_horizontal: 10,
                                        },
                                        #[name = "visibility_selector"]
                                        gtk::DropDown {
                                            set_model: Some(&visibility_model),
                                            set_hexpand: false,

                                            #[track = "model.changed(Self::sending())"]
                                            set_sensitive: !model.sending,

                                            connect_selected_item_notify[sender] => move |w| {
                                                if let Some(obj) = w.selected_item() {
                                                    let v = obj.downcast::<adw::EnumListItem>()
                                                    .unwrap()
                                                    .value();
                                                    let v = glib::EnumClass::new::<Visibility>()
                                                    .to_value(v)
                                                    .unwrap()
                                                    .get()
                                                    .unwrap();
                                                    sender.input(Self::Input::VisibilitySelected(v));
                                                }
                                            },
                                        },
                                    },
                                },
                            },
                        },
                    },
                },

                add_breakpoint = adw::Breakpoint::new(
                    adw::BreakpointCondition::new_length(
                        adw::BreakpointConditionLengthType::MinWidth,
                        512.0,
                        adw::LengthUnit::Px
                    )
                ) {
                    add_setter: (&content, "layout_manager", Some(&model.wide_layout.to_value())),
                    add_setter: (&name_label, "halign", Some(&gtk::Align::End.to_value())),
                    add_setter: (&summary_label, "halign", Some(&gtk::Align::End.to_value())),
                    add_setter: (&tag_label, "halign", Some(&gtk::Align::End.to_value())),
                    add_setter: (&content_label, "halign", Some(&gtk::Align::End.to_value())),
                    add_setter: (&pending_tag_entry, "hexpand", Some(&false.to_value())),
                },

            }
        }
    }

    fn init(
        init: Self::Init,
        root: Self::Root,
        sender: ComponentSender<Self>
    ) -> ComponentParts<Self> {
        #[cfg(feature = "smart-summary")]
        let (http, init) = init;

        let mut model = Self {
            smart_summary_busy_guard: None,
            sending: false,

            name_buffer: gtk::EntryBuffer::default(),
            summary_buffer: gtk::EntryBuffer::default(),
            content_buffer: gtk::TextBuffer::default(),
            pending_tag_buffer: gtk::EntryBuffer::default(),

            tags: FactoryVecDeque::builder()
                .launch({
                    let listbox = gtk::Box::default();
                    listbox.set_orientation(gtk::Orientation::Horizontal);
                    listbox.set_spacing(5);
                    listbox
                })
                .forward(
                    sender.input_sender(),
                    |del: TagPillDelete| Input::RemoveTag(del.0)
                ),
            visibility: Visibility::Public,

            wide_layout: gtk::GridLayout::new(),

            #[cfg(feature = "smart-summary")]
            smart_summary: crate::components::SmartSummaryButton::builder()
                .launch(http)
                .forward(sender.input_sender(), Input::SmartSummary),

            tracker: Default::default(),
            _err: std::marker::PhantomData,
        };

        let visibility_model = adw::EnumListModel::new(Visibility::static_type());

        let widgets = view_output!();

        #[cfg(feature = "smart-summary")]
        widgets.summary_field.append(model.smart_summary.widget());

        widgets.visibility_selector.set_expression(Some(
            gtk::ClosureExpression::new::<String>(
                [] as [gtk::Expression; 0],
                glib::closure::RustClosure::new(|v| {
                    let list_item = v[0].get::<adw::EnumListItem>().unwrap();
                    Some(gettext(list_item.name().as_str()).into())
                })
            )
        ));

        if let Some(post) = init {
            if let Some(name) = post.name {
                model.name_buffer.set_text(glib::GString::from(name));
            }
            if let Some(summary) = post.summary {
                model.summary_buffer.set_text(glib::GString::from(summary));
            }

            let mut tags = model.tags.guard();
            post.tags.into_iter().for_each(|t| { tags.push_back(t.into_boxed_str()); });

            model.content_buffer.set_text(&post.content);

            widgets.visibility_selector.set_selected(
                visibility_model.find_position(post.visibility.into_glib())
            );
            model.visibility = post.visibility;
        }

        let prev_layout = widgets.content.layout_manager().unwrap();
        let layout = &model.wide_layout;
        widgets.content.set_layout_manager(Some(layout.clone()));
        layout.set_column_homogeneous(false);
        layout.set_row_spacing(10);

        enum Row<'a> {
            TwoColumn(&'a gtk::Label, &'a gtk::Widget),
            Span(&'a gtk::Widget),
            SecondColumn(&'a gtk::Widget)
        }

        for (row, content) in [
            Row::TwoColumn(&widgets.name_label, widgets.name_field.upcast_ref::<gtk::Widget>()),
            Row::TwoColumn(&widgets.summary_label, widgets.summary_field.upcast_ref::<gtk::Widget>()),
            Row::TwoColumn(&widgets.tag_label, widgets.tag_holder.upcast_ref::<gtk::Widget>()),
            Row::SecondColumn(widgets.tag_viewport.upcast_ref::<gtk::Widget>()),
            Row::TwoColumn(&widgets.content_label, widgets.content_textarea_wrapper.upcast_ref::<gtk::Widget>()),
            Row::Span(widgets.misc_prop_wrapper.upcast_ref::<gtk::Widget>()),
        ].into_iter().enumerate() {
            match content {
                Row::TwoColumn(label, field) => {
                    let label_layout = layout.layout_child(label)
                        .downcast::<GridLayoutChild>()
                        .unwrap();
                    label_layout.set_row(row as i32);
                    label_layout.set_column(0);

                    let field_layout = layout.layout_child(field)
                        .downcast::<GridLayoutChild>()
                        .unwrap();
                    field_layout.set_row(row as i32);
                    field_layout.set_column(1);
                },
                Row::Span(widget) => {
                    let widget_layout = layout.layout_child(widget)
                        .downcast::<GridLayoutChild>()
                        .unwrap();
                    widget_layout.set_row(row as i32);
                    widget_layout.set_column_span(2);
                },
                Row::SecondColumn(widget) => {
                    let widget_layout = layout.layout_child(widget)
                        .downcast::<GridLayoutChild>()
                        .unwrap();
                    widget_layout.set_row(row as i32);
                    widget_layout.set_column(1);
                }
            }
        }

        widgets.content.set_layout_manager(Some(prev_layout));

        ComponentParts { model, widgets }
    }

    fn update_with_view(&mut self, widgets: &mut Self::Widgets, msg: Self::Input, sender: ComponentSender<Self>, root: &Self::Root) {
        self.reset();
        match msg {
            #[cfg(feature = "smart-summary")]
            Input::SmartSummary(crate::components::SmartSummaryOutput::Start) => {
                widgets.content_textarea.set_sensitive(false);
                if self.content_buffer.char_count() == 0 {
                    let _ = self.smart_summary.sender().send(
                        crate::components::SmartSummaryInput::Cancel
                    );
                } else {
                    let text = self.content_buffer.text(
                        &self.content_buffer.start_iter(),
                        &self.content_buffer.end_iter(),
                        false
                    );

                    self.set_smart_summary_busy_guard(
                        Some(relm4::main_adw_application().mark_busy())
                    );
                    if self.smart_summary.sender().send(
                        crate::components::SmartSummaryInput::Text(text.into())
                    ).is_ok() {
                        self.summary_buffer.set_text("");
                    }
                }
                widgets.content_textarea.set_sensitive(true);
            },
            #[cfg(feature = "smart-summary")]
            Input::SmartSummary(crate::components::SmartSummaryOutput::Chunk(text)) => {
                self.summary_buffer.insert_text(self.summary_buffer.length(), text);
            },
            #[cfg(feature = "smart-summary")]
            Input::SmartSummary(crate::components::SmartSummaryOutput::Done) => {
                self.set_smart_summary_busy_guard(None);
            },
            #[cfg(feature = "smart-summary")]
            Input::SmartSummary(crate::components::SmartSummaryOutput::Error(err)) => {
                self.set_smart_summary_busy_guard(None);

                let toast = adw::Toast::new(&gettext!("Smart Summary error: {}", err));
                toast.set_timeout(0);
                toast.set_priority(adw::ToastPriority::High);
                root.add_toast(toast);
            },
            Input::VisibilitySelected(vis) => {
                log::debug!("Changed visibility: {}", vis);
                self.visibility = vis;
            },
            Input::AddTagFromBuffer => {
                let tag = String::from(self.pending_tag_buffer.text());
                if !tag.is_empty() {
                    self.tags.guard().push_back(
                        tag.into_boxed_str()
                    );
                    self.pending_tag_buffer.set_text("");
                }
            },
            Input::RemoveTag(idx) => {
                self.tags.guard().remove(idx.current_index());
            },
            Input::Submit => {
                self.sending = true;
                let post = if self.content_buffer.char_count() > 0 {
                    Some(Post {
                        name: if self.name_buffer.length() > 0 {
                            Some(self.name_buffer.text().into())
                        } else { None },
                        summary: if self.summary_buffer.length() > 0 {
                            Some(self.summary_buffer.text().into())
                        } else { None },
                        tags: self.tags.iter().map(|t| t.0.clone().into()).collect(),
                        content: self.content_buffer.text(
                            &self.content_buffer.start_iter(),
                            &self.content_buffer.end_iter(),
                            false
                        ).into(),
                        visibility: self.visibility,
                    })
                } else { None };
                let _ = sender.output(post);
            },
            Input::SubmitDone(location) => {
                self.name_buffer.set_text("");
                self.summary_buffer.set_text("");
                self.tags.guard().clear();
                self.content_buffer.set_text("");
                let toast = adw::Toast::new(&gettext("Post submitted"));
                toast.set_button_label(Some(&gettext("Open")));
                toast.connect_button_clicked(move |toast| {
                    gtk::UriLauncher::new(&location.to_string()).launch(
                        None::<&adw::ApplicationWindow>,
                        None::<&gio::Cancellable>,
                        glib::clone!(#[weak] toast, move |result| {
                            if let Err(err) = result {
                                log::warn!("Error opening post URI: {}", err);
                            } else {
                                toast.dismiss()
                            }
                        })
                    );
                });

                root.add_toast(toast);
            },
            Input::SubmitError(err) => {
                let toast = adw::Toast::new(&gettext!("Error sending post: {}", err));
                toast.set_timeout(0);
                toast.set_priority(adw::ToastPriority::High);

                root.add_toast(toast);
            }
        }

        self.update_view(widgets, sender);
    }
}