/* ui_helpers.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::Path;

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

pub fn format_time(timestamp: i64) -> String {
    let datetime = gtk::glib::DateTime::from_unix_local(timestamp);

    match datetime {
        Ok(dt) => {
            let use_short = gtk::glib::DateTime::now_local()
                .map(|now| (now.to_unix() - dt.to_unix()) < 86400)
                .unwrap_or(false);

            let fmt = if use_short { "%H:%M" } else { "%Y-%m-%d %H:%M" };

            dt.format(fmt)
                .map(|s| s.to_string())
                .unwrap_or_else(|_| "?".to_string())
        }

        Err(_) => "?".to_string(),
    }
}

pub fn empty_placeholder(text: &str) -> gtk::Label {
    gtk::Label::builder()
        .label(text)
        .css_classes(["dim-label"])
        .margin_top(6)
        .margin_bottom(6)
        .build()
}

pub fn open_in_file_manager(path: &Path) {
    let file = gio::File::for_path(path);
    let launcher = gtk::FileLauncher::new(Some(&file));

    if path.is_dir() {
        launcher.launch(gtk::Window::NONE, gio::Cancellable::NONE, |_| {});
    } else {
        launcher.open_containing_folder(gtk::Window::NONE, gio::Cancellable::NONE, |_| {});
    }
}

pub fn find_child_by_name(parent: &impl IsA<gtk::Widget>, name: &str) -> Option<gtk::Widget> {
    let mut child = parent.first_child();

    while let Some(widget) = child {
        if widget.widget_name() == name {
            return Some(widget);
        }

        child = widget.next_sibling();
    }

    None
}

const HEX_MESSAGES: &[&str] = &[
    "+++ Out of Cheese Error +++",
    "+++ Divide by Cucumber Error +++",
    "+++ Please Reinstall Universe and Reboot +++",
    "+++ Error At Address 14, Treacle Mine Road +++",
    "+++ Abomination Unto Nuggan Error +++",
    "+++ Anoia Error: Cutlery Stuck in Drawer +++",
];

fn random_hex_message() -> &'static str {
    let nanos = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.subsec_nanos() as usize)
        .unwrap_or(0);

    HEX_MESSAGES[nanos % HEX_MESSAGES.len()]
}

pub fn show_error_dialog(parent: Option<&gtk::Window>, heading: &str, body: &str) {
    let combined_body = if body.is_empty() {
        heading.to_string()
    } else {
        format!("{heading}\n\n{body}")
    };

    let dialog = adw::AlertDialog::builder()
        .heading(random_hex_message())
        .body(combined_body)
        .build();

    dialog.add_response("ok", &gettext("OK"));
    dialog.set_default_response(Some("ok"));
    dialog.set_close_response("ok");
    dialog.present(parent);
}

pub fn icon_label_widget(icon_name: &str, label: &str) -> gtk::Box {
    let bx = gtk::Box::new(gtk::Orientation::Horizontal, 6);
    let image = gtk::Image::from_icon_name(icon_name);
    let lbl = gtk::Label::new(Some(label));
    bx.append(&image);
    bx.append(&lbl);
    bx
}

pub fn format_bytes(bytes: usize) -> String {
    if bytes < 1024 {
        format!("{bytes} B")
    } else if bytes < 1024 * 1024 {
        format!("{:.1} KB", bytes as f64 / 1024.0)
    } else {
        format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0))
    }
}

pub fn apply_diff_font_css(provider: &gtk::CssProvider, font_setting: &str) {
    let css = if font_setting.trim().is_empty() {
        String::new()
    } else {
        let desc = gtk::pango::FontDescription::from_string(font_setting);

        let family = desc
            .family()
            .map(|f| f.to_string())
            .unwrap_or_else(|| "monospace".to_string())
            .replace('"', "");

        let size = desc.size();

        let size_rule = if size > 0 && !desc.is_size_absolute() {
            format!("font-size: {}pt;", size as f64 / gtk::pango::SCALE as f64)
        } else if size > 0 {
            format!("font-size: {}px;", size as f64 / gtk::pango::SCALE as f64)
        } else {
            String::new()
        };

        format!(".diff-content, .diff-line-number {{ font-family: \"{family}\", monospace; {size_rule} }}")
    };

    provider.load_from_string(&css);
}

pub fn apply_compact_css(provider: &gtk::CssProvider, compact: bool) {
    if compact {
        provider.load_from_string(include_str!("../data/style-compact.css"));
    } else {
        provider.load_from_string("");
    }
}

pub fn widget_has_focus(widget: &impl IsA<gtk::Widget>) -> bool {
    let widget = widget.upcast_ref::<gtk::Widget>();

    widget
        .root()
        .and_then(|r| r.focus())
        .is_some_and(|focused| focused.eq(widget) || focused.is_ancestor(widget))
}
