use std::collections::HashMap;
use std::env;
use std::path::Path;

use super::Arch;

#[derive(Debug, Clone, Default)]
pub struct ToolchainPaths {
  pub ranlib: String,
  pub ar: String,
  pub cc: String,
  pub cxx: String,
  pub llvm_as: String,
  pub ld: String,
  pub strip: String,
  pub objdump: String,
  pub objcopy: String,
  pub nm: String,
  pub bin_dir: String,
  pub lib_dir: String,
}

#[derive(Debug, Clone, Default)]
pub struct HmsPaths {
  pub include: Option<String>,
  pub lib: Option<String>,
}

pub fn resolve_toolchain_paths(root: &str) -> ToolchainPaths {
  let bin_dir = Path::new(root).join("bin");
  let lib_dir = Path::new(root).join("lib");

  #[cfg(target_os = "windows")]
  let exe_suffix = ".exe";
  #[cfg(not(target_os = "windows"))]
  let exe_suffix = "";

  let to_string = |p: std::path::PathBuf| p.to_string_lossy().to_string();
  let tool = |name: &str| to_string(bin_dir.join(format!("{}{}", name, exe_suffix)));

  ToolchainPaths {
    ranlib: tool("llvm-ranlib"),
    ar: tool("llvm-ar"),
    cc: tool("clang"),
    cxx: tool("clang++"),
    llvm_as: tool("llvm-as"),
    ld: tool("ld.lld"),
    strip: tool("llvm-strip"),
    objdump: tool("llvm-objdump"),
    objcopy: tool("llvm-objcopy"),
    nm: tool("llvm-nm"),
    bin_dir: to_string(bin_dir),
    lib_dir: to_string(lib_dir),
  }
}

pub fn resolve_hms_paths(hos_ndk: &str, arch: &Arch) -> HmsPaths {
  if hos_ndk.is_empty() {
    return HmsPaths::default();
  }

  let include_path = Path::new(hos_ndk)
    .join("native")
    .join("sysroot")
    .join("usr")
    .join("include");
  let lib_path = Path::new(hos_ndk)
    .join("native")
    .join("sysroot")
    .join("usr")
    .join("lib")
    .join(arch.c_target());

  HmsPaths {
    include: include_path
      .exists()
      .then(|| include_path.to_string_lossy().to_string()),
    lib: lib_path
      .exists()
      .then(|| lib_path.to_string_lossy().to_string()),
  }
}

pub fn append_hms_link_flags(base_flags: &mut Vec<String>, hms_paths: &HmsPaths) {
  if let Some(lib) = hms_paths.lib.as_ref() {
    base_flags.push(format!("-L{}", lib));
    base_flags.push(format!("-Wl,-rpath-link,{}", lib));
  }
}

pub fn apply_hms_include_env(
  prepare_env: &mut HashMap<String, String>,
  hms_paths: &HmsPaths,
  rust_target: &str,
) {
  let Some(include) = hms_paths.include.as_ref() else {
    return;
  };

  let include_flag = format!("-I{}", include);

  append_env_with_flag(prepare_env, "TARGET_CFLAGS", &include_flag);
  append_env_with_flag(prepare_env, "TARGET_CXXFLAGS", &include_flag);

  let bindgen_target = rust_target.replace('-', "_");
  append_env_with_flag(
    prepare_env,
    &format!("BINDGEN_EXTRA_CLANG_ARGS_{}", bindgen_target),
    &include_flag,
  );
  append_env_with_flag(
    prepare_env,
    &format!("BINDGEN_EXTRA_CLANG_ARGS_{}", bindgen_target.to_uppercase()),
    &include_flag,
  );
}

#[cfg(target_os = "windows")]
pub fn apply_windows_ohos_cmake_env(
  prepare_env: &mut HashMap<String, String>,
  rust_target: &str,
  toolchain: &ToolchainPaths,
) {
  let target_key = format!("CMAKE_GENERATOR_{}", rust_target);
  let target_key_alt = format!("CMAKE_GENERATOR_{}", rust_target.replace('-', "_"));
  let has_generator = env::var(&target_key).is_ok()
    || env::var(&target_key_alt).is_ok()
    || env::var("TARGET_CMAKE_GENERATOR").is_ok()
    || env::var("CMAKE_GENERATOR").is_ok();

  if !has_generator {
    prepare_env.insert(
      String::from("TARGET_CMAKE_GENERATOR"),
      String::from("Ninja"),
    );
  }

  if env::var("CC").is_err() {
    prepare_env.insert(String::from("CC"), toolchain.cc.clone());
  }
  if env::var("CXX").is_err() {
    prepare_env.insert(String::from("CXX"), toolchain.cxx.clone());
  }
  if env::var("ASM").is_err() {
    prepare_env.insert(String::from("ASM"), toolchain.cc.clone());
  }
}

#[cfg(not(target_os = "windows"))]
pub fn apply_windows_ohos_cmake_env(_: &mut HashMap<String, String>, _: &str, _: &ToolchainPaths) {}

fn append_env_with_flag(prepare_env: &mut HashMap<String, String>, key: &str, append: &str) {
  let current = env::var(key).unwrap_or_default();
  let merged = if current.is_empty() {
    append.to_string()
  } else {
    format!("{} {}", current, append)
  };
  prepare_env.insert(key.to_string(), merged);
}
