///|
/// Mirror the reference model transform before preparing the shared schema
/// index. Hoisted headers become optional strings and leave the original error
/// documents; the original caller's graph is never mutated.
fn deconflict_errors(
  model : @smithy.Model,
  service : @smithy.Shape,
) -> (@smithy.Model, Map[@smithy.ShapeId, Array[@smithy.ShapeId]]) raise OpenApiError {
  let shapes : Map[@smithy.ShapeId, @smithy.Shape] = Map([])
  for id, shape in model.shapes {
    shapes[id] = shape
  }
  let groups : Map[@smithy.ShapeId, Array[@smithy.ShapeId]] = Map([])
  let service_errors = match service.body {
    @smithy.Service(errors~, ..) => errors
    _ => []
  }
  for operation in service_operations(model, service) {
    guard operation.body is @smithy.Operation(input~, output~, errors~) else {
      continue
    }
    let by_status : Map[Int32, Array[@smithy.ShapeId]] = Map([])
    let seen : Set[@smithy.ShapeId] = Set([])
    for id in errors + service_errors {
      if seen.contains(id) {
        continue
      }
      seen.add(id)
      if model.shapes.get(id) is Some(error) {
        let code = error_code(error)
        let entries = by_status.get_or_init(code, () => [])
        entries.push(id)
      }
    }
    let replacement : Array[@smithy.ShapeId] = []
    for code, ids in by_status {
      if ids.length() == 1 {
        replacement.push(ids[0])
        continue
      }
      let synthetic = @smithy.ShapeId::parse(
        service.id.namespace() +
        "#" +
        operation.id.name() +
        code.to_string() +
        "Error",
      ).unwrap()
      let union_id = @smithy.ShapeId::parse(
        service.id.namespace() + "#" + synthetic.name() + "Content",
      ).unwrap()
      let headers : Map[String, @smithy.NodeValue] = Map([])
      let union_members : Array[@smithy.ShapeId] = []
      for id in ids {
        guard model.shapes.get(id) is Some(error) &&
          error.body is @smithy.Structure(members) else {
          continue
        }
        let retained : Array[@smithy.ShapeId] = []
        for field_id in members {
          guard model.shapes.get(field_id) is Some(field) else { continue }
          if field.trait_value("smithy.api#httpHeader") is Some(header) {
            let name = field_id.member_name().unwrap()
            if headers.get(name) is Some(previous) && previous != header {
              raise InvalidConfiguration(
                "Conflicting header when de-conflicting errors",
              )
            }
            headers[name] = header
          } else {
            retained.push(field_id)
          }
        }
        shapes[id] = { ..error, body: @smithy.Structure(retained), }
        let member_id = union_id.with_member(id.name()).unwrap()
        union_members.push(member_id)
        shapes[member_id] = {
          ..error,
          id: member_id,
          body: @smithy.Member(target=id),
          traits: Map([]),
          mixins: [],
          provenance: None,
        }
      }
      shapes[union_id] = {
        ..operation,
        id: union_id,
        body: @smithy.Union(union_members),
        traits: Map([]),
        mixins: [],
        provenance: None,
      }
      let payload = synthetic.with_member("errorUnion").unwrap()
      let synthetic_members = [payload]
      shapes[payload] = {
        ..operation,
        id: payload,
        body: @smithy.Member(target=union_id),
        traits: Map([]),
        mixins: [],
        provenance: None,
      }
      for name, header in headers {
        let id = synthetic.with_member(name).unwrap()
        synthetic_members.push(id)
        let traits : Map[@smithy.ShapeId, @smithy.NodeValue] = Map([])
        traits[@smithy.ShapeId::parse("smithy.api#httpHeader").unwrap()] = header
        shapes[id] = {
          ..operation,
          id,
          body: @smithy.Member(
            target=@smithy.ShapeId::parse("smithy.api#String").unwrap(),
          ),
          traits,
          mixins: [],
          provenance: None,
        }
      }
      let traits : Map[@smithy.ShapeId, @smithy.NodeValue] = Map([])
      traits[@smithy.ShapeId::parse("smithy.api#error").unwrap()] = @smithy.String(
        if code >= 400 && code < 500 {
          "client"
        } else {
          "server"
        },
      )
      traits[@smithy.ShapeId::parse("smithy.api#httpError").unwrap()] = @smithy.Number(
        @smithy.NodeNumber::parse(code.to_string()).unwrap(),
      )
      shapes[synthetic] = {
        ..operation,
        id: synthetic,
        body: @smithy.Structure(synthetic_members),
        traits,
        mixins: [],
        provenance: None,
      }
      groups[synthetic] = ids
      replacement.push(synthetic)
    }
    shapes[operation.id] = {
      ..operation,
      body: @smithy.Operation(input~, output~, errors=replacement),
    }
  }
  ({ ..model, shapes, }, groups)
}

///|
fn Context::response_phase(
  self : Context,
  subject : @smithy.Shape,
) -> ExamplePhase {
  if subject.trait_value("smithy.api#error") is Some(_) {
    FailedExample(self.error_groups.get(subject.id).unwrap_or([subject.id]))
  } else {
    ResponseExample
  }
}