///|
/// Canonical auto-launch settings shared by all platform backends.
struct AutoLaunchConfig {
  name : String
  app_path : String
  identifier : String
  launch_in_background : Bool
  background_arg : String
  extra_arguments : Array[String]
}

///|
/// A rendered file-based auto-launch entry ready to be written to disk.
struct FileEntry {
  file_path : String
  contents : String
} derive(Debug, Eq)

///|
/// Stores the normalized auto-launch configuration for one application.
///
/// `AutoLaunch` values are created through [`new`](../auto_launch.mbt) after
/// validation and normalization have already completed. Callers therefore get a
/// stable, reusable handle that can be queried or applied multiple times
/// without having to repeat path validation or identifier generation.
pub struct AutoLaunch {
  config : AutoLaunchConfig
}

///|
/// Trims leading and trailing whitespace from `text`.
fn trim_text(text : String) -> String {
  text.trim().to_owned()
}

///|
/// Returns whether `ch` is an ASCII lowercase letter.
fn is_ascii_lower_letter(ch : Char) -> Bool {
  ch >= 'a' && ch <= 'z'
}

///|
/// Returns whether `ch` is an ASCII uppercase letter.
fn is_ascii_upper_letter(ch : Char) -> Bool {
  ch >= 'A' && ch <= 'Z'
}

///|
/// Returns whether `ch` is an ASCII digit.
fn is_ascii_digit(ch : Char) -> Bool {
  ch >= '0' && ch <= '9'
}

///|
/// Returns whether `ch` is any ASCII alphabetic character.
fn is_ascii_letter(ch : Char) -> Bool {
  is_ascii_lower_letter(ch) || is_ascii_upper_letter(ch)
}

///|
/// Returns whether `ch` is allowed punctuation in sanitized identifiers.
fn is_identifier_punctuation(ch : Char) -> Bool {
  ch == '-' || ch == '_' || ch == '.'
}

///|
/// Converts free-form text into a backend-safe identifier fragment.
fn sanitize_identifier(text : String) -> String {
  let lowered = text.to_lower()
  let builder = StringBuilder::new()
  let mut last_was_separator = true
  for ch in lowered {
    if is_ascii_letter(ch) ||
      is_ascii_digit(ch) ||
      is_identifier_punctuation(ch) {
      builder.write_char(ch)
      last_was_separator = false
    } else if !last_was_separator {
      builder.write_char('-')
      last_was_separator = true
    }
  }
  builder.to_string().trim(chars="-._").to_owned()
}

///|
/// Chooses the final identifier for a launcher configuration.
fn default_identifier(name : String, explicit_identifier : String?) -> String {
  let source = match explicit_identifier {
    Some(identifier) => trim_text(identifier)
    None => trim_text(name)
  }
  let sanitized = sanitize_identifier(source)
  if sanitized.is_empty() {
    "auto-launch"
  } else {
    sanitized
  }
}

///|
/// Returns whether `path` looks like an absolute Windows drive path.
fn is_windows_drive_path(path : String) -> Bool {
  match (path.get_char(0), path.get_char(1), path.get_char(2)) {
    (Some(drive), Some(':'), Some('\\')) if is_ascii_letter(drive) => true
    (Some(drive), Some(':'), Some('/')) if is_ascii_letter(drive) => true
    _ => false
  }
}

///|
/// Returns whether `path` uses the Windows UNC form.
fn is_windows_unc_path(path : String) -> Bool {
  path.has_prefix("\\\\") || path.has_prefix("//")
}

///|
/// Performs platform-aware absolute-path detection.
fn is_absolute_path(platform : Platform, path : String) -> Bool {
  match platform {
    Platform::Windows =>
      is_windows_drive_path(path) || is_windows_unc_path(path)
    Platform::Macos => path.has_prefix("/")
    Platform::Linux => path.has_prefix("/")
    Platform::Unsupported => false
  }
}

///|
/// Builds the ordered program argument vector used by every backend.
fn build_program_arguments(config : AutoLaunchConfig) -> Array[String] {
  let arguments : Array[String] = [config.app_path]
  if config.launch_in_background {
    arguments.push(config.background_arg)
  }
  for argument in config.extra_arguments {
    arguments.push(argument)
  }
  arguments
}

///|
/// Appends `count` copies of `ch` into `builder`.
fn write_repeated_char(builder : StringBuilder, ch : Char, count : Int) -> Unit {
  for _ in 0.. String {
  let builder = StringBuilder::new()
  builder.write_char('"')
  let mut backslash_count = 0
  for ch in argument {
    if ch == '\\' {
      backslash_count += 1
    } else if ch == '"' {
      write_repeated_char(builder, '\\', backslash_count * 2 + 1)
      builder.write_char('"')
      backslash_count = 0
    } else {
      write_repeated_char(builder, '\\', backslash_count)
      builder.write_char(ch)
      backslash_count = 0
    }
  }
  write_repeated_char(builder, '\\', backslash_count * 2)
  builder.write_char('"')
  builder.to_string()
}

///|
/// Quotes one argument for file-based Unix desktop launchers.
fn desktop_quote_argument(argument : String) -> String {
  let escaped = argument
    .replace_all(old="\\", new="\\\\")
    .replace_all(old="\"", new="\\\"")
    .replace_all(old="$", new="\\$")
    .replace_all(old="`", new="\\`")
  "\"" + escaped + "\""
}

///|
/// Escapes XML-special characters for LaunchAgent plist output.
fn xml_escape(text : String) -> String {
  text
  .replace_all(old="&", new="&")
  .replace_all(old="<", new="<")
  .replace_all(old=">", new=">")
  .replace_all(old="\"", new=""")
  .replace_all(old="'", new="'")
}

///|
/// Joins `base` and `child` using a single `/` separator.
fn join_unix_path(base : String, child : String) -> String {
  if base.has_suffix("/") {
    base + child
  } else {
    base + "/" + child
  }
}