///|
/// Final decision for a batch of RFC 9457 documents.
pub(all) enum BatchDecision {
  Accept
  Review
  Reject
} derive(Eq, Debug)

///|
/// Machine-readable categories emitted while analyzing a batch.
pub(all) enum BatchFindingKind {
  DocumentLimitExceeded
  ParseFailure
  ParseDiagnostic
  SemanticViolation
  AuditWarning
  RelativeProblemType
  UnregisteredProblemType
  MissingRequiredTitle
  MissingRequiredStatus
} derive(Eq, Debug)

///|
/// One finding tied to a document index. Batch-wide findings use index -1.
pub struct BatchFinding {
  document_index : Int
  kind : BatchFindingKind
  context : String
}

///|
/// Frequency of one effective problem type URI.
pub struct ProblemTypeFrequency {
  type_uri : String
  count : Int
}

///|
/// Frequency of one declared HTTP status. None represents a missing status.
pub struct ProblemStatusFrequency {
  status : Int?
  count : Int
}

///|
/// Frequency of one extension member name across parsed documents.
pub struct ExtensionFrequency {
  name : String
  count : Int
}

///|
/// Quality gate budgets for batch analysis.
pub struct ProblemBatchPolicy {
  max_documents : Int
  max_parse_failures : Int
  max_semantic_issues : Int
  max_audit_issues : Int
  max_unregistered_types : Int
  require_title : Bool
  require_status : Bool
  allow_relative_types : Bool
}

///|
/// Aggregate, deterministic evidence for a collection of problem documents.
pub struct ProblemBatchReport {
  input_count : Int
  parsed_count : Int
  parse_failure_count : Int
  diagnostic_count : Int
  semantic_issue_count : Int
  audit_issue_count : Int
  about_blank_count : Int
  registered_type_count : Int
  unregistered_type_count : Int
  relative_type_count : Int
  missing_title_count : Int
  missing_status_count : Int
  total_member_count : Int
  max_member_count : Int
  informational_status_count : Int
  successful_status_count : Int
  redirection_status_count : Int
  client_error_status_count : Int
  server_error_status_count : Int
  other_status_count : Int
  type_frequencies : Array[ProblemTypeFrequency]
  status_frequencies : Array[ProblemStatusFrequency]
  extension_frequencies : Array[ExtensionFrequency]
  findings : Array[BatchFinding]
  decision : BatchDecision
  quality_score : Int
}

///|
pub fn BatchDecision::name(self : BatchDecision) -> String {
  match self {
    Accept => "accept"
    Review => "review"
    Reject => "reject"
  }
}

///|
pub fn BatchFindingKind::name(self : BatchFindingKind) -> String {
  match self {
    DocumentLimitExceeded => "DocumentLimitExceeded"
    ParseFailure => "ParseFailure"
    ParseDiagnostic => "ParseDiagnostic"
    SemanticViolation => "SemanticViolation"
    AuditWarning => "AuditWarning"
    RelativeProblemType => "RelativeProblemType"
    UnregisteredProblemType => "UnregisteredProblemType"
    MissingRequiredTitle => "MissingRequiredTitle"
    MissingRequiredStatus => "MissingRequiredStatus"
  }
}

///|
pub fn BatchFinding::document_index(self : BatchFinding) -> Int {
  self.document_index
}

///|
pub fn BatchFinding::kind(self : BatchFinding) -> BatchFindingKind {
  self.kind
}

///|
pub fn BatchFinding::context(self : BatchFinding) -> String {
  self.context
}

///|
pub fn ProblemTypeFrequency::type_uri(self : ProblemTypeFrequency) -> String {
  self.type_uri
}

///|
pub fn ProblemTypeFrequency::count(self : ProblemTypeFrequency) -> Int {
  self.count
}

///|
pub fn ProblemStatusFrequency::status(self : ProblemStatusFrequency) -> Int? {
  self.status
}

///|
pub fn ProblemStatusFrequency::count(self : ProblemStatusFrequency) -> Int {
  self.count
}

///|
pub fn ExtensionFrequency::name(self : ExtensionFrequency) -> String {
  self.name
}

///|
pub fn ExtensionFrequency::count(self : ExtensionFrequency) -> Int {
  self.count
}

