///|
pub(all) enum Host {
  Windows
  Posix
} derive(Debug, Eq)

///|
pub struct Tool {
  pub name : String
  pub path : String
} derive(Debug, Eq)

///|
pub struct Toolchain {
  pub host : Host
  pub compiler : Tool?
  pub linker : Tool?
  pub archiver : Tool?
  pub symbol_tool : Tool?
  pub sdk : String?
  pub diagnostics : Array[String]
} derive(Debug)

///|
/// Resolve the native tools used by HTTP and Node-API builds.
///
/// Explicit MOON_CC, MOON_CXX, MOON_AR, and MOON_NM values are
/// authoritative. An explicit path that does not exist is reported as an
/// error rather than silently replaced by another compiler.
pub async fn discover(builder~ : String = "auto") -> Toolchain {
  let host = if @env.get_env_var("OS") == Some("Windows_NT") {
    Windows
  } else {
    Posix
  }
  let diagnostics : Array[String] = []
  let compiler_name = if builder == "auto" {
    if host is Windows {
      "cl"
    } else {
      "cc"
    }
  } else {
    builder
  }
  let compiler = select_tool("MOON_CC", compiler_name, host, diagnostics)
  let linker = select_tool(
    "MOON_LINK",
    if host is Windows {
      "link"
    } else {
      compiler_name
    },
    host,
    diagnostics,
  )
  let archiver = select_tool(
    "MOON_AR",
    if host is Windows {
      "lib"
    } else {
      "ar"
    },
    host,
    diagnostics,
  )
  let symbol_tool = select_tool(
    "MOON_NM",
    if host is Windows {
      "dumpbin"
    } else {
      "nm"
    },
    host,
    diagnostics,
  )
  let sdk = match host {
    Windows => @env.get_env_var("WindowsSdkDir")
    Posix => @env.get_env_var("SDKROOT")
  }
  { host, compiler, linker, archiver, symbol_tool, sdk, diagnostics, }
}

///|
async fn select_tool(
  variable : String,
  default_name : String,
  host : Host,
  diagnostics : Array[String],
) -> Tool? {
  match @env.get_env_var(variable) {
    Some(explicit) =>
      if !safe_exists(explicit) {
        diagnostics.push(
          variable + " points to a missing executable: " + explicit,
        )
        None
      } else {
        Some({ name: default_name, path: explicit, })
      }
    None => {
      let suffixes = if host is Windows { [".exe", ""] } else { [""] }
      let separator = if host is Windows { ";" } else { ":" }
      let path_value = @env.get_env_var("PATH").unwrap_or("")
      for directory in path_value.split(separator) {
        for suffix in suffixes {
          let candidate = directory.to_owned() + "/" + default_name + suffix
          if safe_exists(candidate) {
            return Some({ name: default_name, path: safe_realpath(candidate), })
          }
        }
      }
      diagnostics.push("tool not found on PATH: " + default_name)
      None
    }
  }
}

///|
async fn safe_exists(path : String) -> Bool {
  @fs.exists(path) catch {
    _ => false
  }
}

///|
async fn safe_realpath(path : String) -> String {
  @fs.realpath(path) catch {
    _ => path
  }
}

///|
pub fn Toolchain::is_usable(self : Toolchain) -> Bool {
  self.diagnostics.is_empty() &&
  self.compiler is Some(_) &&
  self.linker is Some(_)
}