use std::sync::Arc;

use adw::prelude::*;
use gtk::GridLayoutChild;
use relm4::{gtk, prelude::{AsyncComponent, AsyncComponentParts}, AsyncComponentSender, RelmWidgetExt};

mod widgets;
pub mod micropub;
pub mod util;
pub const APPLICATION_ID: &str = "xyz.vikanezrimaya.kittybox.Bowl";

#[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] ai_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>,
}

impl PostComposerModel {
    async fn ai_generate_summary(_content: glib::GString, _sender: AsyncComponentSender<Self>) -> PostComposerCommandOutput {
        // This is just a UI mock-up. In real-life conditions, this
        // would send a request to a summarizer API of some sort.
        //
        // Perhaps this API could be created using Ollama.
        //
        // Alternatively, one could probably launch a small local LLM
        // to do this, without network access.
        for i in ["I'", "m ", "sorry,", " I", " am ", "unable", " to", " ", "write", " you ", "a summary.", " I", " am", " not ", "really ", "an ", "LLM."] {
            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
            _sender.input(PostComposerInput::AiGenSummaryProgress(i.to_string()));
        }

        PostComposerCommandOutput::AiSummaryDone(Ok(()))
    }
}

#[derive(Debug)]
pub enum PostComposerInput {
    AiGenSummaryBegin,
    AiGenSummaryProgress(String),
    Submit,
}

#[derive(Debug)]
pub enum PostComposerCommandOutput {
    AiSummaryDone(Result<(), ()>)
}

#[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"),

            adw::ToolbarView {
                add_top_bar: &{
                    relm4::view! {
                        send_button = gtk::Button {
                            set_label: "Post",
                            connect_clicked => Self::Input::Submit,
                            #[track = "model.changed(Self::submit_busy_guard())"]
                            set_sensitive: model.submit_busy_guard.is_none(),
                        },

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

                    bar
                },
                #[name = "content_wrapper"]
                adw::BreakpointBin {
                    set_width_request: 320,
                    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,
                            set_css_classes: &["linked"],

                        
                            gtk::Entry {
                                set_hexpand: true,
                                set_buffer: &model.summary_buffer,
                                #[track = "model.changed(Self::ai_summary_busy_guard() | Self::submit_busy_guard())"]
                                set_sensitive: model.ai_summary_busy_guard.is_none() && model.submit_busy_guard.is_none(),
                            },
                            #[name = "ai_summary_button"]
                            gtk::Button {
                                connect_clicked => Self::Input::AiGenSummaryBegin,

                                #[track = "model.changed(Self::ai_summary_busy_guard() | Self::submit_busy_guard())"]
                                set_sensitive: model.ai_summary_busy_guard.is_none() && model.submit_busy_guard.is_none(),
                                set_tooltip: "Smart Summary\nAsk a language model to summarize the content of your post in a single sentence.",

                                gtk::Stack {
                                    gtk::Label {
                                        #[track = "model.changed(Self::ai_summary_busy_guard())"]
                                        set_visible: model.ai_summary_busy_guard.is_none(),
                                        set_markup: "✨"
                                    },

                                    gtk::Spinner {
                                        #[track = "model.changed(Self::ai_summary_busy_guard())"]
                                        set_visible: model.ai_summary_busy_guard.is_some(),
                                        set_spinning: true,
                                    }
                                }
                            },
                        },

                        #[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 {
                            set_css_classes: &["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,
                                set_css_classes: &["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(),
                            }
                        }
                    },

                    add_breakpoint = adw::Breakpoint::new(
                        adw::BreakpointCondition::new_length(
                            adw::BreakpointConditionLengthType::MinWidth,
                            480.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 model = PostComposerModel {
            ai_summary_busy_guard: None,
            submit_busy_guard: None,

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

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

            micropub: Arc::new(init),

            tracker: Default::default()
        };
        let widgets = view_output!();        

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

        for (row, (label, field)) in [
            (&widgets.name_label, widgets.name_field.upcast_ref::<gtk::Widget>()),
            (&widgets.summary_label, widgets.summary_field.upcast_ref::<gtk::Widget>()),
            (&widgets.tag_label, widgets.tag_holder.upcast_ref::<gtk::Widget>()),
            (&widgets.content_label, widgets.content_textarea.upcast_ref::<gtk::Widget>())
        ].into_iter().enumerate() {
            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);
        }

        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::AiGenSummaryBegin => {
                self.set_ai_summary_busy_guard(
                    Some(relm4::main_adw_application().mark_busy())
                );
                self.summary_buffer.set_text("");
                sender.oneshot_command(
                    Self::ai_generate_summary(
                        // TODO: apparently this thing may keep inline images. Investigate.
                        self.content_buffer.text(
                            &self.content_buffer.start_iter(),
                            &self.content_buffer.end_iter(),
                            false
                        ),
                        sender.clone()
                    )
                )
            },
            PostComposerInput::AiGenSummaryProgress(text) => {
                self.summary_buffer.insert_text(self.summary_buffer.length(), text);
            },
            PostComposerInput::Submit => {
                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()));
                }

                log::warn!("sending post: {:?}", &mf2);
                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("");
                        // TODO: display toast!
                        log::warn!("post submitted: {}", location);
                    },
                    Err(err) => {
                        // TODO: display error dialog
                        log::warn!("error sending the post: {}", err);
                    }
                }
                self.set_submit_busy_guard(None);
            },
        }

        self.update_view(widgets, sender);
    }

    async fn update_cmd(&mut self, msg: Self::CommandOutput, _sender: AsyncComponentSender<Self>, _root: &Self::Root) {
        match msg {
            Self::CommandOutput::AiSummaryDone(res) => {
                self.set_ai_summary_busy_guard(None);
                if let Err(err) = res {
                    log::warn!("AI summary generation failed: {:?}", err);
                };
            },
        }
    }
}