use std::sync::Arc;

use adw::prelude::*;
use gtk::GridLayoutChild;
use relm4::{gtk, prelude::{AsyncComponent, AsyncComponentController, AsyncComponentParts, AsyncController, ComponentController, Controller}, AsyncComponentSender, Component, RelmWidgetExt};

pub mod components {
    pub(crate) mod smart_summary;
    pub(crate) use smart_summary::{
        SmartSummaryButton, Output as SmartSummaryOutput, Input as SmartSummaryInput
    };
}
mod widgets;
pub mod secrets;
pub mod micropub;
pub mod util;
pub const APPLICATION_ID: &str = "xyz.vikanezrimaya.kittybox.Bowl";

pub const VISIBILITY: [&str; 2] = ["public", "private"];

#[tracker::track]
#[derive(Debug)]
pub struct PostComposerModel {
    /// Busy guard for generating the summary using an LLM.
    /// Makes the summary field read-only and blocks the Smart Summary button.
    #[no_eq] smart_summary_busy_guard: Option<gtk::gio::ApplicationBusyGuard>,
    #[no_eq] submit_busy_guard: Option<gtk::gio::ApplicationBusyGuard>,

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

    #[do_not_track] wide_layout: gtk::GridLayout,
    #[do_not_track] narrow_layout: gtk::BoxLayout,

    #[do_not_track] micropub: Arc<micropub::Client>,

    #[do_not_track] smart_summary: Controller<components::SmartSummaryButton>,
}

impl PostComposerModel {
    fn busy_changed(&self) -> bool {
        self.changed(Self::submit_busy_guard() | Self::smart_summary_busy_guard())
    }
    fn busy(&self) -> bool {
        self.submit_busy_guard.is_some() || self.smart_summary_busy_guard.is_some()
    }
}

#[derive(Debug)]
#[allow(private_interfaces)]
pub enum PostComposerInput {
    #[doc(hidden)] SmartSummary(components::smart_summary::Output),
    Submit,
}

#[derive(Debug)]
pub enum PostComposerCommandOutput {
}

#[relm4::component(pub async)]
impl AsyncComponent for PostComposerModel {
    /// The type of the messages that this component can receive.
    type Input = PostComposerInput;
    /// The type of the messages that this component can send.
    type Output = ();
    /// The type of data with which this component will be initialized.
    type Init = micropub::Client;
    /// The type of the command outputs that this component can receive.
    type CommandOutput = PostComposerCommandOutput;
 
