// Prevent console window in addition to Slint window in Windows release builds when, e.g., starting the app via file manager. Ignored on other platforms.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

mod cmds;

use std::path::{Path, PathBuf};

use anyhow::Error;
use cmds::{channel, log};
use slint::SharedString;

slint::include_modules!();

fn main() -> Result<(), Error> {
    let cli_args: CLIArgs = argh::from_env();

    let ui = AppWindow::new()?;
    ui.set_exe_path(cli_args.exe_path.into());
    ui.set_repo_path(match cli_args.repo_path {
        Some(path_str) => path_str.into(),
        None => path_to_uistr(std::env::current_dir()?),
    });

    ui.on_request_channel_list({
        let ui_handle = ui.as_weak();
        move || {
            let ui = ui_handle.unwrap();

            let exe_path_string = ui.get_exe_path();
            let exe_path = Path::new(exe_path_string.as_str());
            let repo_path_string = ui.get_repo_path();
            let repo_path = Path::new(repo_path_string.as_str());

            let channel_output = channel(exe_path, repo_path);
            let channel_list = match channel_output {
                Ok(channel_list) => channel_list,
                Err(e) => {
                    println!("{:?}", e);
                    return;
                }
            };

            let channel_strs = channel_list
                .entries
                .into_iter()
                .map(SharedString::from)
                .collect::<Vec<_>>();
            ui.set_channels((&channel_strs[..]).into());
            ui.set_active_channel(i32::try_from(channel_list.active).unwrap_or(-1));
            let channel_strs = channel_list
                .entries
                .into_iter()
                .map(SharedString::from)
                .collect::<Vec<_>>();
            ui.set_channels(ChannelsData {
                names: (&channel_strs[..]).into(),
                active: i32::try_from(channel_list.active).unwrap_or(-1),
            });
        }
    });

    ui.on_request_changes_log({
        let ui_handle = ui.as_weak();
        move |ch_name: SharedString| {
            let ch_name = ch_name.as_str();
            let ui = ui_handle.unwrap();

            let exe_path_string = ui.get_exe_path();
            let exe_path = Path::new(exe_path_string.as_str());
            let repo_path_string = ui.get_repo_path();
            let repo_path = Path::new(repo_path_string.as_str());

            let log_output = log(
                exe_path,
                repo_path,
                Some(ch_name).filter(|cn| !cn.is_empty()),
            );
            let log = match log_output {
                Ok(log) => log,
                Err(e) => {
                    println!("{:?}", e);
                    return;
                }
            };

            let log_strs: Vec<ChangeData> = log
                .into_iter()
                .map(|change| ChangeData {
                    hash: change.hash.into(),
                    author: change
                        .authors
                        .first()
                        .unwrap_or(&String::new())
                        .clone()
                        .into(),
                    timestamp: change.timestamp.into(),
                    message: change.message.into(),
                })
                .collect();
            ui.set_changes_log((&log_strs[..]).into());
        }
    });

    ui.on_request_change_delta({
        let ui_handle = ui.as_weak();
        move |change_hash: SharedString| {
            let change_hash = change_hash.as_str();
            let ui = ui_handle.unwrap();

            let exe_path_string = ui.get_exe_path();
            let exe_path = Path::new(exe_path_string.as_str());
            let repo_path_string = ui.get_repo_path();
            let repo_path = Path::new(repo_path_string.as_str());

            let change_output = cmds::change(exe_path, repo_path, change_hash);
            let delta = match change_output {
                Ok(delta) => delta,
                Err(e) => {
                    println!("{:?}", e);
                    return;
                }
            };

            ui.set_delta((&String::from(delta)[..]).into());
        }
    });

    ui.on_request_diff_summary({
        let ui_handle = ui.as_weak();
        move |ch_name: SharedString| {
            let ch_name = ch_name.as_str();
            let ui = ui_handle.unwrap();

            let exe_path_string = ui.get_exe_path();
            let exe_path = Path::new(exe_path_string.as_str());
            let repo_path_string = ui.get_repo_path();
            let repo_path = Path::new(repo_path_string.as_str());

            let diff_output = cmds::diff_short(
                exe_path,
                repo_path,
                Some(ch_name).filter(|cn| !cn.is_empty()),
            );
            let file_changes = match diff_output {
                Ok(changes) => changes,
                Err(e) => {
                    println!("{:?}", e);
                    return;
                }
            };

            ui.set_count_files_changed(i32::try_from(file_changes.len()).unwrap_or(-1));
        }
    });

    ui.on_request_diff_delta({
        let ui_handle = ui.as_weak();
        move |ch_name: SharedString| {
            let ch_name = ch_name.as_str();
            let ui = ui_handle.unwrap();

            let exe_path_string = ui.get_exe_path();
            let exe_path = Path::new(exe_path_string.as_str());
            let repo_path_string = ui.get_repo_path();
            let repo_path = Path::new(repo_path_string.as_str());

            let diff_output = cmds::diff(
                exe_path,
                repo_path,
                Some(ch_name).filter(|cn| !cn.is_empty()),
            );
            let delta = match diff_output {
                Ok(delta) => delta,
                Err(e) => {
                    println!("{:?}", e);
                    return;
                }
            };

            ui.set_delta((&String::from(delta)[..]).into());
        }
    });

    ui.run()?;

    Ok(())
}

/// A GUI frontend for the `pijul` program.
#[derive(argh::FromArgs)]
struct CLIArgs {
    /// the file system path of the `pijul` executable
    #[argh(option, default = "\"pijul\".into()")]
    // TODO argh does not support OS strings:
    //  https://github.com/google/argh/blob/f02f6b44444b340a553ff2db298744721ebc77a7/argh/src/lib.rs#L698
    // -> use raw std::env::args_os?:
    //  https://doc.rust-lang.org/std/env/fn.args_os.html
    //exe_path: PathBuf,
    exe_path: String,

    /// the file system path of the pijul repository (i.e., the directory containing the `.pijul` folder)
    #[argh(option)]
    // TODO argh does not support OS strings:
    //  https://github.com/google/argh/blob/f02f6b44444b340a553ff2db298744721ebc77a7/argh/src/lib.rs#L698
    // -> use raw std::env::args_os?:
    //  https://doc.rust-lang.org/std/env/fn.args_os.html
    // repo_path: Option<PathBuf>,
    repo_path: Option<String>,
}

fn path_to_uistr(path: PathBuf) -> SharedString {
    format!("{:?}", path).into()
}