///|
pub fn parse_mtasts(text : String) -> MtastsRecord {
  let raw = text.trim().to_owned()
  let version = colon_value(raw, "version")
  let mode = lower(colon_value(raw, "mode"))
  let max_age_text = colon_value(raw, "max_age")
  let id = colon_value(raw, "id")
  let mx_hosts = colon_values(raw, "mx")
  let mut max_age = 0
  let mut max_age_valid = true
  if max_age_text.is_empty() {
    max_age_valid = false
  } else {
    match parse_digits(max_age_text) {
      Some(value) => max_age = value
      None => max_age_valid = false
    }
  }
  let valid_version = lower(version) == "stsv1"
  let findings = mtasts_findings(
    raw, valid_version, version, mode, max_age, max_age_valid, mx_hosts,
  )
  {
    raw,
    valid_version,
    version,
    mode,
    max_age,
    id,
    mx_count: mx_hosts.length(),
    mx_hosts,
    findings,
  }
}

///|
fn mtasts_findings(
  raw : String,
  valid_version : Bool,
  version : String,
  mode : String,
  max_age : Int,
  max_age_valid : Bool,
  mx_hosts : Array[String],
) -> Array[PolicyFinding] {
  let findings = Array::new(capacity=12)
  if raw.is_empty() {
    findings.push(
      warn(
        "mtasts.policy-missing", "MTA-STS policy text is missing", "Without an MTA-STS policy file, receivers cannot enforce strict TLS delivery from this data.",
        "", "Publish a policy file with version, mode, mx and max_age.",
      ),
    )
    return findings
  }
  if valid_version {
    findings.push(
      pass("mtasts.version", "MTA-STS policy version is valid", version),
    )
  } else {
    findings.push(
      fail(
        "mtasts.version-invalid", "MTA-STS policy version is invalid", "The policy file must start with version: STSv1.",
        version, "Set version: STSv1.",
      ),
    )
  }
  if mode == "enforce" {
    findings.push(
      pass("mtasts.mode", "MTA-STS is enforcing TLS policy", "enforce"),
    )
  } else if mode == "testing" {
    findings.push(
      warn(
        "mtasts.mode-testing", "MTA-STS is in testing mode", "Testing mode reports problems but does not request strict enforcement.",
        "testing", "Move to mode: enforce after validating reports.",
      ),
    )
  } else if mode == "none" {
    findings.push(
      warn(
        "mtasts.mode-none", "MTA-STS policy is disabled", "mode: none disables strict TLS policy.",
        "none", "Use mode: testing or mode: enforce.",
      ),
    )
  } else if mode.is_empty() {
    findings.push(
      fail(
        "mtasts.mode-missing", "MTA-STS mode is missing", "The policy needs mode: enforce, testing or none.",
        "", "Add mode: testing during rollout or mode: enforce in production.",
      ),
    )
  } else {
    findings.push(
      fail(
        "mtasts.mode-invalid", "MTA-STS mode is invalid", "Allowed modes are enforce, testing and none.",
        mode, "Use a valid MTA-STS mode.",
      ),
    )
  }
  if !max_age_valid {
    findings.push(
      fail(
        "mtasts.max-age-invalid", "MTA-STS max_age is missing or invalid", "max_age must be a non-negative integer.",
        "", "Add max_age: 86400 or higher.",
      ),
    )
  } else if max_age < 86400 {
    findings.push(
      warn(
        "mtasts.max-age-low",
        "MTA-STS max_age is short",
        "Very short policies reduce protection and increase fetch pressure.",
        max_age.to_string(),
        "Use at least 86400 seconds after rollout.",
      ),
    )
  } else if max_age > 31557600 {
    findings.push(
      warn(
        "mtasts.max-age-high",
        "MTA-STS max_age is very long",
        "Long cache windows make emergency rollback harder.",
        max_age.to_string(),
        "Keep max_age within the operational rollback window.",
        penalty=3,
      ),
    )
  } else {
    findings.push(
      pass(
        "mtasts.max-age",
        "MTA-STS max_age is in a practical range",
        max_age.to_string(),
      ),
    )
  }
  if mx_hosts.length() == 0 {
    findings.push(
      fail(
        "mtasts.mx-missing", "MTA-STS MX hosts are missing", "The policy must list at least one mx pattern.",
        "", "Add mx: mail.example.com or another legitimate inbound MX host.",
      ),
    )
  } else {
    findings.push(
      pass(
        "mtasts.mx",
        "MTA-STS policy lists MX hosts",
        mx_hosts.length().to_string(),
      ),
    )
    for host in mx_hosts {
      if has_wildcard_host(host) {
        findings.push(
          warn(
            "mtasts.mx-wildcard",
            "MTA-STS MX pattern uses a wildcard",
            "Wildcards are convenient but can hide accidental host coverage.",
            host,
            "Prefer exact MX hosts when the inbound estate is known.",
            penalty=3,
          ),
        )
      } else if host.trim().is_empty() {
        findings.push(
          warn(
            "mtasts.mx-empty",
            "MTA-STS MX entry is empty",
            "Empty mx entries make policy review confusing.",
            "",
            "Remove empty mx lines.",
            penalty=2,
          ),
        )
      }
    }
  }
  findings
}

///|
pub fn MtastsRecord::is_configured(self : MtastsRecord) -> Bool {
  !self.raw.is_empty()
}

///|
pub fn MtastsRecord::is_enforcing(self : MtastsRecord) -> Bool {
  self.mode == "enforce"
}

///|
pub fn MtastsRecord::is_testing(self : MtastsRecord) -> Bool {
  self.mode == "testing"
}

///|
pub fn MtastsRecord::has_mx_hosts(self : MtastsRecord) -> Bool {
  self.mx_count > 0
}

///|
pub fn MtastsRecord::cache_window_label(self : MtastsRecord) -> String {
  if self.max_age <= 0 {
    "missing"
  } else if self.max_age < 86400 {
    "short"
  } else if self.max_age <= 604800 {
    "normal"
  } else if self.max_age <= 31557600 {
    "long"
  } else {
    "very-long"
  }
}