///|
/// The contextual names of shapes in one or more services. A service `rename`
/// map gives a shape another name in the context of that service. A generator
/// applies its own case rules to the result.
pub struct ServiceNames {
  priv renames : Map[@smithy.ShapeId, String]
}

///|
/// The names in the context of one service. It raises `MissingService` or
/// `NotAService` for a bad service ID.
pub fn ServiceNames::new(
  model : @smithy.Model,
  service : @smithy.ShapeId,
) -> ServiceNames raise CodegenError {
  ServiceNames::for_services(model, [service])
}

///|
/// The names in the context of several services, such as the two services of a
/// protocol that one generated package holds. I merge the rename maps. I raise
/// `InvalidSettings` when the services give one shape two names, or when two
/// shapes outside the prelude get the same name without regard to case.
pub fn ServiceNames::for_services(
  model : @smithy.Model,
  services : Array[@smithy.ShapeId],
) -> ServiceNames raise CodegenError {
  let renames : Map[@smithy.ShapeId, String] = Map([])
  let shapes : Map[@smithy.ShapeId, Unit] = Map([])
  for service in services {
    guard model.shapes.get(service) is Some(shape) else {
      raise MissingService(service)
    }
    guard shape.body is Service(rename~, ..) else { raise NotAService(service) }
    let ids = rename.keys().collect()
    ids.sort_by((a, b) => a.to_string().lexical_compare(b.to_string()))
    for id in ids {
      let name = rename[id]
      match renames.get(id) {
        Some(old) if old != name => {
          let (first, second) = if old.lexical_compare(name) <= 0 {
            (old, name)
          } else {
            (name, old)
          }
          raise InvalidSettings(
            "I found two names for shape `\{id}` in the services: `\{first}` and `\{second}`.",
          )
        }
        _ => renames[id] = name
      }
    }
    for id in service_closure(model, service) {
      shapes[id] = ()
    }
  }
  let names = { renames, }
  let ids = shapes.keys().collect()
  ids.sort_by((a, b) => a.to_string().lexical_compare(b.to_string()))
  let seen : Map[String, @smithy.ShapeId] = Map([])
  for id in ids {
    if id.member_name() is Some(_) || id.namespace() == "smithy.api" {
      continue
    }
    let key = names.shape_name(id).to_lower()
    match seen.get(key) {
      Some(first) =>
        raise InvalidSettings(
          "I found shapes `\{first}` and `\{id}` with the same name in the services. Add a rename to a service.",
        )
      None => seen[key] = id
    }
  }
  names
}

///|
/// The name of a shape in the context of the services: the rename, else the
/// name of the shape ID. A member keeps its member name.
pub fn ServiceNames::shape_name(
  self : ServiceNames,
  id : @smithy.ShapeId,
) -> String {
  match id.member_name() {
    Some(name) => name
    None =>
      match self.renames.get(id) {
        Some(name) => name
        None => id.name()
      }
  }
}