///|
/// Protocol families that can be checked by a deployment compatibility matrix.
pub(all) enum CompatibilityProtocol {
  CompatibilityClassicCan
  CompatibilityCanFd
  CompatibilityIsoTp
  CompatibilityUds
  CompatibilityCanOpen
  CompatibilityJ1939
  CompatibilityGateway
  CompatibilityCustom
}

///|
pub fn compatibility_protocol_variants() -> Array[CompatibilityProtocol] {
  [
    CompatibilityClassicCan,
    CompatibilityCanFd,
    CompatibilityIsoTp,
    CompatibilityUds,
    CompatibilityCanOpen,
    CompatibilityJ1939,
    CompatibilityGateway,
    CompatibilityCustom,
  ]
}

///|
pub fn compatibility_protocol_text(protocol : CompatibilityProtocol) -> String {
  match protocol {
    CompatibilityClassicCan => "classic-can"
    CompatibilityCanFd => "can-fd"
    CompatibilityIsoTp => "isotp"
    CompatibilityUds => "uds"
    CompatibilityCanOpen => "canopen"
    CompatibilityJ1939 => "j1939"
    CompatibilityGateway => "gateway"
    CompatibilityCustom => "custom"
  }
}

///|
/// The level attached to a requirement or finding.
pub(all) enum CompatibilitySeverity {
  CompatibilityInfo
  CompatibilityWarning
  CompatibilityError
}

///|
pub fn compatibility_severity_variants() -> Array[CompatibilitySeverity] {
  [CompatibilityInfo, CompatibilityWarning, CompatibilityError]
}

///|
pub fn compatibility_severity_text(severity : CompatibilitySeverity) -> String {
  match severity {
    CompatibilityInfo => "info"
    CompatibilityWarning => "warning"
    CompatibilityError => "error"
  }
}

///|
/// A machine-readable kind of capability requirement.
pub(all) enum CompatibilityRequirementKind {
  CompatibilityMinimumBitrate
  CompatibilityMinimumDataBitrate
  CompatibilityMinimumPayload
  CompatibilityMinimumIdentifierBits
  CompatibilityMinimumNodeCapacity
  CompatibilityProtocolIs
  CompatibilityRequiresFeature
  CompatibilityForbidsFeature
  CompatibilityRequiresFd
  CompatibilityRequiresClassic
  CompatibilityRequiresIsoTp
  CompatibilityRequiresDiagnostics
}

///|
pub fn compatibility_requirement_kind_variants() -> Array[
  CompatibilityRequirementKind,
] {
  [
    CompatibilityMinimumBitrate,
    CompatibilityMinimumDataBitrate,
    CompatibilityMinimumPayload,
    CompatibilityMinimumIdentifierBits,
    CompatibilityMinimumNodeCapacity,
    CompatibilityProtocolIs,
    CompatibilityRequiresFeature,
    CompatibilityForbidsFeature,
    CompatibilityRequiresFd,
    CompatibilityRequiresClassic,
    CompatibilityRequiresIsoTp,
    CompatibilityRequiresDiagnostics,
  ]
}

///|
pub fn compatibility_requirement_kind_text(
  kind : CompatibilityRequirementKind,
) -> String {
  match kind {
    CompatibilityMinimumBitrate => "minimum-bitrate"
    CompatibilityMinimumDataBitrate => "minimum-data-bitrate"
    CompatibilityMinimumPayload => "minimum-payload"
    CompatibilityMinimumIdentifierBits => "minimum-identifier-bits"
    CompatibilityMinimumNodeCapacity => "minimum-node-capacity"
    CompatibilityProtocolIs => "protocol-is"
    CompatibilityRequiresFeature => "requires-feature"
    CompatibilityForbidsFeature => "forbids-feature"
    CompatibilityRequiresFd => "requires-fd"
    CompatibilityRequiresClassic => "requires-classic"
    CompatibilityRequiresIsoTp => "requires-isotp"
    CompatibilityRequiresDiagnostics => "requires-diagnostics"
  }
}

///|
/// A deployment target and the capabilities it advertises.
pub struct CompatibilityTarget {
  name : String
  protocol : CompatibilityProtocol
  nominal_bitrate : Int
  data_bitrate : Int
  max_payload : Int
  identifier_bits : Int
  node_capacity : Int
  features : Array[String]
  notes : String
}

///|
pub fn compatibility_target(
  name : String,
  protocol : CompatibilityProtocol,
  nominal_bitrate : Int,
  data_bitrate : Int,
  max_payload : Int,
  identifier_bits : Int,
  node_capacity : Int,
  features? : Array[String] = [],
  notes? : String = "",
) -> CompatibilityTarget {
  {
    name,
    protocol,
    nominal_bitrate: if nominal_bitrate < 0 {
      0
    } else {
      nominal_bitrate
    },
    data_bitrate: if data_bitrate < 0 {
      0
    } else {
      data_bitrate
    },
    max_payload: if max_payload < 0 {
      0
    } else {
      max_payload
    },
    identifier_bits: if identifier_bits < 0 {
      0
    } else {
      identifier_bits
    },
    node_capacity: if node_capacity < 0 {
      0
    } else {
      node_capacity
    },
    features: features.copy(),
    notes,
  }
}

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

///|
pub fn CompatibilityTarget::protocol(
  self : CompatibilityTarget,
) -> CompatibilityProtocol {
  self.protocol
}

///|
pub fn CompatibilityTarget::nominal_bitrate(self : CompatibilityTarget) -> Int {
  self.nominal_bitrate
}

///|
pub fn CompatibilityTarget::data_bitrate(self : CompatibilityTarget) -> Int {
  self.data_bitrate
}

///|
pub fn CompatibilityTarget::max_payload(self : CompatibilityTarget) -> Int {
  self.max_payload
}

///|
pub fn CompatibilityTarget::identifier_bits(self : CompatibilityTarget) -> Int {
  self.identifier_bits
}

///|
pub fn CompatibilityTarget::node_capacity(self : CompatibilityTarget) -> Int {
  self.node_capacity
}

///|
pub fn CompatibilityTarget::features(
  self : CompatibilityTarget,
) -> Array[String] {
  self.features.copy()
}

///|
pub fn CompatibilityTarget::notes(self : CompatibilityTarget) -> String {
  self.notes
}

///|
pub fn CompatibilityTarget::has_feature(
  self : CompatibilityTarget,
  wanted : String,
) -> Bool {
  for feature in self.features {
    if feature == wanted {
      return true
    }
  }
  false
}

///|
pub fn CompatibilityTarget::feature_count(self : CompatibilityTarget) -> Int {
  self.features.length()
}

///|
pub fn CompatibilityTarget::feature_csv(self : CompatibilityTarget) -> String {
  let builder = StringBuilder()
  for index, feature in self.features {
    if index > 0 {
      builder.write_string(",")
    }
    builder.write_string(feature)
  }
  builder.to_string()
}

///|
pub fn CompatibilityTarget::summary(self : CompatibilityTarget) -> String {
  let builder = StringBuilder()
  builder.write_string(self.name)
  builder.write_string(" protocol=")
  builder.write_string(compatibility_protocol_text(self.protocol))
  builder.write_string(" nominal=")
  builder.write_string(self.nominal_bitrate.to_string())
  builder.write_string(" data=")
  builder.write_string(self.data_bitrate.to_string())
  builder.write_string(" payload=")
  builder.write_string(self.max_payload.to_string())
  builder.write_string(" idbits=")
  builder.write_string(self.identifier_bits.to_string())
  builder.write_string(" nodes=")
  builder.write_string(self.node_capacity.to_string())
  builder.to_string()
}

///|
/// A reusable requirement for a production target.
pub struct CompatibilityRequirement {
  code : String
  description : String
  kind : CompatibilityRequirementKind
  mut numeric_value : Int
  mut protocol_value : CompatibilityProtocol?
  mut feature_value : String?
  severity : CompatibilitySeverity
}

