///|
/// Protocol test parameters are native Smithy values, not wire JSON values:
/// timestamps use epoch seconds, blobs are plain text, and optional nulls are allowed.
fn Model::protocol_test_events(self : Model) -> Array[ValidationEvent] {
  let events : Array[ValidationEvent] = []
  let id_refs = IdRefSelections::new(self)
  for trait_name in ["httpRequestTests", "httpResponseTests"] {
    let trait_id = "smithy.test#" + trait_name
    let ids : Map[String, Array[Shape]] = Map([])
    for _, owner in self.shapes {
      let event_id = if trait_name == "httpRequestTests" {
        "HttpRequestTestsInput"
      } else if owner.body is Structure(_) {
        "HttpResponseTestsError"
      } else {
        "HttpResponseTestsOutput"
      }
      let target = match (trait_name, owner.body) {
        ("httpRequestTests", Operation(input~, ..)) => Some(input)
        ("httpResponseTests", Operation(output~, ..)) => Some(output)
        ("httpResponseTests", Structure(_)) if owner.trait_value(
            "smithy.api#error",
          )
          is Some(_) => Some(owner.id)
        _ => None
      }
      for i, case_ in validation_array(owner.trait_value(trait_id)) {
        if validation_string(case_, "id") is Some(id) {
          let owners = ids.get(id).unwrap_or([])
          owners.push(owner)
          ids[id] = owners
        }
        guard target is Some(target) else { continue }
        let params = validation_field(case_, "params").unwrap_or(
          Object(Map([])),
        )
        let checks = [(target, params, "params")]
        if validation_string(case_, "vendorParamsShape").bind(ShapeId::parse)
          is Some(vendor_shape) {
          let vendor = validation_field(case_, "vendorParams").unwrap_or(
            Object(Map([])),
          )
          if vendor == Object(Map([])) {
            events.push(
              validation_event(
                event_id,
                "WARNING",
                Some(owner),
                "Protocol test case defined a `vendorParamsShape` but no `vendorParams`",
              ),
            )
          } else {
            checks.push((vendor_shape, vendor, "vendorParams"))
          }
        }
        for (target, value, field) in checks {
          if !(value is Object(_)) {
            events.push(
              validation_event(
                event_id,
                "ERROR",
                Some(owner),
                "I expected an object for " +
                trait_id +
                "." +
                i.to_string() +
                "." +
                field +
                ".",
              ),
            )
            continue
          }
          for
            event in self.node_validation_events(
              value,
              target,
              Some(owner),
              event_id,
              "ERROR",
              id_refs~,
              protocol_params=true,
            ) {
            let id = if event.id.has_prefix(event_id + ".UnknownMember.") {
              event_id + ".UnknownMember"
            } else {
              event.id
            }
            events.push({
              ..event,
              id,
              message: trait_id +
              "." +
              i.to_string() +
              "." +
              field +
              ": " +
              event.message,
            })
          }
        }
        let media = validation_string(case_, "bodyMediaType")
          .unwrap_or("")
          .split(";")
          .to_array()[0]
          .trim()
          .to_owned()
          .to_lower()
        if validation_string(case_, "body") is Some(body) &&
          body != "" &&
          (media.has_suffix("/json") || media.has_suffix("+json")) {
          let valid = try {
            ignore(@json.parse(body))
            true
          } catch {
            _ => false
          }
          if !valid {
            events.push(
              validation_event(
                event_id,
                "DANGER",
                Some(owner),
                "I found invalid JSON body content in protocol test `" +
                validation_string(case_, "id").unwrap_or("") +
                "`.",
              ),
            )
          }
        }
      }
    }
    for id, owners in ids {
      if owners.length() > 1 {
        let names = owners.map(s => "`" + s.id.to_string() + "`")
        names.sort()
        for owner in owners {
          events.push(
            validation_event(
              "UniqueProtocolTestCaseId",
              "ERROR",
              Some(owner),
              "Conflicting `" +
              trait_id +
              "` test case IDs found for ID `" +
              id +
              "`: " +
              names.join(", "),
            ),
          )
        }
      }
    }
  }
  events
}