///|
/// Practical defaults: malformed or semantically invalid documents reject;
/// advisory findings remain reviewable within a bounded budget.
pub fn ProblemBatchPolicy::default() -> ProblemBatchPolicy {
  {
    max_documents: 10_000,
    max_parse_failures: 0,
    max_semantic_issues: 0,
    max_audit_issues: 20,
    max_unregistered_types: 100,
    require_title: false,
    require_status: false,
    allow_relative_types: true,
  }
}

///|
/// Strict CI policy for interoperable, registry-backed API error documents.
pub fn ProblemBatchPolicy::strict() -> ProblemBatchPolicy {
  {
    max_documents: 1_000,
    max_parse_failures: 0,
    max_semantic_issues: 0,
    max_audit_issues: 0,
    max_unregistered_types: 0,
    require_title: true,
    require_status: true,
    allow_relative_types: false,
  }
}

///|
/// Permissive inspection policy that records findings without rejecting them.
pub fn ProblemBatchPolicy::permissive() -> ProblemBatchPolicy {
  {
    max_documents: 1_000_000,
    max_parse_failures: 1_000_000,
    max_semantic_issues: 1_000_000,
    max_audit_issues: 1_000_000,
    max_unregistered_types: 1_000_000,
    require_title: false,
    require_status: false,
    allow_relative_types: true,
  }
}

///|
pub fn ProblemBatchPolicy::with_max_documents(
  self : ProblemBatchPolicy,
  value : Int,
) -> ProblemBatchPolicy {
  { ..self, max_documents: non_negative(value) }
}

///|
pub fn ProblemBatchPolicy::with_max_parse_failures(
  self : ProblemBatchPolicy,
  value : Int,
) -> ProblemBatchPolicy {
  { ..self, max_parse_failures: non_negative(value) }
}

///|
pub fn ProblemBatchPolicy::with_max_semantic_issues(
  self : ProblemBatchPolicy,
  value : Int,
) -> ProblemBatchPolicy {
  { ..self, max_semantic_issues: non_negative(value) }
}

///|
pub fn ProblemBatchPolicy::with_max_audit_issues(
  self : ProblemBatchPolicy,
  value : Int,
) -> ProblemBatchPolicy {
  { ..self, max_audit_issues: non_negative(value) }
}

///|
pub fn ProblemBatchPolicy::with_max_unregistered_types(
  self : ProblemBatchPolicy,
  value : Int,
) -> ProblemBatchPolicy {
  { ..self, max_unregistered_types: non_negative(value) }
}

///|
pub fn ProblemBatchPolicy::with_require_title(
  self : ProblemBatchPolicy,
  value : Bool,
) -> ProblemBatchPolicy {
  { ..self, require_title: value }
}

///|
pub fn ProblemBatchPolicy::with_require_status(
  self : ProblemBatchPolicy,
  value : Bool,
) -> ProblemBatchPolicy {
  { ..self, require_status: value }
}

///|
pub fn ProblemBatchPolicy::with_allow_relative_types(
  self : ProblemBatchPolicy,
  value : Bool,
) -> ProblemBatchPolicy {
  { ..self, allow_relative_types: value }
}

///|
pub fn ProblemBatchPolicy::max_documents(self : ProblemBatchPolicy) -> Int {
  self.max_documents
}

///|
pub fn ProblemBatchPolicy::max_parse_failures(self : ProblemBatchPolicy) -> Int {
  self.max_parse_failures
}

///|
pub fn ProblemBatchPolicy::max_semantic_issues(
  self : ProblemBatchPolicy,
) -> Int {
  self.max_semantic_issues
}

///|
pub fn ProblemBatchPolicy::max_audit_issues(self : ProblemBatchPolicy) -> Int {
  self.max_audit_issues
}

///|
pub fn ProblemBatchPolicy::max_unregistered_types(
  self : ProblemBatchPolicy,
) -> Int {
  self.max_unregistered_types
}

///|
pub fn ProblemBatchPolicy::requires_title(self : ProblemBatchPolicy) -> Bool {
  self.require_title
}

///|
pub fn ProblemBatchPolicy::requires_status(self : ProblemBatchPolicy) -> Bool {
  self.require_status
}

///|
pub fn ProblemBatchPolicy::allows_relative_types(
  self : ProblemBatchPolicy,
) -> Bool {
  self.allow_relative_types
}

///|
pub fn ProblemBatchReport::input_count(self : ProblemBatchReport) -> Int {
  self.input_count
}

///|
pub fn ProblemBatchReport::parsed_count(self : ProblemBatchReport) -> Int {
  self.parsed_count
}