///|
fn compatibility_requirement_base(
  code : String,
  description : String,
  kind : CompatibilityRequirementKind,
  severity : CompatibilitySeverity,
) -> CompatibilityRequirement {
  {
    code,
    description,
    kind,
    numeric_value: 0,
    protocol_value: None,
    feature_value: None,
    severity,
  }
}

///|
pub fn compatibility_minimum_bitrate(
  code : String,
  minimum : Int,
  description? : String = "nominal bitrate requirement",
  severity? : CompatibilitySeverity = CompatibilityError,
) -> CompatibilityRequirement {
  let item = compatibility_requirement_base(
    code,
    description,
    CompatibilityMinimumBitrate,
    severity,
  )
  item.numeric_value = if minimum < 0 { 0 } else { minimum }
  item
}

///|
pub fn compatibility_minimum_data_bitrate(
  code : String,
  minimum : Int,
  description? : String = "data phase bitrate requirement",
  severity? : CompatibilitySeverity = CompatibilityError,
) -> CompatibilityRequirement {
  let item = compatibility_requirement_base(
    code,
    description,
    CompatibilityMinimumDataBitrate,
    severity,
  )
  item.numeric_value = if minimum < 0 { 0 } else { minimum }
  item
}

///|
pub fn compatibility_minimum_payload(
  code : String,
  minimum : Int,
  description? : String = "payload capacity requirement",
  severity? : CompatibilitySeverity = CompatibilityError,
) -> CompatibilityRequirement {
  let item = compatibility_requirement_base(
    code,
    description,
    CompatibilityMinimumPayload,
    severity,
  )
  item.numeric_value = if minimum < 0 { 0 } else { minimum }
  item
}

///|
pub fn compatibility_minimum_identifier_bits(
  code : String,
  minimum : Int,
  description? : String = "identifier width requirement",
  severity? : CompatibilitySeverity = CompatibilityError,
) -> CompatibilityRequirement {
  let item = compatibility_requirement_base(
    code,
    description,
    CompatibilityMinimumIdentifierBits,
    severity,
  )
  item.numeric_value = if minimum < 0 { 0 } else { minimum }
  item
}

///|
pub fn compatibility_minimum_node_capacity(
  code : String,
  minimum : Int,
  description? : String = "node capacity requirement",
  severity? : CompatibilitySeverity = CompatibilityError,
) -> CompatibilityRequirement {
  let item = compatibility_requirement_base(
    code,
    description,
    CompatibilityMinimumNodeCapacity,
    severity,
  )
  item.numeric_value = if minimum < 0 { 0 } else { minimum }
  item
}

///|
pub fn compatibility_protocol_is(
  code : String,
  protocol : CompatibilityProtocol,
  description? : String = "protocol family requirement",
  severity? : CompatibilitySeverity = CompatibilityError,
) -> CompatibilityRequirement {
  let item = compatibility_requirement_base(
    code,
    description,
    CompatibilityProtocolIs,
    severity,
  )
  item.protocol_value = Some(protocol)
  item
}

///|
pub fn compatibility_requires_feature(
  code : String,
  feature : String,
  description? : String = "feature is required",
  severity? : CompatibilitySeverity = CompatibilityError,
) -> CompatibilityRequirement {
  let item = compatibility_requirement_base(
    code,
    description,
    CompatibilityRequiresFeature,
    severity,
  )
  item.feature_value = Some(feature)
  item
}

///|
pub fn compatibility_forbids_feature(
  code : String,
  feature : String,
  description? : String = "feature must not be enabled",
  severity? : CompatibilitySeverity = CompatibilityWarning,
) -> CompatibilityRequirement {
  let item = compatibility_requirement_base(
    code,
    description,
    CompatibilityForbidsFeature,
    severity,
  )
  item.feature_value = Some(feature)
  item
}

///|
pub fn compatibility_requires_fd(
  code : String,
  description? : String = "CAN FD is required",
  severity? : CompatibilitySeverity = CompatibilityError,
) -> CompatibilityRequirement {
  compatibility_requirement_base(
    code,
    description,
    CompatibilityRequiresFd,
    severity,
  )
}

///|
pub fn compatibility_requires_classic(
  code : String,
  description? : String = "classic CAN is required",
  severity? : CompatibilitySeverity = CompatibilityError,
) -> CompatibilityRequirement {
  compatibility_requirement_base(
    code,
    description,
    CompatibilityRequiresClassic,
    severity,
  )
}

///|
pub fn compatibility_requires_isotp(
  code : String,
  description? : String = "ISO-TP transport is required",
  severity? : CompatibilitySeverity = CompatibilityError,
) -> CompatibilityRequirement {
  let item = compatibility_requirement_base(
    code,
    description,
    CompatibilityRequiresIsoTp,
    severity,
  )
  item
}

///|
pub fn compatibility_requires_diagnostics(
  code : String,
  description? : String = "diagnostic services are required",
  severity? : CompatibilitySeverity = CompatibilityError,
) -> CompatibilityRequirement {
  let item = compatibility_requirement_base(
    code,
    description,
    CompatibilityRequiresDiagnostics,
    severity,
  )
  item
}

///|
pub fn CompatibilityRequirement::code(
  self : CompatibilityRequirement,
) -> String {
  self.code
}

///|
pub fn CompatibilityRequirement::description(
  self : CompatibilityRequirement,
) -> String {
  self.description
}

///|
pub fn CompatibilityRequirement::kind(
  self : CompatibilityRequirement,
) -> CompatibilityRequirementKind {
  self.kind
}

///|
pub fn CompatibilityRequirement::numeric_value(
  self : CompatibilityRequirement,
) -> Int {
  self.numeric_value
}

///|
pub fn CompatibilityRequirement::protocol_value(
  self : CompatibilityRequirement,
) -> CompatibilityProtocol? {
  self.protocol_value
}

///|
pub fn CompatibilityRequirement::feature_value(
  self : CompatibilityRequirement,
) -> String? {
  self.feature_value
}

///|
pub fn CompatibilityRequirement::severity(
  self : CompatibilityRequirement,
) -> CompatibilitySeverity {
  self.severity
}

///|
/// A single finding produced while checking a target.
pub struct CompatibilityIssue {
  target : String
  code : String
  severity : CompatibilitySeverity
  passed : Bool
  message : String
  observed : String
  expected : String
}

///|
fn compatibility_issue(
  target : String,
  requirement : CompatibilityRequirement,
  passed : Bool,
  message : String,
  observed : String,
  expected : String,
) -> CompatibilityIssue {
  {
    target,
    code: requirement.code,
    severity: requirement.severity,
    passed,
    message,
    observed,
    expected,
  }
}

///|
pub fn CompatibilityIssue::target(self : CompatibilityIssue) -> String {
  self.target
}

///|
pub fn CompatibilityIssue::code(self : CompatibilityIssue) -> String {
  self.code
}

///|
pub fn CompatibilityIssue::severity(
  self : CompatibilityIssue,
) -> CompatibilitySeverity {
  self.severity
}

///|
pub fn CompatibilityIssue::passed(self : CompatibilityIssue) -> Bool {
  self.passed
}

///|
pub fn CompatibilityIssue::message(self : CompatibilityIssue) -> String {
  self.message
}

///|
pub fn CompatibilityIssue::observed(self : CompatibilityIssue) -> String {
  self.observed
}

///|
pub fn CompatibilityIssue::expected(self : CompatibilityIssue) -> String {
  self.expected
}

///|
pub fn CompatibilityIssue::text(self : CompatibilityIssue) -> String {
  let builder = StringBuilder()
  builder.write_string(if self.passed { "PASS" } else { "FAIL" })
  builder.write_string(" ")
  builder.write_string(compatibility_severity_text(self.severity))
  builder.write_string(" ")
  builder.write_string(self.code)
  builder.write_string(" ")
  builder.write_string(self.message)
  builder.write_string(" observed=")
  builder.write_string(self.observed)
  builder.write_string(" expected=")
  builder.write_string(self.expected)
  builder.to_string()
}

///|
/// Aggregated result for one target.
pub struct CompatibilityEvaluation {
  target : CompatibilityTarget
  issues : Array[CompatibilityIssue]
  passed : Int
  warnings : Int
  failures : Int
  score : Int
}

