///|
/// Network helpers built on the system `curl`.
///
/// `curl` ships with macOS and every mainstream Linux distribution and speaks
/// HTTPS, redirects and retries out of the box. Shelling out to it keeps the
/// tool small while behaving correctly against `cli.moonbitlang.com`,
/// `mooncakes.io` and the CloudFront download endpoints.

///|
/// Create a unique temporary file path and return it.
pub fn temp_path() -> String raise MbError {
  let out = run_capture("mktemp \"${TMPDIR:-/tmp}/mb.XXXXXX\"")
  let path = out.trim().to_owned()
  if path == "" {
    raise MbError("failed to create a temporary file")
  }
  path
}

///|
/// Verify the external programs `mb` relies on are present.
pub fn ensure_tools() -> Unit raise MbError {
  let missing = []
  if !run_ok("command -v curl >/dev/null 2>&1") {
    missing.push("curl")
  }
  if !run_ok("command -v tar >/dev/null 2>&1") {
    missing.push("tar")
  }
  if !missing.is_empty() {
    raise MbError(
      "required program(s) not found on PATH: " + missing.join(", "),
    )
  }
}

///|
/// Download `url` to `dest`, following redirects and retrying transient
/// failures. Shows a progress bar for large downloads on a terminal.
pub fn download(
  url : String,
  dest : String,
  progress : Bool,
) -> Unit raise MbError {
  let parent = parent_dir(dest)
  if parent != "" {
    mkdir_p(parent)
  }
  let style = if progress && is_tty() { " --progress-bar" } else { " -s" }
  let cmd = "curl -fL" +
    style +
    " --retry 3 --retry-delay 1 --connect-timeout 30 -o " +
    shell_quote(dest) +
    " " +
    shell_quote(url)
  let code = run_status(cmd)
  if code != 0 {
    rm_rf(dest) catch {
      _ => ()
    }
    raise MbError("failed to download " + url + " (curl exit \{code})")
  }
}

///|
/// Fetch `url` and return the response body as bytes.
pub fn fetch_bytes(url : String) -> Bytes raise MbError {
  let tmp = temp_path()
  download(url, tmp, false)
  let data = read_bytes(tmp)
  rm_rf(tmp) catch {
    _ => ()
  }
  data
}

///|
/// Fetch `url` and return the response body as text.
pub fn fetch_text(url : String) -> String raise MbError {
  @utf8.decode_lossy(fetch_bytes(url))
}

///|
/// Fetch and parse a JSON document.
pub fn fetch_json(url : String) -> Json raise MbError {
  let text = fetch_text(url)
  @json.parse(text) catch {
    _ => raise MbError("invalid JSON returned by " + url)
  }
}