///|
pub fn ProblemBatchReport::parse_failure_count(
  self : ProblemBatchReport,
) -> Int {
  self.parse_failure_count
}

///|
pub fn ProblemBatchReport::diagnostic_count(self : ProblemBatchReport) -> Int {
  self.diagnostic_count
}

///|
pub fn ProblemBatchReport::semantic_issue_count(
  self : ProblemBatchReport,
) -> Int {
  self.semantic_issue_count
}

///|
pub fn ProblemBatchReport::audit_issue_count(self : ProblemBatchReport) -> Int {
  self.audit_issue_count
}

///|
pub fn ProblemBatchReport::about_blank_count(self : ProblemBatchReport) -> Int {
  self.about_blank_count
}

///|
pub fn ProblemBatchReport::registered_type_count(
  self : ProblemBatchReport,
) -> Int {
  self.registered_type_count
}

///|
pub fn ProblemBatchReport::unregistered_type_count(
  self : ProblemBatchReport,
) -> Int {
  self.unregistered_type_count
}

///|
pub fn ProblemBatchReport::relative_type_count(
  self : ProblemBatchReport,
) -> Int {
  self.relative_type_count
}

///|
pub fn ProblemBatchReport::missing_title_count(
  self : ProblemBatchReport,
) -> Int {
  self.missing_title_count
}

///|
pub fn ProblemBatchReport::missing_status_count(
  self : ProblemBatchReport,
) -> Int {
  self.missing_status_count
}

///|
pub fn ProblemBatchReport::total_member_count(self : ProblemBatchReport) -> Int {
  self.total_member_count
}

///|
pub fn ProblemBatchReport::max_member_count(self : ProblemBatchReport) -> Int {
  self.max_member_count
}

///|
pub fn ProblemBatchReport::average_member_count(
  self : ProblemBatchReport,
) -> Double {
  if self.parsed_count == 0 {
    0.0
  } else {
    self.total_member_count.to_double() / self.parsed_count.to_double()
  }
}

///|
pub fn ProblemBatchReport::informational_status_count(
  self : ProblemBatchReport,
) -> Int {
  self.informational_status_count
}

///|
pub fn ProblemBatchReport::successful_status_count(
  self : ProblemBatchReport,
) -> Int {
  self.successful_status_count
}

///|
pub fn ProblemBatchReport::redirection_status_count(
  self : ProblemBatchReport,
) -> Int {
  self.redirection_status_count
}

///|
pub fn ProblemBatchReport::client_error_status_count(
  self : ProblemBatchReport,
) -> Int {
  self.client_error_status_count
}

///|
pub fn ProblemBatchReport::server_error_status_count(
  self : ProblemBatchReport,
) -> Int {
  self.server_error_status_count
}

///|
pub fn ProblemBatchReport::other_status_count(self : ProblemBatchReport) -> Int {
  self.other_status_count
}

///|
pub fn ProblemBatchReport::type_frequencies(
  self : ProblemBatchReport,
) -> Array[ProblemTypeFrequency] {
  self.type_frequencies.copy()
}

///|
pub fn ProblemBatchReport::status_frequencies(
  self : ProblemBatchReport,
) -> Array[ProblemStatusFrequency] {
  self.status_frequencies.copy()
}

///|
pub fn ProblemBatchReport::extension_frequencies(
  self : ProblemBatchReport,
) -> Array[ExtensionFrequency] {
  self.extension_frequencies.copy()
}

///|
pub fn ProblemBatchReport::findings(
  self : ProblemBatchReport,
) -> Array[BatchFinding] {
  self.findings.copy()
}

///|
pub fn ProblemBatchReport::decision(self : ProblemBatchReport) -> BatchDecision {
  self.decision
}

///|
pub fn ProblemBatchReport::quality_score(self : ProblemBatchReport) -> Int {
  self.quality_score
}

///|
pub fn ProblemBatchReport::is_accepted(self : ProblemBatchReport) -> Bool {
  self.decision == Accept
}

