///|
/// Provider-owned E15 manifest text for the deterministic CIA402 runner.

///|
priv struct Cia402RuntimeAdapterRule {
  id : String
  source_label : String
  source_direction : String
  source_payload_digest : Int?
  classification : String
  global_step_index : Int
  protocol_label : String
  object_id : String
  from_role : String
  from_device_id : String
  to_role : String
  to_device_id : String
  terminal_label : String
  reason : String
} derive(ToJson)

///|
priv struct Cia402RuntimeAdapterMapping {
  id : String
  application_id : String
  asset_id : String
  protocol_id : String
  source_adapter_id : String
  source_session_namespace : String
  terminal_epoch_policy : String
  rules : Array[Cia402RuntimeAdapterRule]
} derive(ToJson)

///|
priv struct Cia402RuntimeAdapterManifest {
  schema : String
  version : Int
  manifest_id : String
  provider_id : String
  manifest_digest : String
  mappings : Array[Cia402RuntimeAdapterMapping]
} derive(ToJson)

///|
pub fn cia402_protocol_runtime_adapter_manifest_text() -> String {
  let source = cia402_runtime_adapter_manifest()
  { ..source, manifest_digest: cia402_runtime_adapter_manifest_digest(source) }
  .to_json()
  .stringify()
}

///|
fn cia402_runtime_adapter_manifest() -> Cia402RuntimeAdapterManifest {
  {
    schema: "isoproject.protocol-runtime-adapter-manifest.v1",
    version: 1,
    manifest_id: "cia402-deterministic-runtime-adapter",
    provider_id: "mokomoking2501/fieldbus_core/cia402_scenario_loop",
    manifest_digest: "",
    mappings: [
      {
        id: "axis-control-cia402-axis-authority",
        application_id: "axis-control",
        asset_id: "cia402-enable-fault-reset",
        protocol_id: "AxisAuthority",
        source_adapter_id: "cia402-deterministic-runner",
        source_session_namespace: "cia402",
        terminal_epoch_policy: "terminal-closes-axis-authority;reset-begins-next-recovery-epoch",
        rules: cia402_runtime_adapter_rules(),
      },
    ],
  }
}

///|
fn cia402_runtime_adapter_rules() -> Array[Cia402RuntimeAdapterRule] {
  [
    cia402_runtime_adapter_rule(
      "edge-0-enable",
      "cia402.controlword.write",
      "tx",
      Some(0x0006),
      "exchange",
      0,
      "axis.lifecycle.enable",
      "axis.lifecycle",
      "controller",
      "cell-0",
      "drive",
      "axis-1",
      "",
      "shutdown controlword realizes AxisAuthority enable",
    ),
    cia402_runtime_adapter_rule(
      "edge-1-status-ack",
      "cia402.statusword.read",
      "rx",
      Some(0x0021),
      "exchange",
      1,
      "axis.statusword.ack",
      "axis.statusword",
      "drive",
      "axis-1",
      "controller",
      "cell-0",
      "",
      "ready-to-switch-on status realizes AxisAuthority acknowledgement",
    ),
    cia402_runtime_adapter_rule(
      "config-mode",
      "cia402.mode.write",
      "tx",
      Some(0x08),
      "ignored-configuration",
      -1,
      "",
      "",
      "",
      "",
      "",
      "",
      "",
      "mode selection is profile configuration outside AxisAuthority",
    ),
    cia402_runtime_adapter_rule(
      "edge-2-target",
      "cia402.target_position.write",
      "tx",
      Some(607_000),
      "exchange",
      2,
      "axis.target_position.command",
      "axis.target_position",
      "controller",
      "cell-0",
      "drive",
      "axis-1",
      "",
      "target write realizes AxisAuthority command",
    ),
    cia402_runtime_adapter_rule(
      "edge-3-actual",
      "cia402.actual_position.read",
      "rx",
      Some(606_400),
      "exchange",
      3,
      "axis.actual_position.signal",
      "axis.actual_position",
      "drive",
      "axis-1",
      "controller",
      "cell-0",
      "",
      "actual position read realizes AxisAuthority signal",
    ),
    cia402_runtime_adapter_rule(
      "terminal-fault",
      "cia402.fault.detected",
      "rx",
      Some(1),
      "terminal",
      -1,
      "",
      "",
      "",
      "",
      "",
      "",
      "axis.lifecycle.fault",
      "fault closes the current AxisAuthority epoch",
    ),
    cia402_runtime_adapter_rule(
      "fault-drop-metadata",
      "drop",
      "fault",
      None,
      "ignored-fault-metadata",
      -1,
      "",
      "",
      "",
      "",
      "",
      "",
      "",
      "fault injection metadata is not a role action",
    ),
    cia402_runtime_adapter_rule(
      "recovery-reset-command",
      "cia402.controlword.write",
      "tx",
      Some(0x0080),
      "ignored-recovery",
      -1,
      "",
      "",
      "",
      "",
      "",
      "",
      "",
      "fault reset begins the next recovery epoch",
    ),
    cia402_runtime_adapter_rule(
      "recovery-reset-status",
      "cia402.statusword.read",
      "rx",
      Some(0x0040),
      "ignored-recovery",
      -1,
      "",
      "",
      "",
      "",
      "",
      "",
      "",
      "reset status belongs to the next recovery epoch",
    ),
  ]
}

