///|
pub(all) struct BatchInput {
  source : String
  text : String
} derive(Eq, Debug, ToJson)

///|
pub(all) struct BatchItem {
  source : String
  parsed : Bool
  finding_count : Int
  risk_score : Int
  risk_band : String
  error : String
} derive(Eq, Debug, ToJson)

///|
pub(all) struct BatchSummary {
  total_models : Int
  parsed_models : Int
  failed_models : Int
  finding_count : Int
  high_finding_count : Int
  medium_finding_count : Int
  low_finding_count : Int
  average_risk_score : Int
  maximum_risk_score : Int
} derive(Eq, Debug, ToJson)

///|
pub(all) struct BatchReport {
  items : Array[BatchItem]
  summary : BatchSummary
} derive(Eq, Debug, ToJson)

///|
pub fn analyze_batch(inputs : Array[BatchInput]) -> BatchReport {
  let items : Array[BatchItem] = []
  let mut parsed_models = 0
  let mut failed_models = 0
  let mut finding_count = 0
  let mut high_count = 0
  let mut medium_count = 0
  let mut low_count = 0
  let mut total_risk = 0
  let mut maximum_risk = 0
  for input in inputs {
    match parse_model(input.text) {
      Ok(model) => {
        parsed_models += 1
        let findings = analyze(model)
        let assessment = assess_model(model)
        finding_count += findings.length()
        total_risk += assessment.risk_score
        if assessment.risk_score > maximum_risk {
          maximum_risk = assessment.risk_score
        }
        for finding in findings {
          match finding.severity {
            "high" => high_count += 1
            "medium" => medium_count += 1
            _ => low_count += 1
          }
        }
        items.push({
          source: input.source,
          parsed: true,
          finding_count: findings.length(),
          risk_score: assessment.risk_score,
          risk_band: risk_band_name(assessment.risk_band),
          error: "",
        })
      }
      Err(error) => {
        failed_models += 1
        items.push({
          source: input.source,
          parsed: false,
          finding_count: 0,
          risk_score: 100,
          risk_band: "critical",
          error: format_error(error),
        })
      }
    }
  }
  let average_risk_score = if parsed_models == 0 {
    0
  } else {
    total_risk / parsed_models
  }
  {
    items,
    summary: {
      total_models: inputs.length(),
      parsed_models,
      failed_models,
      finding_count,
      high_finding_count: high_count,
      medium_finding_count: medium_count,
      low_finding_count: low_count,
      average_risk_score,
      maximum_risk_score: maximum_risk,
    },
  }
}

///|
pub fn batch_report_json(report : BatchReport) -> String {
  report.to_json().stringify(indent=2)
}

///|
pub fn format_batch_report(report : BatchReport) -> String {
  let out = StringBuilder()
  out.write_string("batch models=\{report.summary.total_models}")
  out.write_string(" parsed=\{report.summary.parsed_models}")
  out.write_string(" failed=\{report.summary.failed_models}")
  out.write_string(" findings=\{report.summary.finding_count}")
  out.write_string(" average_risk=\{report.summary.average_risk_score}")
  out.write_string(" max_risk=\{report.summary.maximum_risk_score}")
  for item in report.items {
    out.write_string("\n\{item.source}: ")
    if item.parsed {
      out.write_string(
        "parsed findings=\{item.finding_count} risk=\{item.risk_band}(\{item.risk_score})",
      )
    } else {
      out.write_string("parse_error=\{item.error}")
    }
  }
  out.to_string()
}

///|
pub fn batch_has_failures(report : BatchReport) -> Bool {
  report.summary.failed_models > 0
}

///|
pub fn batch_is_clean(report : BatchReport) -> Bool {
  report.summary.failed_models == 0 && report.summary.finding_count == 0
}

///|
pub fn batch_sources(report : BatchReport) -> Array[String] {
  let sources : Array[String] = []
  for item in report.items {
    sources.push(item.source)
  }
  sources
}

///|
pub fn batch_item_for(report : BatchReport, source : String) -> BatchItem? {
  for item in report.items {
    if item.source == source {
      return Some(item)
    }
  }
  None
}

///|
pub fn merge_batch_reports(
  left : BatchReport,
  right : BatchReport,
) -> BatchReport {
  let inputs : Array[BatchInput] = []
  for item in left.items {
    inputs.push({ source: item.source, text: "" })
  }
  for item in right.items {
    inputs.push({ source: item.source, text: "" })
  }
  ignore(inputs)
  let items : Array[BatchItem] = []
  for item in left.items {
    items.push(item)
  }
  for item in right.items {
    items.push(item)
  }
  { items, summary: merge_batch_summaries(left.summary, right.summary) }
}

///|
fn merge_batch_summaries(
  left : BatchSummary,
  right : BatchSummary,
) -> BatchSummary {
  let parsed = left.parsed_models + right.parsed_models
  {
    total_models: left.total_models + right.total_models,
    parsed_models: parsed,
    failed_models: left.failed_models + right.failed_models,
    finding_count: left.finding_count + right.finding_count,
    high_finding_count: left.high_finding_count + right.high_finding_count,
    medium_finding_count: left.medium_finding_count + right.medium_finding_count,
    low_finding_count: left.low_finding_count + right.low_finding_count,
    average_risk_score: if parsed == 0 {
      0
    } else {
      (
        left.average_risk_score * left.parsed_models +
        right.average_risk_score * right.parsed_models
      ) /
      parsed
    },
    maximum_risk_score: if left.maximum_risk_score > right.maximum_risk_score {
      left.maximum_risk_score
    } else {
      right.maximum_risk_score
    },
  }
}