///|
/// Analyze documents independently, preserve deterministic input order, and
/// return aggregate evidence without retaining the original JSON strings.
pub fn analyze_problem_batch(
  inputs : Array[String],
  policy? : ProblemBatchPolicy = ProblemBatchPolicy::default(),
  limits? : Limits = Limits::default(),
) -> ProblemBatchReport {
  let findings : Array[BatchFinding] = []
  let type_frequencies : Array[ProblemTypeFrequency] = []
  let status_frequencies : Array[ProblemStatusFrequency] = []
  let extension_frequencies : Array[ExtensionFrequency] = []
  let mut parsed_count = 0
  let mut parse_failure_count = 0
  let mut diagnostic_count = 0
  let mut semantic_issue_count = 0
  let mut audit_issue_count = 0
  let mut about_blank_count = 0
  let mut registered_type_count = 0
  let mut unregistered_type_count = 0
  let mut relative_type_count = 0
  let mut missing_title_count = 0
  let mut missing_status_count = 0
  let mut total_member_count = 0
  let mut max_member_count = 0
  let mut informational_status_count = 0
  let mut successful_status_count = 0
  let mut redirection_status_count = 0
  let mut client_error_status_count = 0
  let mut server_error_status_count = 0
  let mut other_status_count = 0
  if inputs.length() > policy.max_documents {
    findings.push({
      document_index: -1,
      kind: DocumentLimitExceeded,
      context: "batch contains \{inputs.length()} documents; policy allows \{policy.max_documents}",
    })
  }
  for index = 0; index < inputs.length(); index = index + 1 {
    match parse_problem_json_with_limits(inputs[index], limits) {
      Err(error) => {
        parse_failure_count += 1
        findings.push({
          document_index: index,
          kind: ParseFailure,
          context: error.to_string(),
        })
      }
      Ok(result) => {
        parsed_count += 1
        let diagnostics = result.diagnostics()
        diagnostic_count += diagnostics.length()
        for diagnostic in diagnostics {
          findings.push({
            document_index: index,
            kind: ParseDiagnostic,
            context: "member \{diagnostic.member_name()} expected \{diagnostic.expected()} but received \{diagnostic.actual()}",
          })
        }
        let problem = result.problem()
        let members = problem.member_count()
        total_member_count += members
        if members > max_member_count {
          max_member_count = members
        }
        let effective_type = problem.effective_type()
        increment_type_frequency(type_frequencies, effective_type)
        if problem.is_about_blank() {
          about_blank_count += 1
        }
        match lookup_problem_type(effective_type) {
          Some(_) => registered_type_count += 1
          None =>
            match validate_uri_reference(effective_type) {
              Ok(info) if info.is_relative() => {
                relative_type_count += 1
                findings.push({
                  document_index: index,
                  kind: RelativeProblemType,
                  context: "problem type is a relative URI-reference: \{effective_type}",
                })
              }
              _ => {
                unregistered_type_count += 1
                findings.push({
                  document_index: index,
                  kind: UnregisteredProblemType,
                  context: "problem type is absent from the offline IANA registry: \{effective_type}",
                })
              }
            }
        }
        match problem.title() {
          None => {
            missing_title_count += 1
            if policy.require_title {
              findings.push({
                document_index: index,
                kind: MissingRequiredTitle,
                context: "policy requires a title",
              })
            }
          }
          Some(_) => ()
        }
        match problem.status() {
          None => {
            missing_status_count += 1
            other_status_count += 1
            increment_status_frequency(status_frequencies, None)
            if policy.require_status {
              findings.push({
                document_index: index,
                kind: MissingRequiredStatus,
                context: "policy requires a status",
              })
            }
          }
          Some(status) => {
            increment_status_frequency(status_frequencies, Some(status))
            if status >= 100 && status <= 199 {
              informational_status_count += 1
            } else if status <= 299 {
              successful_status_count += 1
            } else if status <= 399 {
              redirection_status_count += 1
            } else if status <= 499 {
              client_error_status_count += 1
            } else if status <= 599 {
              server_error_status_count += 1
            } else {
              other_status_count += 1
            }
          }
        }
        for extension in problem.extensions() {
          increment_extension_frequency(extension_frequencies, extension.name())
        }
        let semantic = validate_problem(problem)
        semantic_issue_count += semantic.length()
        for issue in semantic {
          findings.push({
            document_index: index,
            kind: SemanticViolation,
            context: "\{issue.kind().name()}: \{issue.context()}",
          })
        }
        let audit = audit_problem(problem)
        audit_issue_count += audit.issues().length()
        for issue in audit.issues() {
          findings.push({
            document_index: index,
            kind: AuditWarning,
            context: "\{issue.kind().name()}: \{issue.context()}",
          })
        }
      }
    }
  }
  let rejected = inputs.length() > policy.max_documents ||
    parse_failure_count > policy.max_parse_failures ||
    semantic_issue_count > policy.max_semantic_issues ||
    audit_issue_count > policy.max_audit_issues ||
    unregistered_type_count > policy.max_unregistered_types ||
    (!policy.allow_relative_types && relative_type_count > 0) ||
    (policy.require_title && missing_title_count > 0) ||
    (policy.require_status && missing_status_count > 0)
  let has_review_signal = parse_failure_count > 0 ||
    diagnostic_count > 0 ||
    semantic_issue_count > 0 ||
    audit_issue_count > 0 ||
    unregistered_type_count > 0 ||
    relative_type_count > 0 ||
    missing_title_count > 0 ||
    missing_status_count > 0
  let decision = if rejected {
    Reject
  } else if has_review_signal {
    Review
  } else {
    Accept
  }
  let quality_score = batch_quality_score(
    parse_failure_count, diagnostic_count, semantic_issue_count, audit_issue_count,
    unregistered_type_count, relative_type_count,
  )
  {
    input_count: inputs.length(),
    parsed_count,
    parse_failure_count,
    diagnostic_count,
    semantic_issue_count,
    audit_issue_count,
    about_blank_count,
    registered_type_count,
    unregistered_type_count,
    relative_type_count,
    missing_title_count,
    missing_status_count,
    total_member_count,
    max_member_count,
    informational_status_count,
    successful_status_count,
    redirection_status_count,
    client_error_status_count,
    server_error_status_count,
    other_status_count,
    type_frequencies,
    status_frequencies,
    extension_frequencies,
    findings,
    decision,
    quality_score,
  }
}