///|
fn cia402_runtime_adapter_rule(
  id : String,
  source_label : String,
  source_direction : String,
  source_payload_digest : Int?,
  classification : String,
  global_step_index : Int,
  protocol_label : String,
  object_id : String,
  from_role : String,
  from_device_id : String,
  to_role : String,
  to_device_id : String,
  terminal_label : String,
  reason : String,
) -> Cia402RuntimeAdapterRule {
  {
    id,
    source_label,
    source_direction,
    source_payload_digest,
    classification,
    global_step_index,
    protocol_label,
    object_id,
    from_role,
    from_device_id,
    to_role,
    to_device_id,
    terminal_label,
    reason,
  }
}

///|
fn cia402_runtime_adapter_manifest_digest(
  manifest : Cia402RuntimeAdapterManifest,
) -> String {
  let text = cia402_runtime_adapter_manifest_canonical_text(manifest)
  let mut digest = @core.SimDigest::empty(seed=5_510).mix(text.length())
  for char in text {
    digest = digest.mix(char.to_int())
  }
  digest.state_digest.to_string() + ":" + digest.event_count.to_string()
}

///|
fn cia402_runtime_adapter_manifest_canonical_text(
  manifest : Cia402RuntimeAdapterManifest,
) -> String {
  let builder = StringBuilder::new()
  cia402_manifest_write_field(builder, "schema", manifest.schema)
  cia402_manifest_write_field(builder, "version", manifest.version.to_string())
  cia402_manifest_write_field(builder, "manifest_id", manifest.manifest_id)
  cia402_manifest_write_field(builder, "provider_id", manifest.provider_id)
  let mappings = manifest.mappings.copy()
  mappings.sort_by((left, right) => {
    cia402_manifest_mapping_key(left).compare(
      cia402_manifest_mapping_key(right),
    )
  })
  cia402_manifest_write_field(
    builder,
    "mapping_count",
    mappings.length().to_string(),
  )
  for mapping in mappings {
    cia402_manifest_write_field(builder, "mapping.id", mapping.id)
    cia402_manifest_write_field(
      builder,
      "mapping.application_id",
      mapping.application_id,
    )
    cia402_manifest_write_field(builder, "mapping.asset_id", mapping.asset_id)
    cia402_manifest_write_field(
      builder,
      "mapping.protocol_id",
      mapping.protocol_id,
    )
    cia402_manifest_write_field(
      builder,
      "mapping.source_adapter_id",
      mapping.source_adapter_id,
    )
    cia402_manifest_write_field(
      builder,
      "mapping.source_session_namespace",
      mapping.source_session_namespace,
    )
    cia402_manifest_write_field(
      builder,
      "mapping.terminal_epoch_policy",
      mapping.terminal_epoch_policy,
    )
    let rules = mapping.rules.copy()
    rules.sort_by((left, right) => {
      cia402_manifest_rule_key(left).compare(cia402_manifest_rule_key(right))
    })
    cia402_manifest_write_field(
      builder,
      "mapping.rule_count",
      rules.length().to_string(),
    )
    for rule in rules {
      cia402_manifest_write_field(builder, "rule.id", rule.id)
      cia402_manifest_write_field(
        builder,
        "rule.source_label",
        rule.source_label,
      )
      cia402_manifest_write_field(
        builder,
        "rule.source_direction",
        rule.source_direction,
      )
      cia402_manifest_write_field(
        builder,
        "rule.source_payload_digest",
        match rule.source_payload_digest {
          Some(value) => "some:" + value.to_string()
          None => "none"
        },
      )
      cia402_manifest_write_field(
        builder,
        "rule.classification",
        rule.classification,
      )
      cia402_manifest_write_field(
        builder,
        "rule.global_step_index",
        rule.global_step_index.to_string(),
      )
      cia402_manifest_write_field(
        builder,
        "rule.protocol_label",
        rule.protocol_label,
      )
      cia402_manifest_write_field(builder, "rule.object_id", rule.object_id)
      cia402_manifest_write_field(builder, "rule.from_role", rule.from_role)
      cia402_manifest_write_field(
        builder,
        "rule.from_device_id",
        rule.from_device_id,
      )
      cia402_manifest_write_field(builder, "rule.to_role", rule.to_role)
      cia402_manifest_write_field(
        builder,
        "rule.to_device_id",
        rule.to_device_id,
      )
      cia402_manifest_write_field(
        builder,
        "rule.terminal_label",
        rule.terminal_label,
      )
      cia402_manifest_write_field(builder, "rule.reason", rule.reason)
    }
  }
  builder.to_string()
}

///|
fn cia402_manifest_mapping_key(mapping : Cia402RuntimeAdapterMapping) -> String {
  cia402_manifest_key_part(mapping.id) +
  cia402_manifest_key_part(mapping.application_id) +
  cia402_manifest_key_part(mapping.asset_id) +
  cia402_manifest_key_part(mapping.protocol_id) +
  cia402_manifest_key_part(mapping.source_adapter_id)
}

///|
fn cia402_manifest_rule_key(rule : Cia402RuntimeAdapterRule) -> String {
  cia402_manifest_key_part(rule.id) +
  cia402_manifest_key_part(rule.source_label) +
  cia402_manifest_key_part(rule.source_direction) +
  cia402_manifest_key_part(
    match rule.source_payload_digest {
      Some(value) => "some:" + value.to_string()
      None => "none"
    },
  )
}

///|
fn cia402_manifest_write_field(
  builder : StringBuilder,
  name : String,
  value : String,
) -> Unit {
  builder.write_string(cia402_manifest_key_part(name))
  builder.write_string(cia402_manifest_key_part(value))
  builder.write_char('\n')
}

///|
fn cia402_manifest_key_part(value : String) -> String {
  value.length().to_string() + ":" + value
}