///|
pub fn CompatibilityEvaluation::target(
  self : CompatibilityEvaluation,
) -> CompatibilityTarget {
  self.target
}

///|
pub fn CompatibilityEvaluation::issues(
  self : CompatibilityEvaluation,
) -> Array[CompatibilityIssue] {
  self.issues.copy()
}

///|
pub fn CompatibilityEvaluation::passed(self : CompatibilityEvaluation) -> Int {
  self.passed
}

///|
pub fn CompatibilityEvaluation::warnings(self : CompatibilityEvaluation) -> Int {
  self.warnings
}

///|
pub fn CompatibilityEvaluation::failures(self : CompatibilityEvaluation) -> Int {
  self.failures
}

///|
pub fn CompatibilityEvaluation::score(self : CompatibilityEvaluation) -> Int {
  self.score
}

///|
pub fn CompatibilityEvaluation::is_compatible(
  self : CompatibilityEvaluation,
) -> Bool {
  self.failures == 0
}

///|
pub fn CompatibilityEvaluation::issue_count(
  self : CompatibilityEvaluation,
) -> Int {
  self.issues.length()
}

///|
pub fn CompatibilityEvaluation::summary(
  self : CompatibilityEvaluation,
) -> String {
  let builder = StringBuilder()
  builder.write_string(self.target.name)
  builder.write_string(" compatible=")
  builder.write_string(if self.is_compatible() { "true" } else { "false" })
  builder.write_string(" passed=")
  builder.write_string(self.passed.to_string())
  builder.write_string(" warnings=")
  builder.write_string(self.warnings.to_string())
  builder.write_string(" failures=")
  builder.write_string(self.failures.to_string())
  builder.write_string(" score=")
  builder.write_string(self.score.to_string())
  builder.to_string()
}

///|
pub fn CompatibilityEvaluation::report(
  self : CompatibilityEvaluation,
) -> String {
  let builder = StringBuilder()
  builder.write_string(self.summary())
  builder.write_string("\n")
  for issue in self.issues {
    builder.write_string("- ")
    builder.write_string(issue.text())
    builder.write_string("\n")
  }
  builder.to_string()
}

///|
/// A matrix of target profiles and production requirements.
pub struct CompatibilityMatrix {
  name : String
  strict : Bool
  targets : Array[CompatibilityTarget]
  requirements : Array[CompatibilityRequirement]
}

///|
pub fn compatibility_matrix(
  name? : String = "can deployment matrix",
  strict? : Bool = true,
) -> CompatibilityMatrix {
  { name, strict, targets: [], requirements: [] }
}

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

///|
pub fn CompatibilityMatrix::strict(self : CompatibilityMatrix) -> Bool {
  self.strict
}

///|
pub fn CompatibilityMatrix::target_count(self : CompatibilityMatrix) -> Int {
  self.targets.length()
}

///|
pub fn CompatibilityMatrix::requirement_count(
  self : CompatibilityMatrix,
) -> Int {
  self.requirements.length()
}

///|
pub fn CompatibilityMatrix::targets(
  self : CompatibilityMatrix,
) -> Array[CompatibilityTarget] {
  self.targets.copy()
}

///|
pub fn CompatibilityMatrix::requirements(
  self : CompatibilityMatrix,
) -> Array[CompatibilityRequirement] {
  self.requirements.copy()
}

///|
pub fn CompatibilityMatrix::add_target(
  self : CompatibilityMatrix,
  target : CompatibilityTarget,
) -> Unit {
  self.targets.push(target)
}

///|
pub fn CompatibilityMatrix::add_requirement(
  self : CompatibilityMatrix,
  requirement : CompatibilityRequirement,
) -> Unit {
  self.requirements.push(requirement)
}

///|
pub fn CompatibilityMatrix::clear_targets(self : CompatibilityMatrix) -> Unit {
  while self.targets.length() > 0 {
    ignore(self.targets.pop())
  }
}

///|
pub fn CompatibilityMatrix::clear_requirements(
  self : CompatibilityMatrix,
) -> Unit {
  while self.requirements.length() > 0 {
    ignore(self.requirements.pop())
  }
}

///|
fn compatibility_same_protocol(
  left : CompatibilityProtocol,
  right : CompatibilityProtocol,
) -> Bool {
  match left {
    CompatibilityClassicCan => right is CompatibilityClassicCan
    CompatibilityCanFd => right is CompatibilityCanFd
    CompatibilityIsoTp => right is CompatibilityIsoTp
    CompatibilityUds => right is CompatibilityUds
    CompatibilityCanOpen => right is CompatibilityCanOpen
    CompatibilityJ1939 => right is CompatibilityJ1939
    CompatibilityGateway => right is CompatibilityGateway
    CompatibilityCustom => right is CompatibilityCustom
  }
}

///|
fn compatibility_protocol_requirement_passes(
  target : CompatibilityTarget,
  requirement : CompatibilityRequirement,
) -> Bool {
  match requirement.protocol_value {
    Some(protocol) => compatibility_same_protocol(target.protocol, protocol)
    None => false
  }
}

///|
fn compatibility_feature_requirement_passes(
  target : CompatibilityTarget,
  requirement : CompatibilityRequirement,
  wanted : Bool,
) -> Bool {
  match requirement.feature_value {
    Some(feature) => target.has_feature(feature) == wanted
    None => false
  }
}

///|
fn compatibility_protocol_has_fd(protocol : CompatibilityProtocol) -> Bool {
  match protocol {
    CompatibilityCanFd => true
    CompatibilityGateway => true
    CompatibilityUds => true
    CompatibilityIsoTp => true
    _ => false
  }
}

///|
fn compatibility_protocol_has_classic(protocol : CompatibilityProtocol) -> Bool {
  match protocol {
    CompatibilityClassicCan => true
    CompatibilityCanFd => true
    CompatibilityCanOpen => true
    CompatibilityJ1939 => true
    CompatibilityGateway => true
    CompatibilityUds => true
    CompatibilityIsoTp => true
    CompatibilityCustom => true
  }
}

///|
fn compatibility_protocol_has_isotp(protocol : CompatibilityProtocol) -> Bool {
  match protocol {
    CompatibilityIsoTp => true
    CompatibilityUds => true
    CompatibilityGateway => true
    _ => false
  }
}

///|
fn compatibility_protocol_has_diagnostics(
  protocol : CompatibilityProtocol,
) -> Bool {
  match protocol {
    CompatibilityUds => true
    CompatibilityIsoTp => true
    CompatibilityGateway => true
    _ => false
  }
}

///|
fn compatibility_requirement_passes(
  target : CompatibilityTarget,
  requirement : CompatibilityRequirement,
) -> Bool {
  match requirement.kind {
    CompatibilityMinimumBitrate =>
      target.nominal_bitrate >= requirement.numeric_value
    CompatibilityMinimumDataBitrate =>
      target.data_bitrate >= requirement.numeric_value
    CompatibilityMinimumPayload =>
      target.max_payload >= requirement.numeric_value
    CompatibilityMinimumIdentifierBits =>
      target.identifier_bits >= requirement.numeric_value
    CompatibilityMinimumNodeCapacity =>
      target.node_capacity >= requirement.numeric_value
    CompatibilityProtocolIs =>
      compatibility_protocol_requirement_passes(target, requirement)
    CompatibilityRequiresFeature =>
      compatibility_feature_requirement_passes(target, requirement, true)
    CompatibilityForbidsFeature =>
      compatibility_feature_requirement_passes(target, requirement, false)
    CompatibilityRequiresFd => compatibility_protocol_has_fd(target.protocol)
    CompatibilityRequiresClassic =>
      compatibility_protocol_has_classic(target.protocol)
    CompatibilityRequiresIsoTp =>
      compatibility_protocol_has_isotp(target.protocol)
    CompatibilityRequiresDiagnostics =>
      compatibility_protocol_has_diagnostics(target.protocol)
  }
}

