/* text_prompt_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 adw::prelude::*;
use gettextrs::gettext;
use relm4::prelude::*;

pub struct TextPromptDialogInit {
    pub heading: String,
    pub body: String,
    pub placeholder: String,
    pub initial: String,
    pub confirm_label: String,
    pub parent_window: Option<gtk::Window>,
    pub validator: Option<fn(&str) -> bool>,
    pub notice: Option<String>,
}

#[derive(Debug)]
pub enum TextPromptDialogMsg {
    TextChanged,
}

#[derive(Debug)]
pub enum TextPromptDialogOutput {
    Confirmed(String),
    Cancelled,
}

pub struct TextPromptDialog {
    dialog: adw::AlertDialog,
    entry: gtk::Entry,
    placeholder: String,
    initial: String,
    validator: Option<fn(&str) -> bool>,
    notice: Option<String>,
}

#[relm4::component(pub)]
impl Component for TextPromptDialog {
    type Init = TextPromptDialogInit;
    type Input = TextPromptDialogMsg;
    type Output = TextPromptDialogOutput;
    type CommandOutput = ();

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

            gtk::Box {
                set_orientation: gtk::Orientation::Horizontal,
                add_css_class: "gap-m",
                set_visible: model.notice.is_some(),

                gtk::Image {
                    set_icon_name: Some("dialog-warning-symbolic"),
                    set_valign: gtk::Align::Start,
                },

                gtk::Label {
                    set_label: model.notice.as_deref().unwrap_or(""),
                    set_wrap: true,
                    set_xalign: 0.0,
                    set_hexpand: true,
                    add_css_class: "caption",
                    add_css_class: "dim-label",
                },
            },

            #[name = "entry"]
            gtk::Entry {
                set_placeholder_text: Some(&model.placeholder),
                update_property: &[gtk::accessible::Property::Label(&model.placeholder)],
                set_text: &model.initial,
                set_activates_default: true,
            },
        }
    }

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

        let mut model = TextPromptDialog {
            dialog,
            entry: gtk::Entry::new(),
            placeholder: init.placeholder,
            initial: init.initial.clone(),
            validator: init.validator,
            notice: init.notice,
        };

        let widgets = view_output!();

        model.entry = widgets.entry.clone();

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

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

        let initial_valid = {
            let trimmed = init.initial.trim();
            !trimmed.is_empty() && init.validator.is_none_or(|v| v(trimmed))
        };

        model.dialog.set_response_enabled("confirm", initial_valid);

        let s = sender.clone();
        widgets.entry.connect_changed(move |_| {
            s.input(TextPromptDialogMsg::TextChanged);
        });

        let entry = widgets.entry.clone();
        model.dialog.connect_response(None, move |_dialog, response| {
            if response == "confirm" {
                let text = entry.text().trim().to_string();

                if !text.is_empty() {
                    sender.output(TextPromptDialogOutput::Confirmed(text)).ok();
                }
            } else {
                sender.output(TextPromptDialogOutput::Cancelled).ok();
            }
        });

        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 {
            TextPromptDialogMsg::TextChanged => {
                let trimmed = self.entry.text().trim().to_string();
                let valid = !trimmed.is_empty() && self.validator.is_none_or(|v| v(&trimmed));
                self.dialog.set_response_enabled("confirm", valid);
            }
        }
    }
}