    view! {
        #[root]
        adw::ApplicationWindow {
            set_title: Some("Create post"),
            set_width_request: 360,
            set_height_request: 480,

            adw::ToolbarView {
                add_top_bar: &{
                    relm4::view! {
                        send_button = gtk::Button {
                            set_icon_name: "document-send-symbolic",
                            set_tooltip: "Send post",

                            connect_clicked => Self::Input::Submit,
                            #[track = "model.busy_changed()"]
                            set_sensitive: !model.busy(),
                        },

                        bar = adw::HeaderBar::new() {
                            pack_end: &send_button,
                        },
                    }

                    bar
                },
                #[name = "toast_overlay"]
                adw::ToastOverlay {
                    #[name = "content_wrapper"]
                    adw::BreakpointBin {
                        set_width_request: 360,
                        set_height_request: 480,

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

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

                            #[name = "summary_label"]
                            gtk::Label {
                                set_markup: "Summary",
                                set_margin_vertical: 10,
                                set_margin_horizontal: 10,
                                set_halign: gtk::Align::Start,
                                set_valign: gtk::Align::Start,
                            },
                            #[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(),
                                },

                                model.smart_summary.widget(),
                            },

                            #[name = "tag_label"]
                            gtk::Label {
                                set_markup: "Tags",
                                set_margin_vertical: 10,
                                set_margin_horizontal: 10,
                                set_halign: gtk::Align::Start,
                                set_valign: gtk::Align::Start,
                            },
                            #[name = "tag_holder"]
                            // TODO: tag component (because of complex logic)
                            gtk::Box {
                                add_css_class: "frame",
                                set_hexpand: true,
                                set_orientation: gtk::Orientation::Horizontal,
                                set_spacing: 5,
                                set_height_request: 36,
                            },


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

                            #[name = "content_textarea"]
                            gtk::ScrolledWindow {
                                set_vexpand: true,

                                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::submit_busy_guard())"]
                                    set_sensitive: model.submit_busy_guard.is_none(),
                                }
                            },

                            #[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::Box {
                                        set_spacing: 5,

                                        #[name = "visibility_label"]
                                        gtk::Label {
                                            set_markup: "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(&gtk::StringList::new(&VISIBILITY)),
                                            set_hexpand: false,

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

                        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())),
                        },

                    }
                }
            }
        }
                
    }
    /// Initialize the UI and model.
    async fn init(
        init: Self::Init,
        window: Self::Root,
        sender: AsyncComponentSender<Self>,
    ) -> AsyncComponentParts<Self> {
        let content_buffer = gtk::TextBuffer::default();
        let model = PostComposerModel {
            smart_summary_busy_guard: None,
            submit_busy_guard: None,

            name_buffer: gtk::EntryBuffer::default(),
            summary_buffer: gtk::EntryBuffer::default(),
            content_buffer: content_buffer.clone(),

            wide_layout: gtk::GridLayout::new(),
            narrow_layout: gtk::BoxLayout::new(gtk::Orientation::Vertical),

            micropub: Arc::new(init),
            smart_summary: components::SmartSummaryButton::builder()
                .launch(())
                .forward(sender.input_sender(), PostComposerInput::SmartSummary),

            tracker: Default::default()
        };

        let widgets = view_output!();

        #[cfg(debug_assertions)]
        window.add_css_class("devel");

        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)
        }

        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::TwoColumn(&widgets.content_label, widgets.content_textarea.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);
                }
            }
        }

        widgets.content.set_layout_manager(Some(model.narrow_layout.clone()));

        AsyncComponentParts { model, widgets }
    }

    async fn update_with_view(
        &mut self,
        widgets: &mut Self::Widgets,
        message: Self::Input,
        sender: AsyncComponentSender<Self>,
        root: &Self::Root
    ) {
        self.reset(); // Reset the tracker

        match message {
            PostComposerInput::SmartSummary(components::SmartSummaryOutput::Start) => {
                widgets.content_textarea.set_sensitive(false);
                if self.content_buffer.char_count() == 0 {
                    let _ = self.smart_summary.sender().send(
                        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(
                        components::SmartSummaryInput::Text(text.into())
                    ).is_ok() {
                        self.summary_buffer.set_text("");
                    }
                }
                widgets.content_textarea.set_sensitive(true);
            },
            PostComposerInput::SmartSummary(components::SmartSummaryOutput::Chunk(text)) => {
                self.summary_buffer.insert_text(self.summary_buffer.length(), text);
            },
            PostComposerInput::SmartSummary(components::SmartSummaryOutput::Done) => {
                self.set_smart_summary_busy_guard(None);
            }
            PostComposerInput::SmartSummary(components::SmartSummaryOutput::Error(err)) => {
                self.set_smart_summary_busy_guard(None);

                let toast = adw::Toast::new(&format!("Smart Summary error: {}", err));
                toast.set_timeout(0);
                toast.set_priority(adw::ToastPriority::High);
                widgets.toast_overlay.add_toast(toast);

            },
            PostComposerInput::Submit => {
                if self.content_buffer.char_count() == 0 {
                    self.update_view(widgets, sender);
                    return
                }
                self.set_submit_busy_guard(
                    Some(relm4::main_adw_application().mark_busy())
                );
                // Update view to lock the interface up
                self.update_view(widgets, sender.clone());
                self.reset();

                use microformats::types::{Item, Class, KnownClass, PropertyValue};
                let mut mf2 = Item::new(vec![Class::Known(KnownClass::Entry)]);
                if self.name_buffer.length() > 0 {
                    let proplist = mf2.properties.entry("name".to_owned()).or_default();
                    proplist.push(PropertyValue::Plain(self.name_buffer.text().into()));
                }
                if self.summary_buffer.length() > 0 {
                    let proplist = mf2.properties.entry("summary".to_owned()).or_default();
                    proplist.push(PropertyValue::Plain(self.summary_buffer.text().into()));
                }

                // TODO: tags

                {
                    let proplist = mf2.properties.entry("content".to_owned()).or_default();
                    proplist.push(PropertyValue::Plain(self.content_buffer.text(
                        &self.content_buffer.start_iter(),
                        &self.content_buffer.end_iter(),
                        false
                    ).into()));
                }

                {
                    let proplist = mf2.properties.entry("visibility".to_owned()).or_default();
                    let selected = VISIBILITY[widgets.visibility_selector.selected() as usize];
                    proplist.push(PropertyValue::Plain(selected.to_owned()));
                }

                match self.micropub.send_post(mf2).await {
                    Ok(location) => {
                        self.name_buffer.set_text("");
                        self.summary_buffer.set_text("");
                        // TODO: tags
                        self.content_buffer.set_text("");
                        let toast = adw::Toast::new("Post submitted");
                        toast.set_button_label(Some("Open"));
                        toast.connect_button_clicked(glib::clone!(#[strong] root, move |toast| {
                            gtk::UriLauncher::new(&location.to_string()).launch(
                                Some(root.upcast_ref::<gtk::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()
                                    }
                                })
                            );
                        }));
                        widgets.toast_overlay.add_toast(toast);
                    },
                    Err(err) => {
                        log::warn!("Error sending post: {}", err);
                        let toast = adw::Toast::new(&format!("Error sending post: {}", err));
                        toast.set_timeout(0);
                        toast.set_priority(adw::ToastPriority::High);
                        widgets.toast_overlay.add_toast(toast);
                    }
                }
                self.set_submit_busy_guard(None);
            },
        }

        self.update_view(widgets, sender);
    }
}