///|
fn compatibility_observed(
  target : CompatibilityTarget,
  requirement : CompatibilityRequirement,
) -> String {
  match requirement.kind {
    CompatibilityMinimumBitrate => target.nominal_bitrate.to_string()
    CompatibilityMinimumDataBitrate => target.data_bitrate.to_string()
    CompatibilityMinimumPayload => target.max_payload.to_string()
    CompatibilityMinimumIdentifierBits => target.identifier_bits.to_string()
    CompatibilityMinimumNodeCapacity => target.node_capacity.to_string()
    CompatibilityProtocolIs => compatibility_protocol_text(target.protocol)
    CompatibilityRequiresFeature => target.feature_csv()
    CompatibilityForbidsFeature => target.feature_csv()
    CompatibilityRequiresFd => compatibility_protocol_text(target.protocol)
    CompatibilityRequiresClassic => compatibility_protocol_text(target.protocol)
    CompatibilityRequiresIsoTp => compatibility_protocol_text(target.protocol)
    CompatibilityRequiresDiagnostics =>
      compatibility_protocol_text(target.protocol)
  }
}

///|
fn compatibility_expected(requirement : CompatibilityRequirement) -> String {
  match requirement.kind {
    CompatibilityMinimumBitrate => ">= " + requirement.numeric_value.to_string()
    CompatibilityMinimumDataBitrate =>
      ">= " + requirement.numeric_value.to_string()
    CompatibilityMinimumPayload => ">= " + requirement.numeric_value.to_string()
    CompatibilityMinimumIdentifierBits =>
      ">= " + requirement.numeric_value.to_string()
    CompatibilityMinimumNodeCapacity =>
      ">= " + requirement.numeric_value.to_string()
    CompatibilityProtocolIs =>
      match requirement.protocol_value {
        Some(protocol) => compatibility_protocol_text(protocol)
        None => "protocol"
      }
    CompatibilityRequiresFeature =>
      match requirement.feature_value {
        Some(feature) => "contains " + feature
        None => "feature"
      }
    CompatibilityForbidsFeature =>
      match requirement.feature_value {
        Some(feature) => "without " + feature
        None => "feature"
      }
    CompatibilityRequiresFd => "CAN FD family"
    CompatibilityRequiresClassic => "classic CAN family"
    CompatibilityRequiresIsoTp => "ISO-TP family"
    CompatibilityRequiresDiagnostics => "diagnostic family"
  }
}

///|
fn compatibility_message(
  requirement : CompatibilityRequirement,
  passed : Bool,
) -> String {
  if passed {
    requirement.description + " satisfied"
  } else {
    requirement.description + " not satisfied"
  }
}

///|
pub fn CompatibilityMatrix::evaluate(
  self : CompatibilityMatrix,
  target : CompatibilityTarget,
) -> CompatibilityEvaluation {
  let issues : Array[CompatibilityIssue] = []
  let mut passed = 0
  let mut warnings = 0
  let mut failures = 0
  let mut score = 100
  for requirement in self.requirements {
    let requirement_passed = compatibility_requirement_passes(
      target, requirement,
    )
    let issue = compatibility_issue(
      target.name,
      requirement,
      requirement_passed,
      compatibility_message(requirement, requirement_passed),
      compatibility_observed(target, requirement),
      compatibility_expected(requirement),
    )
    issues.push(issue)
    if requirement_passed {
      passed += 1
    } else {
      match requirement.severity {
        CompatibilityInfo => score -= 1
        CompatibilityWarning => {
          warnings += 1
          score -= 5
        }
        CompatibilityError => {
          failures += 1
          score -= if self.strict { 20 } else { 10 }
        }
      }
    }
  }
  {
    target,
    issues,
    passed,
    warnings,
    failures,
    score: if score < 0 {
      0
    } else {
      score
    },
  }
}

///|
pub fn CompatibilityMatrix::evaluate_all(
  self : CompatibilityMatrix,
) -> Array[CompatibilityEvaluation] {
  let results : Array[CompatibilityEvaluation] = []
  for target in self.targets {
    results.push(self.evaluate(target))
  }
  results
}

///|
pub fn CompatibilityMatrix::compatible_targets(
  self : CompatibilityMatrix,
) -> Array[CompatibilityTarget] {
  let result : Array[CompatibilityTarget] = []
  for target in self.targets {
    let evaluation = self.evaluate(target)
    if evaluation.is_compatible() {
      result.push(target)
    }
  }
  result
}

///|
pub fn CompatibilityMatrix::best_target(
  self : CompatibilityMatrix,
) -> CompatibilityTarget? {
  let mut best : CompatibilityTarget? = None
  let mut best_score = -1
  for target in self.targets {
    let score = self.evaluate(target).score
    if score > best_score {
      best_score = score
      best = Some(target)
    }
  }
  best
}

///|
pub fn CompatibilityMatrix::report(self : CompatibilityMatrix) -> String {
  let builder = StringBuilder()
  builder.write_string("# ")
  builder.write_string(self.name)
  builder.write_string("\n")
  builder.write_string("strict=")
  builder.write_string(if self.strict { "true" } else { "false" })
  builder.write_string(" targets=")
  builder.write_string(self.target_count().to_string())
  builder.write_string(" requirements=")
  builder.write_string(self.requirement_count().to_string())
  builder.write_string("\n")
  for evaluation in self.evaluate_all() {
    builder.write_string(evaluation.report())
  }
  builder.to_string()
}

///|
pub fn CompatibilityMatrix::csv(self : CompatibilityMatrix) -> String {
  let builder = StringBuilder()
  builder.write_string("target,compatible,passed,warnings,failures,score\n")
  for evaluation in self.evaluate_all() {
    builder.write_string(evaluation.target.name)
    builder.write_string(",")
    builder.write_string(
      if evaluation.is_compatible() {
        "true"
      } else {
        "false"
      },
    )
    builder.write_string(",")
    builder.write_string(evaluation.passed.to_string())
    builder.write_string(",")
    builder.write_string(evaluation.warnings.to_string())
    builder.write_string(",")
    builder.write_string(evaluation.failures.to_string())
    builder.write_string(",")
    builder.write_string(evaluation.score.to_string())
    builder.write_string("\n")
  }
  builder.to_string()
}

///|
pub fn CompatibilityMatrix::requirement_report(
  self : CompatibilityMatrix,
) -> String {
  let builder = StringBuilder()
  for requirement in self.requirements {
    builder.write_string(requirement.code)
    builder.write_string(" ")
    builder.write_string(compatibility_requirement_kind_text(requirement.kind))
    builder.write_string(" ")
    builder.write_string(requirement.description)
    builder.write_string(" severity=")
    builder.write_string(compatibility_severity_text(requirement.severity))
    builder.write_string("\n")
  }
  builder.to_string()
}

///|
/// A fleet-level summary used by acceptance gates and dashboards.
pub struct CompatibilityFleetSummary {
  target_count : Int
  compatible_count : Int
  warning_count : Int
  failure_count : Int
  average_score : Int
  lowest_score : Int
  highest_score : Int
}

///|
pub fn CompatibilityFleetSummary::target_count(
  self : CompatibilityFleetSummary,
) -> Int {
  self.target_count
}

///|
pub fn CompatibilityFleetSummary::compatible_count(
  self : CompatibilityFleetSummary,
) -> Int {
  self.compatible_count
}

///|
pub fn CompatibilityFleetSummary::warning_count(
  self : CompatibilityFleetSummary,
) -> Int {
  self.warning_count
}

///|
pub fn CompatibilityFleetSummary::failure_count(
  self : CompatibilityFleetSummary,
) -> Int {
  self.failure_count
}

///|
pub fn CompatibilityFleetSummary::average_score(
  self : CompatibilityFleetSummary,
) -> Int {
  self.average_score
}

///|
pub fn CompatibilityFleetSummary::lowest_score(
  self : CompatibilityFleetSummary,
) -> Int {
  self.lowest_score
}

///|
pub fn CompatibilityFleetSummary::highest_score(
  self : CompatibilityFleetSummary,
) -> Int {
  self.highest_score
}

///|
pub fn CompatibilityFleetSummary::is_release_ready(
  self : CompatibilityFleetSummary,
) -> Bool {
  self.target_count > 0 && self.failure_count == 0
}

