use clap::{Args, Subcommand};

use crate::{
    data::package::{PackageConfig, SharedPackageConfig},
    utils::toggle::Toggle,
};

#[derive(Args, Debug, Clone)]

pub struct EditExtraArgs {
    /// Change the branch name in additional data
    #[clap(long = "branchName")]
    pub branch_name: Option<String>,

    /// Change the headers only bool in additional data, pass enable or disable
    #[clap(long = "headersOnly")]
    pub headers_only: Option<Toggle>,

    /// Make the package be statically linked, 0 for false, 1 for true
    #[clap(long = "staticLinking")]
    pub static_linking: Option<Toggle>,

    /// Provide a so link for downloading the regular .so file
    #[clap(long = "soLink")]
    pub so_link: Option<String>,

    /// Provide a debug so link for downloading the debug .so file
    #[clap(long = "debugSoLink")]
    pub debug_so_link: Option<String>,

    /// Provide an additional file to add to the extra files list, prepend with - to remove an entry
    #[clap(long = "extraFiles")]
    pub extra_files: Option<String>,

    /// Provide an overridden name for the .so file
    #[clap(long = "overrideSoName")]
    pub override_so_name: Option<String>,

    /// Provide a link to the mod
    #[clap(long = "modLink")]
    pub mod_link: Option<String>,

    /// If this package is defined in a repo with more packages in subfolders, this is where you specify the subfolder to be used
    #[clap(long = "subFolder")]
    pub sub_folder: Option<String>,

    /// Additional options for compilation and edits to compilation related files.
    #[clap(subcommand)]
    pub compile_options: Option<EditExtraOptions>,
}

#[derive(Subcommand, Debug, Clone)]

pub enum EditExtraOptions {
    /// Additional options for compilation and edits to compilation related files.
    CompileOptions(CompileOptionsEditArgs),
}

#[derive(Args, Debug, Clone)]

pub struct CompileOptionsEditArgs {
    /// Additional include paths to add, relative to the extern directory. Prefix with a '-' to remove that entry
    #[clap(long = "includePaths")]
    pub include_paths: Option<String>,
    /// Additional system include paths to add, relative to the extern directory. Prefix with a '-' to remove that entry
    #[clap(long = "systemIncludes")]
    pub system_includes: Option<String>,
    /// Additional C++ features to add. Prefix with a '-' to remove that entry
    #[clap(long = "cppFeatures")]
    pub cpp_features: Option<String>,
    /// Additional C++ flags to add. Prefix with a '-' to remove that entry
    #[clap(long = "cppFlags")]
    pub cpp_flags: Option<String>,
    /// Additional C flags to add. Prefix with a '-' to remove that entry
    #[clap(long = "cFlags")]
    pub c_flags: Option<String>,
}

pub fn package_edit_extra_operation(edit_parameters: EditExtraArgs) {
    let mut package = PackageConfig::read();
    let mut any_changed = false;
    if let Some(branch_name) = edit_parameters.branch_name {
        package_edit_extra_branch_name(&mut package, branch_name);
        any_changed = true;
    }
    if let Some(headers_only) = edit_parameters.headers_only {
        package_edit_extra_headers_only(&mut package, headers_only.into());
        any_changed = true;
    }
    if let Some(static_linking) = edit_parameters.static_linking {
        package_edit_extra_static_linking(&mut package, static_linking.into());
        any_changed = true;
    }
    if let Some(so_link) = edit_parameters.so_link {
        package_edit_extra_so_link(&mut package, so_link);
        any_changed = true;
    }
    if let Some(extra_files) = edit_parameters.extra_files {
        package_edit_extra_extra_files(&mut package, extra_files);
        any_changed = true;
    }
    if let Some(debug_so_link) = edit_parameters.debug_so_link {
        package_edit_extra_debug_so_link(&mut package, debug_so_link);
        any_changed = true;
    }
    if let Some(mod_link) = edit_parameters.mod_link {
        package_edit_extra_mod_link(&mut package, mod_link);
        any_changed = true;
    }
    if let Some(override_so_name) = edit_parameters.override_so_name {
        package_edit_extra_override_so_name(&mut package, override_so_name);
        any_changed = true;
    }
    if let Some(sub_folder) = edit_parameters.sub_folder {
        package_edit_extra_sub_folder(&mut package, sub_folder);
        any_changed = true;
    }

    if any_changed {
        package.write();
        let mut shared_package = SharedPackageConfig::read();
        shared_package.config = package;
        shared_package.write();

        // HACK: Not sure if this is a proper way of doing this but it seems logical
        shared_package.write_define_cmake();
        shared_package.write_extern_cmake();
    }
}

pub fn package_edit_extra_branch_name(package: &mut PackageConfig, branch_name: String) {
    println!("Setting branch name: {:#?}", branch_name);
    package.info.additional_data.branch_name = Some(branch_name);
}

pub fn package_edit_extra_headers_only(package: &mut PackageConfig, headers_only: bool) {
    println!("Setting headers_only: {:#?}", headers_only);
    package.info.additional_data.headers_only = Some(headers_only);
}

pub fn package_edit_extra_static_linking(package: &mut PackageConfig, static_linking: bool) {
    println!("Setting static_linking: {:#?}", static_linking);
    package.info.additional_data.static_linking = Some(static_linking);
}

pub fn package_edit_extra_so_link(package: &mut PackageConfig, so_link: String) {
    println!("Setting so_link: {:#?}", so_link);
    package.info.additional_data.so_link = Some(so_link);
}

pub fn package_edit_extra_mod_link(package: &mut PackageConfig, mod_link: String) {
    println!("Setting mod_link: {:#?}", mod_link);
    package.info.additional_data.mod_link = Some(mod_link);
}

pub fn package_edit_extra_extra_files(package: &mut PackageConfig, extra_file: String) {
    println!("Setting extra_file: {}", extra_file);
    match extra_file.chars().next().unwrap() {
        '-' => {
            // remove
            package_edit_extra_remove_extra_files(package, extra_file[1..].to_string());
        }
        _ => {
            // add
            package_edit_extra_add_extra_files(package, extra_file);
        }
    }
}

pub fn package_edit_extra_remove_extra_files(package: &mut PackageConfig, extra_file: String) {
    if let Some(extra_files) = &mut package.info.additional_data.extra_files {
        if let Some(idx) = extra_files.iter().position(|f| f == &extra_file) {
            extra_files.remove(idx);
        }
    }
}

pub fn package_edit_extra_add_extra_files(package: &mut PackageConfig, extra_file: String) {
    if let Some(extra_files) = &mut package.info.additional_data.extra_files {
        if !extra_files.iter().any(|f| f == &extra_file) {
            extra_files.push(extra_file);
        }
    } else {
        package.info.additional_data.extra_files = Some(vec![extra_file]);
    }
}

pub fn package_edit_extra_debug_so_link(package: &mut PackageConfig, debug_so_link: String) {
    println!("Setting debug_so_link: {:#?}", debug_so_link);
    package.info.additional_data.debug_so_link = Some(debug_so_link);
}

pub fn package_edit_extra_override_so_name(package: &mut PackageConfig, override_so_name: String) {
    println!("Setting override_so_name: {:#?}", override_so_name);
    package.info.additional_data.override_so_name = Some(override_so_name);
}

pub fn package_edit_extra_sub_folder(package: &mut PackageConfig, sub_folder: String) {
    println!("Setting sub_folder: {:#?}", sub_folder);
    package.info.additional_data.sub_folder = Some(sub_folder);
}
