///|
pub(all) enum ClientProfile {
  ModernHttpClient
  LegacyHttpClient
  BrowserDownload
  MultipartFormParser
  ObjectStorageGateway
} derive(Eq, Debug)

///|
pub(all) struct CompatibilityReport {
  profile : ClientProfile
  header : String
  compatible : Bool
  recommendations : Array[Recommendation]
} derive(Eq, Debug)

///|
pub fn ClientProfile::name(self : ClientProfile) -> String {
  match self {
    ModernHttpClient => "modern-http-client"
    LegacyHttpClient => "legacy-http-client"
    BrowserDownload => "browser-download"
    MultipartFormParser => "multipart-form-parser"
    ObjectStorageGateway => "object-storage-gateway"
  }
}

///|
pub fn check_compatibility(
  header : String,
  profile : ClientProfile,
) -> CompatibilityReport {
  let recommendations : Array[Recommendation] = []
  match parse_header(header) {
    Ok(disposition) => {
      append_profile_recommendations(recommendations, disposition, profile)
      {
        profile,
        header,
        compatible: !recommendations.any(r => r.severity == Error),
        recommendations,
      }
    }
    Err(err) => {
      recommendations.push(
        Recommendation::make(
          FixSyntax,
          Error,
          err.message,
          None,
          None,
          err.offset,
        ),
      )
      { profile, header, compatible: false, recommendations }
    }
  }
}

///|
pub fn compatibility_matrix(header : String) -> Array[CompatibilityReport] {
  [
    check_compatibility(header, ModernHttpClient),
    check_compatibility(header, LegacyHttpClient),
    check_compatibility(header, BrowserDownload),
    check_compatibility(header, MultipartFormParser),
    check_compatibility(header, ObjectStorageGateway),
  ]
}

///|
pub fn CompatibilityReport::status(self : CompatibilityReport) -> ReviewStatus {
  if self.recommendations.any(r => r.severity == Error) {
    Rejected
  } else if self.recommendations.any(r => r.severity == Warning) {
    NeedsAttention
  } else {
    Accepted
  }
}

///|
pub fn CompatibilityReport::to_text(self : CompatibilityReport) -> String {
  let buf = StringBuilder(size_hint=self.header.length() + 96)
  buf.write_string(self.profile.name())
  buf.write_string(": ")
  buf.write_string(self.status().name())
  if self.recommendations.length() > 0 {
    for r in self.recommendations {
      buf.write_char('\n')
      buf.write_string("- ")
      buf.write_string(r.to_text())
    }
  }
  buf.to_string()
}

///|
pub fn compatibility_matrix_to_text(
  reports : Array[CompatibilityReport],
) -> String {
  let buf = StringBuilder(size_hint=256)
  let mut first = true
  for report in reports {
    if first {
      first = false
    } else {
      buf.write_string("\n\n")
    }
    buf.write_string(report.to_text())
  }
  buf.to_string()
}

///|
fn append_profile_recommendations(
  recommendations : Array[Recommendation],
  disposition : Disposition,
  profile : ClientProfile,
) -> Unit {
  append_diagnostic_recommendations(recommendations, disposition.diagnostics)
  match profile {
    ModernHttpClient =>
      append_modern_recommendations(recommendations, disposition)
    LegacyHttpClient =>
      append_legacy_recommendations(recommendations, disposition)
    BrowserDownload =>
      append_browser_download_recommendations(recommendations, disposition)
    MultipartFormParser =>
      append_multipart_recommendations(recommendations, disposition)
    ObjectStorageGateway =>
      append_object_storage_recommendations(recommendations, disposition)
  }
}

///|
fn append_modern_recommendations(
  recommendations : Array[Recommendation],
  disposition : Disposition,
) -> Unit {
  match disposition.filename() {
    Some(value) =>
      if has_non_ascii(value) && disposition.extended_filename() is None {
        recommendations.push(
          Recommendation::make(
            PreferFilenameStar,
            Warning,
            "Modern HTTP clients should receive filename* for non-ASCII names",
            Some("filename*"),
            Some(value),
            None,
          ),
        )
      }
    None => ()
  }
}

///|
fn append_legacy_recommendations(
  recommendations : Array[Recommendation],
  disposition : Disposition,
) -> Unit {
  match disposition.plain_filename() {
    Some(value) =>
      if has_non_ascii(value) {
        recommendations.push(
          Recommendation::make(
            AddAsciiFallback,
            Warning,
            "Legacy clients need an ASCII filename fallback",
            Some("filename"),
            Some(filename_ascii_fallback(value)),
            None,
          ),
        )
      }
    None =>
      if disposition.extended_filename() is Some(value) {
        recommendations.push(
          Recommendation::make(
            AddAsciiFallback,
            Warning,
            "Legacy clients may ignore filename* without a plain filename",
            Some("filename"),
            Some(filename_ascii_fallback(value)),
            None,
          ),
        )
      }
  }
}

///|
fn append_browser_download_recommendations(
  recommendations : Array[Recommendation],
  disposition : Disposition,
) -> Unit {
  if !disposition.is_attachment() {
    recommendations.push(
      Recommendation::make(
        PreferAttachmentDisposition,
        Warning,
        "Browser download flows should use attachment disposition",
        None,
        Some("attachment"),
        None,
      ),
    )
  }
  append_modern_recommendations(recommendations, disposition)
}

///|
fn append_multipart_recommendations(
  recommendations : Array[Recommendation],
  disposition : Disposition,
) -> Unit {
  if !disposition.is_form_data() {
    recommendations.push(
      Recommendation::make(
        PreserveExplicitDisposition,
        Error,
        "Multipart parsers expect form-data disposition",
        None,
        Some("form-data"),
        None,
      ),
    )
  }
  if !disposition.has_param("name") {
    recommendations.push(
      Recommendation::make(
        AddMultipartName,
        Error,
        "Multipart form-data parts require a name parameter",
        Some("name"),
        None,
        None,
      ),
    )
  }
}

///|
fn append_object_storage_recommendations(
  recommendations : Array[Recommendation],
  disposition : Disposition,
) -> Unit {
  match disposition.filename() {
    Some(value) => {
      let report = sanitize_object_key(value)
      if report.changed {
        recommendations.push(
          Recommendation::make(
            SanitizeFilename,
            Warning,
            "Object storage keys should be normalized before persistence",
            Some("filename"),
            Some(report.value),
            None,
          ),
        )
      }
    }
    None => ()
  }
  if disposition.diagnostics.any(d => d.severity == Error) {
    recommendations.push(
      Recommendation::make(
        FixSyntax,
        Error,
        "Header has syntax or decoding errors",
        None,
        None,
        None,
      ),
    )
  }
}