///|
pub fn CompatibilityFleetSummary::text(
  self : CompatibilityFleetSummary,
) -> String {
  let builder = StringBuilder()
  builder.write_string("targets=")
  builder.write_string(self.target_count.to_string())
  builder.write_string(" compatible=")
  builder.write_string(self.compatible_count.to_string())
  builder.write_string(" warnings=")
  builder.write_string(self.warning_count.to_string())
  builder.write_string(" failures=")
  builder.write_string(self.failure_count.to_string())
  builder.write_string(" average=")
  builder.write_string(self.average_score.to_string())
  builder.write_string(" lowest=")
  builder.write_string(self.lowest_score.to_string())
  builder.write_string(" highest=")
  builder.write_string(self.highest_score.to_string())
  builder.write_string(" ready=")
  builder.write_string(if self.is_release_ready() { "true" } else { "false" })
  builder.to_string()
}

///|
pub fn CompatibilityMatrix::fleet_summary(
  self : CompatibilityMatrix,
) -> CompatibilityFleetSummary {
  let evaluations = self.evaluate_all()
  if evaluations.length() == 0 {
    return {
      target_count: 0,
      compatible_count: 0,
      warning_count: 0,
      failure_count: 0,
      average_score: 0,
      lowest_score: 0,
      highest_score: 0,
    }
  }
  let mut compatible_count = 0
  let mut warning_count = 0
  let mut failure_count = 0
  let mut score_total = 0
  let mut lowest_score = 101
  let mut highest_score = 0
  for evaluation in evaluations {
    if evaluation.is_compatible() {
      compatible_count += 1
    }
    warning_count += evaluation.warnings
    failure_count += evaluation.failures
    score_total += evaluation.score
    if evaluation.score < lowest_score {
      lowest_score = evaluation.score
    }
    if evaluation.score > highest_score {
      highest_score = evaluation.score
    }
  }
  {
    target_count: evaluations.length(),
    compatible_count,
    warning_count,
    failure_count,
    average_score: score_total / evaluations.length(),
    lowest_score,
    highest_score,
  }
}

///|
pub fn compatibility_classic_target(
  name? : String = "classic-can",
  node_capacity? : Int = 32,
) -> CompatibilityTarget {
  compatibility_target(
    name,
    CompatibilityClassicCan,
    500,
    0,
    8,
    11,
    node_capacity,
    features=["classic", "remote-frame", "error-frame"],
    notes="Classical CAN, 11-bit identifiers, 8-byte data fields",
  )
}

///|
pub fn compatibility_fd_target(
  name? : String = "can-fd",
  node_capacity? : Int = 64,
) -> CompatibilityTarget {
  compatibility_target(
    name,
    CompatibilityCanFd,
    500,
    2_000,
    64,
    29,
    node_capacity,
    features=["fd", "bitrate-switch", "extended-id", "crc"],
    notes="CAN FD with separate data phase and extended identifiers",
  )
}

///|
pub fn compatibility_isotp_target(
  name? : String = "isotp",
  node_capacity? : Int = 16,
) -> CompatibilityTarget {
  compatibility_target(
    name,
    CompatibilityIsoTp,
    500,
    0,
    4095,
    11,
    node_capacity,
    features=["classic", "flow-control", "segmentation", "padding"],
    notes="ISO-TP transport over classical CAN",
  )
}

///|
pub fn compatibility_uds_target(
  name? : String = "uds",
  node_capacity? : Int = 16,
) -> CompatibilityTarget {
  compatibility_target(
    name,
    CompatibilityUds,
    500,
    2_000,
    4095,
    29,
    node_capacity,
    features=["fd", "diagnostics", "isotp", "security-access", "dtc"],
    notes="UDS diagnostic endpoint with segmented payload support",
  )
}

///|
pub fn compatibility_canopen_target(
  name? : String = "canopen",
  node_capacity? : Int = 127,
) -> CompatibilityTarget {
  compatibility_target(
    name,
    CompatibilityCanOpen,
    1000,
    0,
    8,
    11,
    node_capacity,
    features=["classic", "heartbeat", "sdo", "pdo", "nmt"],
    notes="CANopen network with heartbeat and object dictionary",
  )
}

///|
pub fn compatibility_j1939_target(
  name? : String = "j1939",
  node_capacity? : Int = 30,
) -> CompatibilityTarget {
  compatibility_target(
    name,
    CompatibilityJ1939,
    250,
    0,
    1785,
    29,
    node_capacity,
    features=["classic", "extended-id", "address-claim", "bam", "request"],
    notes="J1939 network with PGN addressing and BAM transport",
  )
}

///|
pub fn compatibility_gateway_target(
  name? : String = "gateway",
  node_capacity? : Int = 128,
) -> CompatibilityTarget {
  compatibility_target(
    name,
    CompatibilityGateway,
    1000,
    5_000,
    4095,
    29,
    node_capacity,
    features=["classic", "fd", "routing", "rate-limit", "diagnostics", "isotp"],
    notes="Multi-bus gateway with policy and diagnostic routing",
  )
}

///|
pub fn compatibility_default_matrix() -> CompatibilityMatrix {
  let matrix = compatibility_matrix(
    name="default production matrix",
    strict=true,
  )
  matrix.add_target(compatibility_classic_target())
  matrix.add_target(compatibility_fd_target())
  matrix.add_target(compatibility_isotp_target())
  matrix.add_target(compatibility_uds_target())
  matrix.add_target(compatibility_canopen_target())
  matrix.add_target(compatibility_j1939_target())
  matrix.add_target(compatibility_gateway_target())
  matrix.add_requirement(
    compatibility_minimum_bitrate(
      "NET-001",
      250,
      description="nominal bus speed supports the deployment profile",
    ),
  )
  matrix.add_requirement(
    compatibility_minimum_payload(
      "NET-002",
      8,
      description="single-frame payload is at least eight bytes",
    ),
  )
  matrix.add_requirement(
    compatibility_minimum_identifier_bits(
      "NET-003",
      11,
      description="identifier representation accepts standard CAN IDs",
    ),
  )
  matrix.add_requirement(
    compatibility_minimum_node_capacity(
      "NET-004",
      8,
      description="target can host the minimum node set",
    ),
  )
  matrix
}

///|
pub fn compatibility_diagnostics_matrix() -> CompatibilityMatrix {
  let matrix = compatibility_matrix(
    name="diagnostics deployment matrix",
    strict=true,
  )
  matrix.add_target(compatibility_isotp_target())
  matrix.add_target(compatibility_uds_target())
  matrix.add_target(compatibility_gateway_target())
  matrix.add_requirement(compatibility_requires_isotp("UDS-001"))
  matrix.add_requirement(compatibility_requires_diagnostics("UDS-002"))
  matrix.add_requirement(
    compatibility_requires_feature(
      "UDS-003",
      "dtc",
      description="diagnostic trouble code storage is available",
    ),
  )
  matrix.add_requirement(
    compatibility_minimum_payload(
      "UDS-004",
      4095,
      description="segmented diagnostic payload is available",
    ),
  )
  matrix
}

///|
pub fn compatibility_gateway_matrix() -> CompatibilityMatrix {
  let matrix = compatibility_matrix(
    name="gateway deployment matrix",
    strict=true,
  )
  matrix.add_target(compatibility_gateway_target())
  matrix.add_target(compatibility_fd_target())
  matrix.add_requirement(compatibility_requires_fd("GW-001"))
  matrix.add_requirement(
    compatibility_requires_feature(
      "GW-002",
      "routing",
      description="routing policy engine is available",
    ),
  )
  matrix.add_requirement(
    compatibility_requires_feature(
      "GW-003",
      "rate-limit",
      description="rate limiting is available",
      severity=CompatibilityWarning,
    ),
  )
  matrix.add_requirement(
    compatibility_minimum_data_bitrate(
      "GW-004",
      2000,
      description="data phase keeps gateway forwarding latency bounded",
    ),
  )
  matrix
}

