// MSVC child-process environment for Windows desktop builds, mirroring
// `moui/scripts/windows/msvc_env.ps1`. `moui_cli_process_exec` REPLACES the
// child environment when a non-empty env array is supplied, so the full
// environment is assembled here: capture the `vcvarsall.bat` environment via
// `cmd /c ... && set`, then layer CC/CXX/CL/LINK and the vcpkg zlib triplet
// on top. Builds on other platforms pass an empty env array and inherit.

///|
priv enum EnvOverride {
  SetEnv(String, String)
  /// Prepend `value;` to the current value of `key` (PATH-style keys).
  PrependPath(String, String)
}

///|
/// Parse `cmd /c set` output into KEY/VALUE pairs. Lines without `=` and
/// `=KEY=VALUE`-style oddities are skipped.
fn parse_env_output(text : String) -> Array[(String, String)] {
  let entries : Array[(String, String)] = []
  for line_view in text.split("\n") {
    let line = line_view.to_owned()
    let trimmed = strip_trailing_cr(line)
    if trimmed == "" || trimmed.has_prefix("=") {
      continue
    }
    match trimmed.split_once("=") {
      Some((key_view, value_view)) =>
        entries.push((key_view.to_owned(), value_view.to_owned()))
      None => ()
    }
  }
  entries
}

///|
fn strip_trailing_cr(line : String) -> String {
  if line.has_suffix("\r") {
    line[:line.length() - 1].to_owned()
  } else {
    line
  }
}

///|
fn apply_env_overrides(
  base : Array[(String, String)],
  overrides : Array[EnvOverride],
) -> Array[(String, String)] {
  let result = base.copy()
  for entry in overrides {
    match entry {
      SetEnv(key, value) => {
        let mut found = false
        for index, entry in result {
          if entry.0 == key {
            result[index] = (key, value)
            found = true
            break
          }
        }
        if !found {
          result.push((key, value))
        }
      }
      PrependPath(key, value) => {
        if value == "" {
          continue
        }
        let mut found = false
        for index, entry in result {
          if entry.0 == key {
            let current = entry.1
            if current == "" {
              result[index] = (key, value)
            } else if current == value || current.has_prefix(value + ";") {
              // Already present at the front; keep the environment stable.
              result[index] = (key, current)
            } else {
              result[index] = (key, value + ";" + current)
            }
            found = true
            break
          }
        }
        if !found {
          result.push((key, value))
        }
      }
    }
  }
  result
}

///|
fn vswhere_path() -> String {
  let program_files = @env.get_env_var("ProgramFiles(x86)").unwrap_or(
    "C:/Program Files (x86)",
  )
  join_path(program_files, "Microsoft Visual Studio/Installer/vswhere.exe")
}

///|
/// Locate `vcvarsall.bat` through vswhere. Fails with an actionable message
/// when Visual Studio Build Tools with the VC.Tools.x86.x64 component are
/// missing.
fn locate_vcvarsall() -> Result[String, String] {
  let vswhere = vswhere_path()
  if !path_is_file(vswhere) {
    return Err(
      "vswhere.exe was not found at " +
      vswhere +
      "; install Visual Studio Build Tools with: winget install --id Microsoft.VisualStudio.2022.BuildTools -e",
    )
  }
  let result = process_exec_argv(
    [
      vswhere, "-latest", "-products", "*", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
      "-property", "installationPath",
    ],
    capture=true,
  )
  let install_path = match result {
    Ok(value) =>
      if value.exit_code != 0 {
        return Err(
          "vswhere exited with code " +
          value.exit_code.to_string() +
          ": " +
          value.stderr,
        )
      } else {
        value.stdout.trim().to_owned()
      }
    Err(message) => return Err("could not run vswhere: " + message)
  }
  if install_path == "" {
    return Err(
      "no Visual Studio installation with the C++ x64/x86 toolset was found; install Visual Studio Build Tools with: winget install --id Microsoft.VisualStudio.2022.BuildTools -e",
    )
  }
  let vcvars = join_path(install_path, "VC/Auxiliary/Build/vcvarsall.bat")
  if !path_is_file(vcvars) {
    return Err("vcvarsall.bat was not found: " + vcvars)
  }
  Ok(vcvars)
}

///|
/// Capture the full environment produced by importing `vcvarsall.bat`.
fn msvc_capture_environment(
  vcvars : String,
  arch? : String = "x64",
) -> Result[Array[(String, String)], String] {
  let command = "\"" + vcvars + "\" " + arch + " >nul && set"
  let result = process_exec_argv(["cmd.exe", "/d", "/c", command], capture=true)
  match result {
    Ok(value) =>
      if value.exit_code != 0 {
        Err(
          "importing the MSVC environment failed (exit " +
          value.exit_code.to_string() +
          "): " +
          value.stderr,
        )
      } else {
        Ok(parse_env_output(value.stdout))
      }
    Err(message) => Err("could not import the MSVC environment: " + message)
  }
}

