///|
/// Loads this application's identifier from managed Proton metadata.
///
/// Development runs read `proton.project.json`. Packaged applications read
/// the sanitized `proton-package.json` staged beside the executable.
pub fn App::load_config(self : App) -> App {
  match load_managed_application_identifier() {
    Ok(identifier) => self.set_application_identity(ProjectConfig(identifier))
    Err(error) => {
      self.validation_errors.push(error)
      self
    }
  }
}

///|
/// Sets the canonical identifier for an application without project metadata.
pub fn App::identifier(self : App, identifier : String) -> App {
  match resolve_explicit_application_identifier(identifier) {
    Ok(identifier) => self.set_application_identity(Explicit(identifier))
    Err(error) => {
      self.validation_errors.push(error)
      self
    }
  }
}

///|
fn resolve_explicit_application_identifier(
  identifier : String,
) -> Result[String, AppConfigurationError] {
  let identifier = match validate_application_identifier(identifier) {
    Ok(identifier) => identifier
    Err(error) => return Err(error)
  }
  guard packaged_application_manifest_path() is Some(path) else {
    return Ok(identifier)
  }
  match load_packaged_application_identifier(path) {
    Err(error) => Err(error)
    Ok(packaged_identifier) =>
      verify_packaged_application_identifier(identifier, packaged_identifier)
  }
}

///|
fn verify_packaged_application_identifier(
  identifier : String,
  packaged_identifier : String,
) -> Result[String, AppConfigurationError] {
  if identifier == packaged_identifier {
    Ok(identifier)
  } else {
    Err(
      InvalidSetting(
        name="identifier",
        message="does not match packaged application metadata (expected " +
          packaged_identifier +
          ")",
      ),
    )
  }
}

///|
fn App::set_application_identity(
  self : App,
  identity : ApplicationIdentitySource,
) -> App {
  match self.application_identity {
    Some(_) =>
      self.validation_errors.push(
        InvalidSetting(
          name="identifier",
          message="is already configured; use either .load_config() or .identifier(...), not both",
        ),
      )
    None => self.application_identity = Some(identity)
  }
  self
}

///|
fn App::application_identifier(self : App) -> String raise AppPathError {
  match self.application_identity {
    Some(ProjectConfig(identifier) | Explicit(identifier)) => identifier
    None => raise MissingApplicationIdentifier
  }
}

///|
fn validate_application_identifier(
  identifier : String,
) -> Result[String, AppConfigurationError] {
  try @proton_config.validate_application_identifier(identifier) catch {
    @proton_config.InvalidField(expectation~, ..) =>
      Err(InvalidSetting(name="identifier", message=expectation))
    error => Err(InvalidSetting(name="identifier", message=error.message()))
  } noraise {
    identifier => Ok(identifier)
  }
}

///|
fn load_managed_application_identifier() -> Result[
  String,
  AppConfigurationError,
] {
  match packaged_application_manifest_path() {
    Some(path) => load_packaged_application_identifier(path)
    None => load_project_application_identifier(project_config_path())
  }
}

///|
fn project_config_path() -> String {
  match @env.get_env_var("PROTON_PROJECT_CONFIG") {
    Some(path) if path.trim().to_owned() != "" => {
      let path : @mbpath.Path = path.trim().to_owned()
      path.resolve().to_string()
    }
    _ =>
      @mbpath.Path(runtime_resource_dir())
      .join("proton.project.json")
      .normalize()
      .to_string()
  }
}

///|
fn load_project_application_identifier(
  path : String,
) -> Result[String, AppConfigurationError] {
  try @proton_config.load_project_config_from_file(path) catch {
    error => Err(InvalidSetting(name="config", message=error.message()))
  } noraise {
    config => Ok(config.identifier())
  }
}

///|
fn load_packaged_application_identifier(
  path : String,
) -> Result[String, AppConfigurationError] {
  let source = @mbfs.read_file_to_string(path, encoding="utf8") catch {
    error =>
      return Err(
        InvalidSetting(
          name="config",
          message="failed to read packaged application metadata: " +
            @debug.render(Repr(error)),
        ),
      )
  }
  packaged_application_identifier_from_json(source)
}

///|
fn packaged_application_identifier_from_json(
  source : String,
) -> Result[String, AppConfigurationError] {
  let json = @json.parse(source) catch {
    error =>
      return Err(
        InvalidSetting(
          name="config",
          message="invalid packaged application metadata: " + error.to_string(),
        ),
      )
  }
  guard json is Object(fields) else {
    return Err(
      InvalidSetting(
        name="config",
        message="invalid packaged application metadata: expected an object",
      ),
    )
  }
  guard fields.get("identifier") is Some(String(identifier)) else {
    return Err(
      InvalidSetting(
        name="config",
        message="packaged application metadata requires identifier",
      ),
    )
  }
  validate_application_identifier(identifier)
}