///|
/// A named release gate composed of several matrices.
pub struct CompatibilityReleaseGate {
  name : String
  matrices : Array[CompatibilityMatrix]
  minimum_score : Int
  require_all_targets : Bool
}

///|
pub fn compatibility_release_gate(
  name? : String = "release compatibility gate",
  minimum_score? : Int = 80,
  require_all_targets? : Bool = false,
) -> CompatibilityReleaseGate {
  {
    name,
    matrices: [],
    minimum_score: if minimum_score < 0 {
      0
    } else if minimum_score > 100 {
      100
    } else {
      minimum_score
    },
    require_all_targets,
  }
}

///|
pub fn CompatibilityReleaseGate::add_matrix(
  self : CompatibilityReleaseGate,
  matrix : CompatibilityMatrix,
) -> Unit {
  self.matrices.push(matrix)
}

///|
pub fn CompatibilityReleaseGate::matrix_count(
  self : CompatibilityReleaseGate,
) -> Int {
  self.matrices.length()
}

///|
pub fn CompatibilityReleaseGate::minimum_score(
  self : CompatibilityReleaseGate,
) -> Int {
  self.minimum_score
}

///|
pub fn CompatibilityReleaseGate::require_all_targets(
  self : CompatibilityReleaseGate,
) -> Bool {
  self.require_all_targets
}

///|
pub fn CompatibilityReleaseGate::ready(self : CompatibilityReleaseGate) -> Bool {
  if self.matrices.length() == 0 {
    return false
  }
  for matrix in self.matrices {
    let summary = matrix.fleet_summary()
    if summary.failure_count > 0 {
      return false
    }
    if summary.lowest_score < self.minimum_score {
      return false
    }
    if self.require_all_targets &&
      summary.compatible_count != summary.target_count {
      return false
    }
  }
  true
}

///|
pub fn CompatibilityReleaseGate::report(
  self : CompatibilityReleaseGate,
) -> String {
  let builder = StringBuilder()
  builder.write_string("# ")
  builder.write_string(self.name)
  builder.write_string(" ready=")
  builder.write_string(if self.ready() { "true" } else { "false" })
  builder.write_string(" minimum_score=")
  builder.write_string(self.minimum_score.to_string())
  builder.write_string("\n")
  for matrix in self.matrices {
    builder.write_string(matrix.report())
  }
  builder.to_string()
}

///|
pub fn compatibility_default_release_gate() -> CompatibilityReleaseGate {
  let gate = compatibility_release_gate(
    name="CAN release compatibility gate",
    minimum_score=70,
    require_all_targets=false,
  )
  gate.add_matrix(compatibility_default_matrix())
  gate.add_matrix(compatibility_diagnostics_matrix())
  gate.add_matrix(compatibility_gateway_matrix())
  gate
}

///|
/// Compare two targets and describe the capabilities that are lost during a downgrade.
pub struct CompatibilityDelta {
  source : String
  destination : String
  bitrate_loss : Int
  data_bitrate_loss : Int
  payload_loss : Int
  identifier_loss : Int
  node_capacity_loss : Int
  lost_features : Array[String]
}

///|
fn compatibility_positive_loss(source : Int, destination : Int) -> Int {
  if source > destination {
    source - destination
  } else {
    0
  }
}

///|
pub fn compatibility_compare_targets(
  source : CompatibilityTarget,
  destination : CompatibilityTarget,
) -> CompatibilityDelta {
  let lost_features : Array[String] = []
  for feature in source.features {
    if !destination.has_feature(feature) {
      lost_features.push(feature)
    }
  }
  {
    source: source.name,
    destination: destination.name,
    bitrate_loss: compatibility_positive_loss(
      source.nominal_bitrate,
      destination.nominal_bitrate,
    ),
    data_bitrate_loss: compatibility_positive_loss(
      source.data_bitrate,
      destination.data_bitrate,
    ),
    payload_loss: compatibility_positive_loss(
      source.max_payload,
      destination.max_payload,
    ),
    identifier_loss: compatibility_positive_loss(
      source.identifier_bits,
      destination.identifier_bits,
    ),
    node_capacity_loss: compatibility_positive_loss(
      source.node_capacity,
      destination.node_capacity,
    ),
    lost_features,
  }
}

///|
pub fn CompatibilityDelta::source(self : CompatibilityDelta) -> String {
  self.source
}

///|
pub fn CompatibilityDelta::destination(self : CompatibilityDelta) -> String {
  self.destination
}

///|
pub fn CompatibilityDelta::bitrate_loss(self : CompatibilityDelta) -> Int {
  self.bitrate_loss
}

///|
pub fn CompatibilityDelta::data_bitrate_loss(self : CompatibilityDelta) -> Int {
  self.data_bitrate_loss
}

///|
pub fn CompatibilityDelta::payload_loss(self : CompatibilityDelta) -> Int {
  self.payload_loss
}

///|
pub fn CompatibilityDelta::identifier_loss(self : CompatibilityDelta) -> Int {
  self.identifier_loss
}

///|
pub fn CompatibilityDelta::node_capacity_loss(self : CompatibilityDelta) -> Int {
  self.node_capacity_loss
}

///|
pub fn CompatibilityDelta::lost_features(
  self : CompatibilityDelta,
) -> Array[String] {
  self.lost_features.copy()
}

///|
pub fn CompatibilityDelta::is_lossless(self : CompatibilityDelta) -> Bool {
  self.bitrate_loss == 0 &&
  self.data_bitrate_loss == 0 &&
  self.payload_loss == 0 &&
  self.identifier_loss == 0 &&
  self.node_capacity_loss == 0 &&
  self.lost_features.length() == 0
}

///|
pub fn CompatibilityDelta::report(self : CompatibilityDelta) -> String {
  let builder = StringBuilder()
  builder.write_string(self.source)
  builder.write_string(" -> ")
  builder.write_string(self.destination)
  builder.write_string(" lossless=")
  builder.write_string(if self.is_lossless() { "true" } else { "false" })
  builder.write_string(" bitrate_loss=")
  builder.write_string(self.bitrate_loss.to_string())
  builder.write_string(" data_bitrate_loss=")
  builder.write_string(self.data_bitrate_loss.to_string())
  builder.write_string(" payload_loss=")
  builder.write_string(self.payload_loss.to_string())
  builder.write_string(" identifier_loss=")
  builder.write_string(self.identifier_loss.to_string())
  builder.write_string(" node_capacity_loss=")
  builder.write_string(self.node_capacity_loss.to_string())
  builder.write_string(" lost_features=")
  builder.write_string(self.lost_features.length().to_string())
  builder.to_string()
}

///|
/// A ranked compatibility candidate.
pub struct CompatibilityCandidate {
  target : CompatibilityTarget
  evaluation : CompatibilityEvaluation
  rank : Int
}

///|
pub fn CompatibilityCandidate::target(
  self : CompatibilityCandidate,
) -> CompatibilityTarget {
  self.target
}

///|
pub fn CompatibilityCandidate::evaluation(
  self : CompatibilityCandidate,
) -> CompatibilityEvaluation {
  self.evaluation
}

///|
pub fn CompatibilityCandidate::rank(self : CompatibilityCandidate) -> Int {
  self.rank
}

///|
pub fn CompatibilityCandidate::summary(self : CompatibilityCandidate) -> String {
  self.rank.to_string() +
  ": " +
  self.target.name +
  " " +
  self.evaluation.summary()
}

///|
pub fn CompatibilityMatrix::ranked_candidates(
  self : CompatibilityMatrix,
) -> Array[CompatibilityCandidate] {
  let candidates : Array[CompatibilityCandidate] = []
  for target in self.targets {
    let evaluation = self.evaluate(target)
    let mut rank = 0
    for previous in candidates {
      if evaluation.score > previous.evaluation.score {
        rank += 1
      }
    }
    candidates.push({ target, evaluation, rank })
  }
  candidates
}

///|
/// Validate a route between two protocol endpoints.
pub struct CompatibilityRoute {
  source : CompatibilityTarget
  destination : CompatibilityTarget
  transport : CompatibilityProtocol
  max_payload : Int
  uses_segmentation : Bool
  valid : Bool
  reason : String
}

