///|
/// Resolves the current executable path before running `on_current_path`.
fn with_current_executable_path(
  current_path_result : () -> Result[String, ReplaceSelfError],
  on_current_path : (String) -> Result[Unit, ReplaceSelfError],
) -> Result[Unit, ReplaceSelfError] {
  match current_path_result() {
    Ok(current_path) => on_current_path(current_path)
    Err(error) => Err(error)
  }
}

///|
/// Replaces the current executable through injected dependencies.
fn replace_self_with(
  platform : Platform,
  new_executable : String,
  current_path_result : () -> Result[String, ReplaceSelfError],
  replace_action : (String) -> Result[Unit, ReplaceSelfError],
) -> Result[Unit, ReplaceSelfError] {
  with_current_executable_path(current_path_result, fn(current_path) {
    match validate_replacement_path(platform, new_executable, current_path) {
      Ok(validated_path) => replace_action(validated_path)
      Err(error) => Err(error)
    }
  })
}

///|
/// Deletes the current executable through injected dependencies.
fn delete_self_with(
  platform : Platform,
  current_path_result : () -> Result[String, ReplaceSelfError],
  delete_action : () -> Result[Unit, ReplaceSelfError],
) -> Result[Unit, ReplaceSelfError] {
  if platform == Unsupported {
    return Err(UnsupportedPlatform(platform))
  }

  with_current_executable_path(current_path_result, fn(_) { delete_action() })
}

///|
/// Performs the native replace-self call.
fn replace_self_native_result(
  validated_path : String,
) -> Result[Unit, ReplaceSelfError] {
  unit_result_from_status(
    replace_self_ffi(@utf8.encode(validated_path)),
    "replace self",
    "Failed to replace the current executable",
  )
}

///|
/// Performs the native delete-self call.
fn delete_self_native_result() -> Result[Unit, ReplaceSelfError] {
  unit_result_from_status(
    delete_self_ffi(),
    "delete self",
    "Failed to delete the current executable",
  )
}

///|
/// Replaces the currently running executable with `new_executable`.
///
/// Use this when an application has already downloaded a new binary and wants
/// the current process image on disk to be swapped for that replacement.
/// The replacement file is treated as the source of truth for the next launch
/// of the program.
///
/// # Arguments
///
/// - `new_executable`: absolute path to the replacement executable file.
///   Leading and trailing whitespace is trimmed before validation. The
///   replacement file must already exist and must not be the same path as the
///   current executable.
///
/// # Returns
///
/// Returns `Ok(())` after the replacement has completed on Unix platforms, or
/// after the replacement helper has been scheduled successfully on Windows.
/// A successful result never includes the final executable path because the
/// current executable location is always the destination.
///
/// # Platform behavior
///
/// - Linux and macOS perform the replacement immediately with an atomic rename.
/// - Windows launches a detached helper script, then completes the replacement
///   after the current process exits and the executable file is unlocked.
///
/// # Notes
///
/// - On Unix hosts, the replacement source path is moved into the current
///   executable path, so the original source path typically disappears after a
///   successful replacement.
/// - On Windows, a successful return means the replacement was only scheduled
///   successfully. Callers should exit soon after `Ok(())` so the helper can
///   take over and finish the move.
///
/// # Errors
///
/// Returns `Err(ReplaceSelfError)` when:
///
/// - `EmptyReplacementPath`: the path is empty after trimming whitespace
/// - `RelativeReplacementPath`: the path is not absolute for the current
///   platform
/// - `ReplacementMatchesCurrentExecutable`: the replacement path equals the
///   current executable path
/// - `ExecutablePathUnavailable`: the runtime cannot determine the current
///   executable path
/// - `UnsupportedPlatform`: the host platform is unsupported by the native shim
/// - `NativeFailure`: the operating system rejects the replacement request
///
/// # Example
/// ```mbt nocheck
/// match @replace_self.replace_self("/tmp/app.next") {
///   Ok(()) => ()
///   Err(error) => println(error)
/// }
/// ```
pub fn replace_self(new_executable : String) -> Result[Unit, ReplaceSelfError] {
  replace_self_with(
    current_platform(),
    new_executable,
    current_executable_path_result,
    replace_self_native_result,
  )
}

///|
/// Deletes the currently running executable.
///
/// Use this when a process should remove its own executable file after it has
/// finished running, such as a one-shot bootstrapper or an uninstall helper.
/// This is useful when the executable should clean up its on-disk image as part
/// of its own shutdown flow.
///
/// # Returns
///
/// Returns `Ok(())` after the file has been unlinked on Unix platforms, or
/// after the deletion helper has been scheduled successfully on Windows.
/// The function does not report whether the current process has already exited;
/// it only reports whether the delete operation or delayed delete setup
/// succeeded.
///
/// # Platform behavior
///
/// - Linux and macOS unlink the file immediately.
/// - Windows launches a detached helper script that waits for the process to
///   exit, then removes the executable file.
///
/// # Notes
///
/// - On Unix hosts, unlinking removes the directory entry immediately even
///   though the current process may continue running until it exits.
/// - On Windows, a successful return means deletion was only scheduled
///   successfully. Callers should exit soon after `Ok(())` so the helper can
///   remove the file.
///
/// # Errors
///
/// Returns `Err(ReplaceSelfError)` when:
///
/// - `ExecutablePathUnavailable`: the runtime cannot determine the current
///   executable path
/// - `UnsupportedPlatform`: the host platform is unsupported by the native shim
/// - `NativeFailure`: the operating system rejects the deletion request
///
/// # Example
/// ```mbt nocheck
/// match @replace_self.delete_self() {
///   Ok(()) => ()
///   Err(error) => println(error)
/// }
/// ```
pub fn delete_self() -> Result[Unit, ReplaceSelfError] {
  delete_self_with(
    current_platform(),
    current_executable_path_result,
    delete_self_native_result,
  )
}