///|
/// First existing zlib import library under the vcpkg triplet root.
fn msvc_zlib_lib_file(triplet_root : String) -> String? {
  for candidate in ["lib/zlib.lib", "lib/z.lib", "lib/zlibstatic.lib"] {
    let path = join_path(triplet_root, candidate)
    if path_is_file(path) {
      return Some(path)
    }
  }
  None
}

///|
/// First existing zlib runtime DLL under the vcpkg triplet root.
fn msvc_zlib_runtime_dll(triplet_root : String) -> String? {
  for candidate in ["bin/zlib1.dll", "bin/z.dll", "bin/zlib.dll"] {
    let path = join_path(triplet_root, candidate)
    if path_is_file(path) {
      return Some(path)
    }
  }
  None
}

///|
/// Layer the MoUI MSVC build flags over a captured vcvarsall environment.
/// Flag strings match `moui/scripts/windows/msvc_env.ps1` exactly. Both
/// `zlib_triplet` and `zlib_lib` are resolved by the caller so this function
/// stays pure; `zlib_lib` must live under `zlib_triplet`.
fn msvc_build_overrides(
  zlib_triplet : String?,
  zlib_lib : String?,
  wgpu_native_root? : String? = None,
) -> Result[Array[EnvOverride], String] {
  let overrides : Array[EnvOverride] = []
  overrides.push(SetEnv("CC", "cl.exe"))
  overrides.push(SetEnv("CXX", "cl.exe"))
  overrides.push(
    SetEnv(
      "CL", "/DNOMINMAX /experimental:c11atomics /utf-8 /wd4005 /DMOONBIT_FFI_EXPORT=",
    ),
  )
  match (zlib_triplet, zlib_lib) {
    (Some(triplet), Some(lib)) => {
      overrides.push(
        SetEnv(
          "LINK",
          "comdlg32.lib shell32.lib advapi32.lib ole32.lib user32.lib gdi32.lib dwrite.lib d2d1.lib " +
          lib +
          " /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup",
        ),
      )
      overrides.push(PrependPath("INCLUDE", join_path(triplet, "include")))
      overrides.push(PrependPath("LIB", join_path(triplet, "lib")))
      overrides.push(PrependPath("PATH", join_path(triplet, "bin")))
      overrides.push(SetEnv("MOUI_MSVC_ZLIB_TRIPLET_ROOT", triplet))
    }
    _ =>
      return Err(
        "zlib:x64-windows was not detected; run scripts/windows/setup_msvc_deps.ps1 -InstallZlib",
      )
  }
  match wgpu_native_root {
    Some(root) => overrides.push(SetEnv("MBT_WGPU_NATIVE_ROOT", root))
    None => ()
  }
  Ok(overrides)
}

///|
/// Resolve the zlib triplet and import library for a project root.
fn msvc_resolve_zlib(project_root : String) -> Result[(String, String), String] {
  match msvc_zlib_root(project_root) {
    Some(triplet) =>
      match msvc_zlib_lib_file(triplet) {
        Some(lib) => Ok((triplet, lib))
        None =>
          Err(
            "zlib import library was not found under " +
            triplet +
            "; run scripts/windows/setup_msvc_deps.ps1 -InstallZlib",
          )
      }
    None =>
      Err(
        "zlib:x64-windows was not detected; run scripts/windows/setup_msvc_deps.ps1 -InstallZlib",
      )
  }
}

///|
/// Full child environment for `moon build --target native` on Windows.
/// Returns an empty array when the platform is not Windows or the user
/// already configured a C compiler (CC preset), meaning "inherit as-is".
fn windows_moon_env(
  project_root : String,
  host : HostPlatform,
  wgpu_native_root? : String? = None,
) -> Result[Array[(String, String)], String] {
  if host != Windows {
    return Ok([])
  }
  if @env.get_env_var("CC") is Some(_) {
    return Ok([])
  }
  let vcvars = match locate_vcvarsall() {
    Ok(path) => path
    Err(message) => return Err(message)
  }
  let base = match msvc_capture_environment(vcvars) {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  let (zlib_triplet, zlib_lib) = match msvc_resolve_zlib(project_root) {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  let overrides = match
    msvc_build_overrides(Some(zlib_triplet), Some(zlib_lib), wgpu_native_root~) {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  Ok(apply_env_overrides(base, overrides))
}