///|
fn compatibility_route_reason(
  valid : Bool,
  transport : CompatibilityProtocol,
) -> String {
  if valid {
    "route supports the selected transport"
  } else {
    "route cannot carry the selected transport: " +
    compatibility_protocol_text(transport)
  }
}

///|
pub fn compatibility_route(
  source : CompatibilityTarget,
  destination : CompatibilityTarget,
  transport : CompatibilityProtocol,
) -> CompatibilityRoute {
  let source_payload = source.max_payload
  let destination_payload = destination.max_payload
  let max_payload = if source_payload < destination_payload {
    source_payload
  } else {
    destination_payload
  }
  let source_fd = compatibility_protocol_has_fd(source.protocol)
  let destination_fd = compatibility_protocol_has_fd(destination.protocol)
  let protocol_valid = match transport {
    CompatibilityClassicCan =>
      compatibility_protocol_has_classic(source.protocol) &&
      compatibility_protocol_has_classic(destination.protocol)
    CompatibilityCanFd => source_fd && destination_fd
    CompatibilityIsoTp =>
      compatibility_protocol_has_isotp(source.protocol) &&
      compatibility_protocol_has_isotp(destination.protocol)
    CompatibilityUds =>
      compatibility_protocol_has_diagnostics(source.protocol) &&
      compatibility_protocol_has_diagnostics(destination.protocol)
    CompatibilityCanOpen =>
      source.protocol is CompatibilityCanOpen &&
      destination.protocol is CompatibilityCanOpen
    CompatibilityJ1939 =>
      source.protocol is CompatibilityJ1939 &&
      destination.protocol is CompatibilityJ1939
    CompatibilityGateway =>
      source.protocol is CompatibilityGateway ||
      destination.protocol is CompatibilityGateway
    CompatibilityCustom => true
  }
  let uses_segmentation = transport is CompatibilityIsoTp ||
    transport is CompatibilityUds ||
    max_payload > 64
  let valid = protocol_valid && max_payload > 0
  {
    source,
    destination,
    transport,
    max_payload,
    uses_segmentation,
    valid,
    reason: compatibility_route_reason(valid, transport),
  }
}

///|
pub fn CompatibilityRoute::source(
  self : CompatibilityRoute,
) -> CompatibilityTarget {
  self.source
}

///|
pub fn CompatibilityRoute::destination(
  self : CompatibilityRoute,
) -> CompatibilityTarget {
  self.destination
}

///|
pub fn CompatibilityRoute::transport(
  self : CompatibilityRoute,
) -> CompatibilityProtocol {
  self.transport
}

///|
pub fn CompatibilityRoute::max_payload(self : CompatibilityRoute) -> Int {
  self.max_payload
}

///|
pub fn CompatibilityRoute::uses_segmentation(self : CompatibilityRoute) -> Bool {
  self.uses_segmentation
}

///|
pub fn CompatibilityRoute::valid(self : CompatibilityRoute) -> Bool {
  self.valid
}

///|
pub fn CompatibilityRoute::reason(self : CompatibilityRoute) -> String {
  self.reason
}

///|
pub fn CompatibilityRoute::summary(self : CompatibilityRoute) -> String {
  self.source.name +
  " -> " +
  self.destination.name +
  " " +
  compatibility_protocol_text(self.transport) +
  " valid=" +
  (if self.valid { "true" } else { "false" })
}

///|
/// A route table that prevents unsupported forwarding paths from reaching runtime.
pub struct CompatibilityRouteTable {
  routes : Array[CompatibilityRoute]
}

///|
pub fn compatibility_route_table() -> CompatibilityRouteTable {
  { routes: [] }
}

///|
pub fn CompatibilityRouteTable::add(
  self : CompatibilityRouteTable,
  route : CompatibilityRoute,
) -> Unit {
  self.routes.push(route)
}

///|
pub fn CompatibilityRouteTable::routes(
  self : CompatibilityRouteTable,
) -> Array[CompatibilityRoute] {
  self.routes.copy()
}

///|
pub fn CompatibilityRouteTable::valid_routes(
  self : CompatibilityRouteTable,
) -> Array[CompatibilityRoute] {
  let result : Array[CompatibilityRoute] = []
  for route in self.routes {
    if route.valid {
      result.push(route)
    }
  }
  result
}

///|
pub fn CompatibilityRouteTable::invalid_routes(
  self : CompatibilityRouteTable,
) -> Array[CompatibilityRoute] {
  let result : Array[CompatibilityRoute] = []
  for route in self.routes {
    if !route.valid {
      result.push(route)
    }
  }
  result
}

///|
pub fn CompatibilityRouteTable::report(
  self : CompatibilityRouteTable,
) -> String {
  let builder = StringBuilder()
  for route in self.routes {
    builder.write_string(route.summary())
    builder.write_string(" ")
    builder.write_string(route.reason)
    builder.write_string("\n")
  }
  builder.to_string()
}

///|
pub fn compatibility_build_default_routes() -> CompatibilityRouteTable {
  let table = compatibility_route_table()
  let classic = compatibility_classic_target()
  let fd = compatibility_fd_target()
  let uds = compatibility_uds_target()
  let gateway = compatibility_gateway_target()
  table.add(compatibility_route(classic, fd, CompatibilityClassicCan))
  table.add(compatibility_route(fd, gateway, CompatibilityCanFd))
  table.add(compatibility_route(uds, gateway, CompatibilityUds))
  table.add(compatibility_route(gateway, classic, CompatibilityGateway))
  table
}

///|
/// A compact machine-readable snapshot for logging and support bundles.
pub fn compatibility_snapshot(matrix : CompatibilityMatrix) -> String {
  let summary = matrix.fleet_summary()
  let builder = StringBuilder()
  builder.write_string("{\"name\":\"")
  builder.write_string(matrix.name)
  builder.write_string("\",\"targets\":")
  builder.write_string(summary.target_count.to_string())
  builder.write_string(",\"compatible\":")
  builder.write_string(summary.compatible_count.to_string())
  builder.write_string(",\"warnings\":")
  builder.write_string(summary.warning_count.to_string())
  builder.write_string(",\"failures\":")
  builder.write_string(summary.failure_count.to_string())
  builder.write_string(",\"average_score\":")
  builder.write_string(summary.average_score.to_string())
  builder.write_string("}")
  builder.to_string()
}

///|
/// Stable schema version for compatibility snapshots.
pub fn compatibility_schema_version() -> String {
  "compatibility-matrix-v1"
}

///|
/// Return a deterministic checksum-like fingerprint for a target.
pub fn compatibility_target_fingerprint(target : CompatibilityTarget) -> UInt64 {
  let mut value : UInt64 = 1469598103934665603
  value = (value ^ target.name.length().to_uint64()) * 1099511628211
  value = (value ^ target.nominal_bitrate.to_uint64()) * 1099511628211
  value = (value ^ target.data_bitrate.to_uint64()) * 1099511628211
  value = (value ^ target.max_payload.to_uint64()) * 1099511628211
  value = (value ^ target.identifier_bits.to_uint64()) * 1099511628211
  value = (value ^ target.node_capacity.to_uint64()) * 1099511628211
  for feature in target.features {
    value = (value ^ feature.length().to_uint64()) * 1099511628211
  }
  value
}

///|
/// Fingerprint all targets in insertion order for reproducible deployment metadata.
pub fn CompatibilityMatrix::fingerprint(self : CompatibilityMatrix) -> UInt64 {
  let mut value : UInt64 = 2166136261
  for target in self.targets {
    value = (value ^ compatibility_target_fingerprint(target)) * 16777619
  }
  for requirement in self.requirements {
    value = (value ^ requirement.code.length().to_uint64()) * 16777619
  }
  value
}

///|
/// Return a short status suitable for a health endpoint.
pub fn CompatibilityMatrix::status(self : CompatibilityMatrix) -> String {
  let summary = self.fleet_summary()
  if summary.target_count == 0 {
    "empty"
  } else if summary.failure_count > 0 {
    "failed"
  } else if summary.warning_count > 0 {
    "degraded"
  } else {
    "ready"
  }
}