///|
/// Compact deterministic JSON suitable for CI logs.
pub fn ProblemBatchReport::summary_json(self : ProblemBatchReport) -> String {
  "{" +
  "\"decision\":" +
  json_quote(self.decision.name()) +
  "," +
  "\"quality_score\":\{self.quality_score}," +
  "\"input_count\":\{self.input_count}," +
  "\"parsed_count\":\{self.parsed_count}," +
  "\"parse_failures\":\{self.parse_failure_count}," +
  "\"diagnostics\":\{self.diagnostic_count}," +
  "\"semantic_issues\":\{self.semantic_issue_count}," +
  "\"audit_issues\":\{self.audit_issue_count}," +
  "\"registered_types\":\{self.registered_type_count}," +
  "\"unregistered_types\":\{self.unregistered_type_count}," +
  "\"relative_types\":\{self.relative_type_count}," +
  "\"findings\":\{self.findings.length()}" +
  "}"
}

///|
fn increment_type_frequency(
  frequencies : Array[ProblemTypeFrequency],
  type_uri : String,
) -> Unit {
  for i = 0; i < frequencies.length(); i = i + 1 {
    if frequencies[i].type_uri == type_uri {
      frequencies[i] = { type_uri, count: frequencies[i].count + 1 }
      return
    }
  }
  frequencies.push({ type_uri, count: 1 })
}

///|
fn increment_status_frequency(
  frequencies : Array[ProblemStatusFrequency],
  status : Int?,
) -> Unit {
  for i = 0; i < frequencies.length(); i = i + 1 {
    if frequencies[i].status == status {
      frequencies[i] = { status, count: frequencies[i].count + 1 }
      return
    }
  }
  frequencies.push({ status, count: 1 })
}

///|
fn increment_extension_frequency(
  frequencies : Array[ExtensionFrequency],
  name : String,
) -> Unit {
  for i = 0; i < frequencies.length(); i = i + 1 {
    if frequencies[i].name == name {
      frequencies[i] = { name, count: frequencies[i].count + 1 }
      return
    }
  }
  frequencies.push({ name, count: 1 })
}

///|
fn batch_quality_score(
  parse_failures : Int,
  diagnostics : Int,
  semantic_issues : Int,
  audit_issues : Int,
  unregistered_types : Int,
  relative_types : Int,
) -> Int {
  let score = 100 -
    parse_failures * 25 -
    diagnostics * 3 -
    semantic_issues * 15 -
    audit_issues * 2 -
    unregistered_types * 3 -
    relative_types * 2
  if score < 0 {
    0
  } else {
    score
  }
}

///|
fn non_negative(value : Int) -> Int {
  if value < 0 {
    0
  } else {
    value
  }
}