// 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::{
    chown_component::ChownComponent,
    connection::Connection,
    dir::Directory,
    file::{File, FileOps},
    pwd::{gid_t, uid, uid_t},
    utils::{ErrorMessage, NO_BUTTONS, SenderExt, WindowExt, dialog_button_box},
};
use gettextrs::gettext;
use gtk::{glib, prelude::*};

#[async_recursion::async_recursion(?Send)]
async fn chown_recursively(
    parent_window: &gtk::Window,
    connection: Option<&Connection>,
    file: &File,
    uid: Option<uid_t>,
    gid: gid_t,
    recurse: bool,
) {
    if let Err(error) = file.chown(uid, gid) {
        ErrorMessage::with_error(
            gettext("Could not chown {}").replace("{}", &file.name()),
            &error,
        )
        .show(parent_window)
        .await;
    }

    if !recurse {
        return;
    }

    if file.is_directory()
        && let Some(connection) = connection
    {
        let dir = Directory::new_from_file(connection, &*file.file());
        if let Err(error) = dir.list_files(parent_window, false).await {
            error.show(parent_window).await;
            return;
        }

        // Create a copy of the list to guard against modifications while we process it
        let files = dir
            .files()
            .iter()
            .filter(|child| !child.is_dotdot() && child.name() != "." && !child.is_symlink())
            .cloned()
            .collect::<Vec<_>>();
        for child in files {
            chown_recursively(parent_window, Some(connection), &child, uid, gid, recurse).await;
        }
    }
}

async fn chown_files(
    parent_window: &gtk::Window,
    connection: Option<&Connection>,
    files: &[File],
    uid: Option<uid_t>,
    gid: gid_t,
    recursive: bool,
) {
    for file in files {
        chown_recursively(parent_window, connection, file, uid, gid, recursive).await;
    }
}

pub async fn show_chown_dialog(
    parent_window: &gtk::Window,
    connection: Option<&Connection>,
    files: &[File],
) -> bool {
    let Some(file) = files.first() else {
        return false;
    };

    let dialog = gtk::Window::builder()
        .transient_for(parent_window)
        .title(gettext("File Owner"))
        .resizable(false)
        .build();
    dialog.add_css_class("dialog");

    let content_area = gtk::Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .build();
    dialog.set_child(Some(&content_area));

    let chown_component = ChownComponent::new();
    chown_component.set_ownership(file.uid() as uid_t, file.gid() as gid_t);
    content_area.append(&chown_component);

    content_area.append(&gtk::Separator::new(gtk::Orientation::Horizontal));

    let recurse_check = gtk::CheckButton::with_label(&gettext("Apply Recursively"));
    content_area.append(&recurse_check);

    let (sender, receiver) = async_channel::bounded::<bool>(1);

    let cancel_btn = gtk::Button::builder()
        .label(gettext("_Cancel"))
        .use_underline(true)
        .build();
    cancel_btn.connect_clicked(glib::clone!(
        #[strong]
        sender,
        move |_| sender.toss(false)
    ));

    let ok_btn = gtk::Button::builder()
        .label(gettext("C_hange Owner"))
        .use_underline(true)
        .build();
    ok_btn.connect_clicked(glib::clone!(
        #[strong]
        sender,
        move |_| sender.toss(true)
    ));

    content_area.append(&dialog_button_box(NO_BUTTONS, &[&cancel_btn, &ok_btn]));

    dialog.set_default_widget(Some(&ok_btn));
    dialog.set_cancel_widget(&cancel_btn);
    dialog.present();

    let result = receiver.recv().await == Ok(true);

    if result {
        let (owner, group) = chown_component.ownership();
        let owner = (uid() == 0).then_some(owner);

        let recursive = recurse_check.is_active();

        chown_files(
            dialog.upcast_ref(),
            connection,
            files,
            owner,
            group,
            recursive,
        )
        .await;
    }

    dialog.close();

    result
}