///|
/// Filter requirements by severity for an operator-facing view.
pub fn CompatibilityMatrix::requirements_by_severity(
  self : CompatibilityMatrix,
  severity : CompatibilitySeverity,
) -> Array[CompatibilityRequirement] {
  let result : Array[CompatibilityRequirement] = []
  for requirement in self.requirements {
    let same = match severity {
      CompatibilityInfo => requirement.severity is CompatibilityInfo
      CompatibilityWarning => requirement.severity is CompatibilityWarning
      CompatibilityError => requirement.severity is CompatibilityError
    }
    if same {
      result.push(requirement)
    }
  }
  result
}

///|
/// Add the common minimums for a classical vehicle network.
pub fn compatibility_add_vehicle_requirements(
  matrix : CompatibilityMatrix,
) -> Unit {
  matrix.add_requirement(compatibility_minimum_bitrate("VEH-001", 250))
  matrix.add_requirement(compatibility_minimum_node_capacity("VEH-002", 16))
  matrix.add_requirement(
    compatibility_requires_feature(
      "VEH-003",
      "extended-id",
      description="vehicle network supports extended arbitration IDs",
      severity=CompatibilityWarning,
    ),
  )
}

///|
/// Add the common minimums for a diagnostic service.
pub fn compatibility_add_diagnostic_requirements(
  matrix : CompatibilityMatrix,
) -> Unit {
  matrix.add_requirement(compatibility_requires_isotp("DGN-001"))
  matrix.add_requirement(compatibility_requires_diagnostics("DGN-002"))
  matrix.add_requirement(compatibility_minimum_payload("DGN-003", 256))
  matrix.add_requirement(
    compatibility_requires_feature(
      "DGN-004",
      "security-access",
      description="security access hooks are available",
      severity=CompatibilityWarning,
    ),
  )
}

///|
/// Add the common minimums for a gateway deployment.
pub fn compatibility_add_gateway_requirements(
  matrix : CompatibilityMatrix,
) -> Unit {
  matrix.add_requirement(compatibility_requires_fd("GAT-001"))
  matrix.add_requirement(
    compatibility_requires_feature(
      "GAT-002",
      "routing",
      description="route policy evaluation is available",
    ),
  )
  matrix.add_requirement(
    compatibility_requires_feature(
      "GAT-003",
      "rate-limit",
      description="traffic shaping is available",
      severity=CompatibilityWarning,
    ),
  )
}

///|
/// Build a target set from a requested protocol family.
pub fn compatibility_targets_for_protocol(
  protocol : CompatibilityProtocol,
) -> Array[CompatibilityTarget] {
  match protocol {
    CompatibilityClassicCan => [compatibility_classic_target()]
    CompatibilityCanFd => [compatibility_fd_target()]
    CompatibilityIsoTp => [compatibility_isotp_target()]
    CompatibilityUds => [compatibility_uds_target()]
    CompatibilityCanOpen => [compatibility_canopen_target()]
    CompatibilityJ1939 => [compatibility_j1939_target()]
    CompatibilityGateway => [compatibility_gateway_target()]
    CompatibilityCustom => []
  }
}

///|
/// Check whether a target can be used as a bridge endpoint for a protocol.
pub fn compatibility_can_bridge(
  target : CompatibilityTarget,
  protocol : CompatibilityProtocol,
) -> Bool {
  let route = compatibility_route(target, target, protocol)
  route.valid
}

///|
/// Produce a bounded payload size for a route, reserving a byte for framing when needed.
pub fn compatibility_route_payload_budget(route : CompatibilityRoute) -> Int {
  if route.max_payload <= 0 {
    0
  } else if route.uses_segmentation {
    if route.max_payload > 1 {
      route.max_payload - 1
    } else {
      0
    }
  } else {
    route.max_payload
  }
}

///|
/// Build a plain-text incident hint from a failed evaluation.
pub fn compatibility_remediation(
  evaluation : CompatibilityEvaluation,
) -> String {
  if evaluation.is_compatible() {
    "no remediation required"
  } else {
    let builder = StringBuilder()
    builder.write_string("target ")
    builder.write_string(evaluation.target.name)
    builder.write_string(" has ")
    builder.write_string(evaluation.failures.to_string())
    builder.write_string(" blocking compatibility findings")
    if evaluation.warnings > 0 {
      builder.write_string(" and ")
      builder.write_string(evaluation.warnings.to_string())
      builder.write_string(" warnings")
    }
    builder.to_string()
  }
}

///|
/// Use the matrix as an acceptance predicate for one target.
pub fn compatibility_accepts(
  matrix : CompatibilityMatrix,
  target : CompatibilityTarget,
) -> Bool {
  matrix.evaluate(target).is_compatible()
}

///|
/// Return all failed requirements for a target.
pub fn compatibility_failures(
  evaluation : CompatibilityEvaluation,
) -> Array[CompatibilityIssue] {
  let result : Array[CompatibilityIssue] = []
  for issue in evaluation.issues {
    if !issue.passed {
      result.push(issue)
    }
  }
  result
}

///|
/// Return all warnings for a target.
pub fn compatibility_warnings(
  evaluation : CompatibilityEvaluation,
) -> Array[CompatibilityIssue] {
  let result : Array[CompatibilityIssue] = []
  for issue in evaluation.issues {
    if !issue.passed && issue.severity is CompatibilityWarning {
      result.push(issue)
    }
  }
  result
}

///|
/// Stable line-oriented output for CI annotations.
pub fn compatibility_ci_annotations(matrix : CompatibilityMatrix) -> String {
  let builder = StringBuilder()
  for evaluation in matrix.evaluate_all() {
    for issue in evaluation.issues {
      if !issue.passed {
        builder.write_string("::")
        builder.write_string(
          if issue.severity is CompatibilityError {
            "error"
          } else {
            "warning"
          },
        )
        builder.write_string(" title=")
        builder.write_string(issue.code)
        builder.write_string("::")
        builder.write_string(issue.message)
        builder.write_string("\n")
      }
    }
  }
  builder.to_string()
}

///|
/// Create a matrix from a single target and a reusable requirement set.
pub fn compatibility_matrix_for_target(
  target : CompatibilityTarget,
  requirements : Array[CompatibilityRequirement],
  strict? : Bool = true,
) -> CompatibilityMatrix {
  let matrix = compatibility_matrix(name=target.name + " matrix", strict~)
  matrix.add_target(target)
  for requirement in requirements {
    matrix.add_requirement(requirement)
  }
  matrix
}

///|
/// Construct a standard acceptance matrix for one target.
pub fn compatibility_standard_target_matrix(
  target : CompatibilityTarget,
) -> CompatibilityMatrix {
  let matrix = compatibility_matrix_for_target(target, [])
  matrix.add_requirement(compatibility_minimum_bitrate("STD-001", 125))
  matrix.add_requirement(compatibility_minimum_payload("STD-002", 8))
  matrix.add_requirement(compatibility_minimum_identifier_bits("STD-003", 11))
  matrix
}

///|
/// Describe the exact resources available to a target.
pub fn compatibility_inventory(target : CompatibilityTarget) -> String {
  let builder = StringBuilder()
  builder.write_string("name=")
  builder.write_string(target.name)
  builder.write_string("\nprotocol=")
  builder.write_string(compatibility_protocol_text(target.protocol))
  builder.write_string("\nnominal_bitrate=")
  builder.write_string(target.nominal_bitrate.to_string())
  builder.write_string("\ndata_bitrate=")
  builder.write_string(target.data_bitrate.to_string())
  builder.write_string("\nmax_payload=")
  builder.write_string(target.max_payload.to_string())
  builder.write_string("\nidentifier_bits=")
  builder.write_string(target.identifier_bits.to_string())
  builder.write_string("\nnode_capacity=")
  builder.write_string(target.node_capacity.to_string())
  builder.write_string("\nfeatures=")
  builder.write_string(target.feature_csv())
  builder.write_string("\nfingerprint=")
  builder.write_string(compatibility_target_fingerprint(target).to_string())
  builder.to_string()
}