///|
pub(all) struct HeaderRepair {
  input : String
  output : String
  changed : Bool
  review : HeaderReview
  disposition : Disposition
  filename : FilenameReport?
} derive(Eq, Debug)

///|
pub fn repair_header(header : String) -> HeaderRepair {
  repair_header_with_policy(header, FilenamePolicy::default())
}

///|
pub fn repair_header_with_policy(
  header : String,
  policy : FilenamePolicy,
) -> HeaderRepair {
  let review = lint_header_with_policy(header, policy)
  match parse_header(header) {
    Ok(disposition) => {
      let normalized = normalize_disposition_with_policy(disposition, policy)
      let output = normalized.disposition.to_header()
      {
        input: header,
        output,
        changed: header != output || normalized.changed,
        review,
        disposition: normalized.disposition,
        filename: normalized.filename,
      }
    }
    Err(_) => {
      let report = sanitize_filename_with_policy(policy.default_name, policy)
      let fallback = DispositionBuilder::attachment()
        .with_filename(report.value)
        .build()
      {
        input: header,
        output: fallback.to_header(),
        changed: true,
        review,
        disposition: fallback,
        filename: Some(report),
      }
    }
  }
}

///|
pub fn canonicalize_header(header : String) -> Result[String, ParseError] {
  match parse_header_strict(header) {
    Ok(disposition) => Ok(disposition.to_header())
    Err(err) => Err(err)
  }
}

///|
pub fn canonicalize_header_loose(header : String) -> Result[String, ParseError] {
  match parse_header(header) {
    Ok(disposition) => Ok(disposition.to_header())
    Err(err) => Err(err)
  }
}

///|
pub fn HeaderRepair::is_lossless(self : HeaderRepair) -> Bool {
  !self.changed && self.review.status == Accepted
}

///|
pub fn HeaderRepair::has_filename_change(self : HeaderRepair) -> Bool {
  match self.filename {
    Some(report) => report.changed
    None => false
  }
}

///|
pub fn HeaderRepair::to_text(self : HeaderRepair) -> String {
  let buf = StringBuilder(
    size_hint=self.input.length() + self.output.length() + 96,
  )
  buf.write_string("input: ")
  buf.write_string(self.input)
  buf.write_string("\noutput: ")
  buf.write_string(self.output)
  buf.write_string("\nchanged: ")
  if self.changed {
    buf.write_string("true")
  } else {
    buf.write_string("false")
  }
  buf.write_string("\nstatus: ")
  buf.write_string(self.review.status.name())
  match self.filename {
    Some(report) => {
      buf.write_string("\nfilename: ")
      buf.write_string(report.value)
    }
    None => ()
  }
  buf.to_string()
}

///|
pub fn repair_headers(headers : Array[String]) -> Array[HeaderRepair] {
  let repairs : Array[HeaderRepair] = []
  for header in headers {
    repairs.push(repair_header(header))
  }
  repairs
}

///|
pub fn repairs_to_headers(repairs : Array[HeaderRepair]) -> Array[String] {
  let headers : Array[String] = []
  for repair in repairs {
    headers.push(repair.output)
  }
  headers
}

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

///|
struct NormalizedDisposition {
  disposition : Disposition
  filename : FilenameReport?
  changed : Bool
} derive(Eq, Debug)

///|
fn normalize_disposition_with_policy(
  disposition : Disposition,
  policy : FilenamePolicy,
) -> NormalizedDisposition {
  let params : Array[Parameter] = []
  let diagnostics = clone_diagnostics(disposition.diagnostics)
  let mut filename_report : FilenameReport? = None
  let mut saw_filename = false
  let mut changed = false
  for p in disposition.params {
    if p.is_filename() {
      if !saw_filename {
        let report = sanitize_filename_with_policy(p.value, policy)
        filename_report = Some(report)
        params.push(Parameter::plain("filename", report.value))
        if needs_extended_filename(report.value) {
          params.push(
            Parameter::extended(
              "filename",
              report.value,
              charset="UTF-8",
              language="",
            ),
          )
        }
        if report.changed || p.effective_name() != "filename" {
          changed = true
        }
        saw_filename = true
      } else {
        changed = true
      }
    } else if !has_equivalent_parameter(params, p) {
      params.push(p)
    } else {
      changed = true
    }
  }
  let normalized : Disposition = { kind: disposition.kind, params, diagnostics }
  { disposition: normalized, filename: filename_report, changed }
}

///|
fn has_equivalent_parameter(
  params : Array[Parameter],
  candidate : Parameter,
) -> Bool {
  for p in params {
    if p.name == candidate.name && p.extended == candidate.extended {
      return true
    }
  }
  false
}