///|
/// Returns the per-user LaunchAgent plist path for `identifier`.
fn launch_agent_path(home_directory : String, identifier : String) -> String {
  join_unix_path(
    join_unix_path(home_directory, "Library"),
    "LaunchAgents/" + identifier + ".plist",
  )
}

///|
/// Returns the XDG autostart desktop-entry path for `identifier`.
fn linux_desktop_path(home_directory : String, identifier : String) -> String {
  join_unix_path(
    join_unix_path(join_unix_path(home_directory, ".config"), "autostart"),
    identifier + ".desktop",
  )
}

///|
/// Renders the command string stored in the Windows `Run` registry value.
fn render_command_line(
  config : AutoLaunchConfig,
  quote_argument : (String) -> String,
) -> String {
  let quoted : Array[String] = []
  for argument in build_program_arguments(config) {
    quoted.push(quote_argument(argument))
  }
  quoted.join(" ")
}

///|
/// Renders the command string stored in the Windows `Run` registry value.
fn windows_command(config : AutoLaunchConfig) -> String {
  render_command_line(config, windows_quote_argument)
}

///|
/// Renders a freedesktop.org desktop-entry file for Linux auto-start.
fn linux_desktop_contents(config : AutoLaunchConfig) -> String {
  let exec_line = render_command_line(config, desktop_quote_argument)
  (
    $|[Desktop Entry]
    $|Type=Application
    $|Version=1.0
    $|Name=\{config.name}
    $|Comment=Launch \{config.name} automatically at login
    $|Exec=\{exec_line}
    $|Terminal=false
    $|StartupNotify=false
    $|
  )
}

///|
/// Renders the `` items for a LaunchAgent `ProgramArguments` array.
fn plist_program_arguments(arguments : Array[String]) -> String {
  let builder = StringBuilder::new()
  for argument in arguments {
    builder.write_string("    ")
    builder.write_string(xml_escape(argument))
    builder.write_string("\n")
  }
  builder.to_string()
}

///|
/// Renders a macOS LaunchAgent plist.
fn mac_launch_agent_contents(config : AutoLaunchConfig) -> String {
  let arguments = build_program_arguments(config)
  let argument_items = plist_program_arguments(arguments)
  (
    $|
    $|
    $|
    $|
    $|  Label
    $|  \{xml_escape(config.identifier)}
    $|  ProgramArguments
    $|  
    $|\{argument_items}  
    $|  RunAtLoad
    $|  
    $|
    $|
    $|
  )
}

///|
/// Produces the file-backed entry for macOS or Linux.
fn file_entry_result(
  config : AutoLaunchConfig,
  platform : Platform,
  home_directory : String,
) -> Result[FileEntry, AutoLaunchError] {
  match platform {
    Platform::Macos =>
      Ok(FileEntry::{
        file_path: launch_agent_path(home_directory, config.identifier),
        contents: mac_launch_agent_contents(config),
      })
    Platform::Linux =>
      Ok(FileEntry::{
        file_path: linux_desktop_path(home_directory, config.identifier),
        contents: linux_desktop_contents(config),
      })
    _ => Err(AutoLaunchError::UnsupportedPlatform(platform))
  }
}

///|
/// Resolves the concrete file entry for a launcher.
fn resolve_file_entry(
  launcher : AutoLaunch,
  platform : Platform,
  get_home_directory : () -> Result[String, AutoLaunchError],
) -> Result[FileEntry, AutoLaunchError] {
  match get_home_directory() {
    Ok(home_directory) =>
      file_entry_result(launcher.config, platform, home_directory)
    Err(error) => Err(error)
  }
}

///|
/// Writes a resolved file-backed launcher entry.
fn write_resolved_file_entry(
  launcher : AutoLaunch,
  platform : Platform,
  get_home_directory : () -> Result[String, AutoLaunchError],
  write_file : (String, String) -> Result[Unit, AutoLaunchError],
) -> Result[Unit, AutoLaunchError] {
  match resolve_file_entry(launcher, platform, get_home_directory) {
    Ok(entry) => write_file(entry.file_path, entry.contents)
    Err(error) => Err(error)
  }
}

///|
/// Removes a resolved file-backed launcher entry.
fn remove_resolved_file_entry(
  launcher : AutoLaunch,
  platform : Platform,
  get_home_directory : () -> Result[String, AutoLaunchError],
  remove_file : (String) -> Result[Unit, AutoLaunchError],
) -> Result[Unit, AutoLaunchError] {
  match resolve_file_entry(launcher, platform, get_home_directory) {
    Ok(entry) => remove_file(entry.file_path)
    Err(error) => Err(error)
  }
}

///|
/// Checks whether a resolved file-backed launcher entry exists.
fn resolved_file_entry_exists(
  launcher : AutoLaunch,
  platform : Platform,
  get_home_directory : () -> Result[String, AutoLaunchError],
  file_exists : (String) -> Result[Bool, AutoLaunchError],
) -> Result[Bool, AutoLaunchError] {
  match resolve_file_entry(launcher, platform, get_home_directory) {
    Ok(entry) => file_exists(entry.file_path)
    Err(error) => Err(error)
  }
}

///|
/// Enables auto-launch using injected side-effect functions.
fn enable_with(
  launcher : AutoLaunch,
  platform : Platform,
  get_home_directory : () -> Result[String, AutoLaunchError],
  set_windows_entry : (String, String) -> Result[Unit, AutoLaunchError],
  write_file : (String, String) -> Result[Unit, AutoLaunchError],
) -> Result[Unit, AutoLaunchError] {
  match platform {
    Platform::Windows =>
      set_windows_entry(
        launcher.config.identifier,
        windows_command(launcher.config),
      )
    Platform::Macos | Platform::Linux =>
      write_resolved_file_entry(
        launcher, platform, get_home_directory, write_file,
      )
    Platform::Unsupported => Err(AutoLaunchError::UnsupportedPlatform(platform))
  }
}

///|
/// Disables auto-launch using injected side-effect functions.
fn disable_with(
  launcher : AutoLaunch,
  platform : Platform,
  get_home_directory : () -> Result[String, AutoLaunchError],
  delete_windows_entry : (String) -> Result[Unit, AutoLaunchError],
  remove_file : (String) -> Result[Unit, AutoLaunchError],
) -> Result[Unit, AutoLaunchError] {
  match platform {
    Platform::Windows => delete_windows_entry(launcher.config.identifier)
    Platform::Macos | Platform::Linux =>
      remove_resolved_file_entry(
        launcher, platform, get_home_directory, remove_file,
      )
    Platform::Unsupported => Err(AutoLaunchError::UnsupportedPlatform(platform))
  }
}

///|
/// Checks whether auto-launch is enabled using injected lookup functions.
fn is_enabled_with(
  launcher : AutoLaunch,
  platform : Platform,
  get_home_directory : () -> Result[String, AutoLaunchError],
  windows_entry_exists : (String) -> Result[Bool, AutoLaunchError],
  file_exists : (String) -> Result[Bool, AutoLaunchError],
) -> Result[Bool, AutoLaunchError] {
  match platform {
    Platform::Windows => windows_entry_exists(launcher.config.identifier)
    Platform::Macos | Platform::Linux =>
      resolved_file_entry_exists(
        launcher, platform, get_home_directory, file_exists,
      )
    Platform::Unsupported => Err(AutoLaunchError::UnsupportedPlatform(platform))
  }
}