// SPDX-FileCopyrightText: 2024 Andrey Kutejko <andy128k@gmail.com>
// SPDX-FileCopyrightText: 2026 Wladimir Palant https://palant.info/
//
// SPDX-License-Identifier: GPL-3.0-or-later

use crate::{
    dir::Directory,
    file::{File, FileOps},
    utils::{ErrorMessage, NO_BUTTONS, WindowExt, dialog_button_box},
};
use gettextrs::gettext;
use gtk::{gio, glib, prelude::*, subclass::prelude::*};
use std::path::Path;

mod imp {
    use super::*;
    use crate::utils::SenderExt;

    pub struct CreateSymlinkDialog {
        pub link_name_entry: gtk::Entry,
        cancel_button: gtk::Button,
        ok_button: gtk::Button,
        sender: async_channel::Sender<Option<glib::GString>>,
        pub receiver: async_channel::Receiver<Option<glib::GString>>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for CreateSymlinkDialog {
        const NAME: &'static str = "GnomeCmdCreateSymlinkDialog";
        type Type = super::CreateSymlinkDialog;
        type ParentType = gtk::Window;

        fn new() -> Self {
            let (sender, receiver) = async_channel::bounded(1);
            Self {
                link_name_entry: gtk::Entry::builder().activates_default(true).build(),
                cancel_button: gtk::Button::builder()
                    .label(gettext("_Cancel"))
                    .use_underline(true)
                    .build(),
                ok_button: gtk::Button::builder()
                    .label(gettext("C_reate"))
                    .use_underline(true)
                    .sensitive(false)
                    .build(),
                sender,
                receiver,
            }
        }
    }

    impl ObjectImpl for CreateSymlinkDialog {
        fn constructed(&self) {
            self.parent_constructed();

            let dialog = self.obj();

            dialog.add_css_class("dialog");
            dialog.set_title(Some(&gettext("Create Symbolic Link")));
            dialog.set_resizable(false);
            dialog.set_modal(true);

            let grid = gtk::Grid::builder().build();
            dialog.set_child(Some(&grid));

            let label = gtk::Label::builder()
                .label(gettext("Symbolic link name:"))
                .mnemonic_widget(&self.link_name_entry)
                .build();
            grid.attach(&label, 0, 0, 1, 1);
            grid.attach(&self.link_name_entry, 1, 0, 1, 1);

            grid.attach(
                &dialog_button_box(NO_BUTTONS, &[&self.cancel_button, &self.ok_button]),
                0,
                1,
                2,
                1,
            );

            self.link_name_entry.connect_changed(glib::clone!(
                #[weak(rename_to = imp)]
                self,
                move |_| imp.link_name_changed()
            ));

            self.cancel_button.connect_clicked(glib::clone!(
                #[weak(rename_to = imp)]
                self,
                move |_| imp.sender.toss(None)
            ));

            self.ok_button.connect_clicked(glib::clone!(
                #[weak(rename_to = imp)]
                self,
                move |_| imp.ok_clicked()
            ));

            dialog.set_default_widget(Some(&self.ok_button));
            dialog.set_cancel_widget(&self.cancel_button);
        }
    }

    impl WidgetImpl for CreateSymlinkDialog {}
    impl WindowImpl for CreateSymlinkDialog {}

    impl CreateSymlinkDialog {
        fn link_name_changed(&self) {
            self.ok_button
                .set_sensitive(!self.link_name_entry.text().is_empty());
        }

        fn ok_clicked(&self) {
            let link_name = self.link_name_entry.text();
            if !link_name.is_empty() {
                self.sender.toss(Some(link_name));
            }
        }
    }
}

glib::wrapper! {
    pub struct CreateSymlinkDialog(ObjectSubclass<imp::CreateSymlinkDialog>)
        @extends gtk::Window, gtk::Widget,
        @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::ShortcutManager, gtk::Root, gtk::Native;
}

impl Default for CreateSymlinkDialog {
    fn default() -> Self {
        glib::Object::builder().build()
    }
}

pub async fn show_create_symlink_dialog(
    parent_window: &gtk::Window,
    file: &File,
    directory: &Directory,
    link_name: &str,
) {
    let dialog = CreateSymlinkDialog::default();
    dialog.set_transient_for(Some(parent_window));
    dialog.imp().link_name_entry.set_text(link_name);

    dialog.present();

    loop {
        let response = dialog.imp().receiver.recv().await;
        let Ok(Some(link_name)) = response else {
            break;
        };

        let symlink_file: gio::File = if link_name.starts_with('/') {
            let con = directory.connection();
            gio::File::for_uri(&con.create_uri(Path::new(&link_name)))
        } else {
            directory.get_child_gfile(Path::new(&link_name))
        };
        let absolute_path = file.file().parse_name();

        match symlink_file.make_symbolic_link(absolute_path, gio::Cancellable::NONE) {
            Ok(_) => {
                if symlink_file
                    .parent()
                    .is_some_and(|parent| directory.file().equal(&parent))
                {
                    directory.file_created(&symlink_file.uri());
                }
                break;
            }
            Err(error) => {
                ErrorMessage::with_error(gettext("Making a symbolic link failed"), &error)
                    .show(dialog.upcast_ref())
                    .await;
            }
        }
    }
    dialog.close();
}
