/* clone_dialog.rs
 *
 * Copyright 2026 Christian Kruse
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

use std::path::PathBuf;

use adw::prelude::*;
use gettextrs::gettext;
use relm4::prelude::*;

use crate::portal;
use crate::text_prompt_dialog::{TextPromptDialog, TextPromptDialogInit, TextPromptDialogOutput};

pub struct CloneDialogInit {
    pub parent_window: Option<gtk::Window>,
    pub folder_select_supported: bool,
}

#[derive(Debug)]
pub enum CloneDialogMsg {
    UrlChanged,
    ChooseDirectory,
    DirectoryChosen(PathBuf),
    DirectoryPromptDismissed,
    DirectoryNameChanged,
    Confirmed,
    Cancelled,
}

#[derive(Debug)]
pub enum CloneDialogOutput {
    Confirmed { url: String, destination: PathBuf },
    Cancelled,
}

pub struct CloneDialog {
    dialog: adw::AlertDialog,
    url_row: adw::EntryRow,
    directory_row: adw::ActionRow,
    name_row: adw::EntryRow,
    base_path: Option<PathBuf>,
    user_edited_name: bool,
    programmatic_change: bool,
    folder_select_supported: bool,
    text_prompt_dialog: Option<Controller<TextPromptDialog>>,
}

impl CloneDialog {
    fn validate(&self) {
        let url = self.url_row.text();
        let name = self.name_row.text();
        let has_base = self.base_path.is_some();

        let valid = !url.trim().is_empty() && !name.trim().is_empty() && has_base;
        self.dialog.set_response_enabled("confirm", valid);
    }

    fn derive_name_from_url(url: &str) -> String {
        let url = url.trim().trim_end_matches('/');
        let url = url.strip_suffix(".git").unwrap_or(url);

        if let Some(pos) = url.rfind('/') {
            return url[pos + 1..].to_string();
        }

        // SSH scp-style: git@host:user/repo
        if let Some(pos) = url.rfind(':') {
            let after = &url[pos + 1..];

            if let Some(slash) = after.rfind('/') {
                return after[slash + 1..].to_string();
            }

            return after.to_string();
        }

        url.to_string()
    }
}

#[relm4::component(pub)]
impl Component for CloneDialog {
    type Init = CloneDialogInit;
    type Input = CloneDialogMsg;
    type Output = CloneDialogOutput;
    type CommandOutput = ();

    view! {
        #[root]
        gtk::Box {
            set_orientation: gtk::Orientation::Vertical,
            add_css_class: "gap-l",

            adw::PreferencesGroup {
                #[name = "url_row"]
                adw::EntryRow {
                    set_title: &gettext("Repository URL"),
                },

                #[name = "directory_row"]
                adw::ActionRow {
                    set_title: &gettext("Location"),
                    set_subtitle: &dirs::home_dir()
                        .map(|p| p.to_string_lossy().to_string())
                        .unwrap_or_default(),
                    set_activatable: true,
                    add_suffix = &gtk::Image {
                        set_icon_name: Some("folder-symbolic"),
                    },
                    connect_activated => CloneDialogMsg::ChooseDirectory,
                },

                #[name = "name_row"]
                adw::EntryRow {
                    set_title: &gettext("Directory Name"),
                },
            },
        }
    }

    fn init(init: Self::Init, root: Self::Root, sender: ComponentSender<Self>) -> ComponentParts<Self> {
        let dialog = adw::AlertDialog::builder().heading(gettext("Clone Repository")).build();

        let base_path = dirs::home_dir();

        let mut model = CloneDialog {
            dialog,
            url_row: adw::EntryRow::new(),
            directory_row: adw::ActionRow::new(),
            name_row: adw::EntryRow::new(),
            base_path: base_path.clone(),
            user_edited_name: false,
            programmatic_change: false,
            folder_select_supported: init.folder_select_supported,
            text_prompt_dialog: None,
        };

        let widgets = view_output!();

        model.url_row = widgets.url_row.clone();
        model.directory_row = widgets.directory_row.clone();
        model.name_row = widgets.name_row.clone();

        model.dialog.set_extra_child(Some(&root));

        model.dialog.add_response("cancel", &gettext("Cancel"));
        model.dialog.add_response("confirm", &gettext("Clone"));
        model
            .dialog
            .set_response_appearance("confirm", adw::ResponseAppearance::Suggested);
        model.dialog.set_default_response(Some("confirm"));
        model.dialog.set_close_response("cancel");
        model.dialog.set_response_enabled("confirm", false);

        let s = sender.clone();
        widgets.url_row.connect_changed(move |_| {
            s.input(CloneDialogMsg::UrlChanged);
        });

        let s = sender.clone();
        widgets.name_row.connect_changed(move |_| {
            s.input(CloneDialogMsg::DirectoryNameChanged);
        });

        model.dialog.connect_response(None, move |_dialog, response| {
            if response == "confirm" {
                sender.input(CloneDialogMsg::Confirmed);
            } else {
                sender.input(CloneDialogMsg::Cancelled);
            }
        });

        model.dialog.present(init.parent_window.as_ref());

        ComponentParts { model, widgets }
    }

    fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>, root: &Self::Root) {
        match msg {
            CloneDialogMsg::UrlChanged => {
                if !self.user_edited_name {
                    let url = self.url_row.text();
                    let name = Self::derive_name_from_url(&url);

                    self.programmatic_change = true;
                    self.name_row.set_text(&name);
                    self.programmatic_change = false;
                }

                self.validate();
            }

            CloneDialogMsg::ChooseDirectory => {
                let window = root.ancestor(gtk::Window::static_type()).and_downcast::<gtk::Window>();

                if !self.folder_select_supported {
                    let initial = self
                        .base_path
                        .as_ref()
                        .map(|p| p.to_string_lossy().to_string())
                        .unwrap_or_default();

                    self.text_prompt_dialog = Some(
                        TextPromptDialog::builder()
                            .launch(TextPromptDialogInit {
                                heading: gettext("Select Clone Destination"),
                                body: gettext("Enter the absolute path of the directory to clone into."),
                                placeholder: "/path/to/parent-directory".to_string(),
                                initial,
                                confirm_label: gettext("Select"),
                                parent_window: window,
                                validator: Some(|s| std::path::Path::new(s.trim()).is_dir()),
                                notice: Some(portal::fallback_notice()),
                            })
                            .forward(sender.input_sender(), |output| match output {
                                TextPromptDialogOutput::Confirmed(text) => {
                                    CloneDialogMsg::DirectoryChosen(PathBuf::from(text.trim()))
                                }
                                TextPromptDialogOutput::Cancelled => CloneDialogMsg::DirectoryPromptDismissed,
                            }),
                    );
                } else {
                    let dialog = gtk::FileDialog::builder()
                        .title(gettext("Select Clone Destination"))
                        .modal(true)
                        .build();

                    let sender = sender.clone();

                    dialog.select_folder(window.as_ref(), None::<&gtk::gio::Cancellable>, move |result| {
                        if let Ok(file) = result
                            && let Some(path) = file.path()
                        {
                            sender.input(CloneDialogMsg::DirectoryChosen(path));
                        }
                    });
                }
            }

            CloneDialogMsg::DirectoryPromptDismissed => {
                self.text_prompt_dialog = None;
            }

            CloneDialogMsg::DirectoryChosen(path) => {
                self.base_path = Some(path.clone());
                self.directory_row.set_subtitle(&path.to_string_lossy());
                self.validate();
            }

            CloneDialogMsg::DirectoryNameChanged => {
                if self.programmatic_change {
                    return;
                }

                self.user_edited_name = !self.name_row.text().is_empty();
                self.validate();
            }

            CloneDialogMsg::Confirmed => {
                let url = self.url_row.text().trim().to_string();
                let name = self.name_row.text().trim().to_string();

                if let Some(ref base) = self.base_path {
                    let destination = base.join(&name);
                    sender.output(CloneDialogOutput::Confirmed { url, destination }).ok();
                }
            }

            CloneDialogMsg::Cancelled => {
                sender.output(CloneDialogOutput::Cancelled).ok();
            }
        }
    }
}
