///|
/// A preparation refusal. Each case names the shape whose declaration is refused.
pub(all) suberror ContractError {
  UnknownProfile(ProfileId)
  UnknownBundle(BundleId)
  InvalidTarget(@smithy.ShapeId, String)
  InvalidDeclaration(@smithy.ShapeId, String)
  UnsupportedDeclaration(@smithy.ShapeId, String)
  ConflictingDeclaration(@smithy.ShapeId, String)
  MissingAdapter(@smithy.ShapeId, AdapterId)
  PreparationLimit(@smithy.ShapeId, String)
} derive(Debug)

///|
pub fn ContractError::message(self : ContractError) -> String {
  match self {
    UnknownProfile(id) =>
      "I expected a declared serialization profile, but found no profile \{id}."
    UnknownBundle(id) =>
      "I expected a declared document bundle, but found no bundle \{id}."
    InvalidTarget(id, message)
    | InvalidDeclaration(id, message)
    | UnsupportedDeclaration(id, message)
    | ConflictingDeclaration(id, message)
    | PreparationLimit(id, message) => id.to_string() + ": " + message
    MissingAdapter(id, adapter) =>
      id.to_string() +
      ": I expected the host to register scalar adapter \{adapter}, but it is not registered."
  }
}

///|
/// The code of a refusal, used in structured diagnostics.
pub fn ContractError::code(self : ContractError) -> String {
  match self {
    UnknownProfile(_) => "UnknownProfile"
    UnknownBundle(_) => "UnknownBundle"
    InvalidTarget(_, _) => "InvalidTarget"
    InvalidDeclaration(_, _) => "InvalidDeclaration"
    UnsupportedDeclaration(_, _) => "UnsupportedDeclaration"
    ConflictingDeclaration(_, _) => "ConflictingDeclaration"
    MissingAdapter(_, _) => "MissingAdapter"
    PreparationLimit(_, _) => "PreparationLimit"
  }
}

///|
/// What a host adapter declares: the JSON kinds that it reads, the logical scalar kind that it produces
/// and whether it orders dictionary keys.
pub struct AdapterSignature {
  id : AdapterId
  inputs : Array[JsonKind]
  output : JsonKind
  orders_keys : Bool
} derive(Eq, Debug)

///|
pub fn AdapterSignature::new(
  id : AdapterId,
  inputs~ : Array[JsonKind],
  output~ : JsonKind,
  orders_keys~ : Bool = false,
) -> AdapterSignature raise IdentityError {
  guard !inputs.is_empty() else {
    raise InvalidIdentity(
      "AdapterSignature", "I expected at least one input JSON kind.",
    )
  }
  guard output is (StringKind | NumberKind | BooleanKind) else {
    raise InvalidIdentity(
      "AdapterSignature", "I expected a string, number or boolean output kind.",
    )
  }
  { id, inputs: inputs.copy(), output, orders_keys, }
}

///|
pub(all) enum WriterMode {
  Canonical
  Expanded
} derive(Eq, Debug)

///|
pub(all) enum UnknownMemberPolicy {
  RejectUnknown
  IgnoreUnknown
} derive(Eq, Debug)

///|
pub(all) enum OptionalTail {
  OmitTail
  NullTail
} derive(Eq, Debug)

///|
/// One member of a structure, union, list or map, with its effective wire decisions.
pub struct MemberContract {
  /// The Smithy member name: the logical identity.
  name : String
  member_id : @smithy.ShapeId
  target : @smithy.ShapeId
  required : Bool
  /// The field name in object layouts.
  wire : WireName
  /// Reader-only field names.
  aliases : Array[WireName]
  /// The tag of a union member.
  tag : WireName
  adapter : AdapterId?
  empty_as_absent : Bool
  /// Written as null when absent; a null input means absent.
  null_when_absent : Bool
  /// The member's external union is read from the fields that no other member claims.
  flatten : Bool
} derive(Debug)

///|
pub(all) enum UnionLayout {
  External
  Internal(tag~ : WireName)
  Adjacent(tag~ : WireName, content~ : WireName)
  TagArray(tag_only_unit~ : Bool)
  /// The member index selected by each accepted JSON kind.
  Untagged(Array[(JsonKind, Int32)])
} derive(Debug)

///|
/// Fixed member positions and an optional final rest list, as member indexes.
pub struct TupleLayout {
  fixed : Array[Int32]
  rest : Int32?
  optional : OptionalTail?
} derive(Debug)

///|
pub(all) enum KeyOrder {
  LexicalKeys
  AdapterKeys
  PreservedKeys
} derive(Eq, Debug)

///|
pub struct DictionaryLayout {
  entries : Bool
  /// Readers also accept the other layout.
  accept_other : Bool
  key_adapter : AdapterId?
  order : KeyOrder
} derive(Debug)

///|
pub(all) enum FormBinding {
  /// The whole JSON value is the given member. -1 means the payload itself.
  Whole(Int32)
  Positional(fixed~ : Array[Int32], rest~ : Int32?)
} derive(Debug)

///|
pub struct ReaderGuard {
  kind : JsonKind
  adapter : AdapterId?
  field : WireName?
  absent : WireName?
} derive(Debug)

///|
/// A writer uses the form when every `empty` member is absent or empty and every `present` member is
/// present and not empty.
pub struct WriterGuard {
  empty : Array[Int32]
  present : Array[Int32]
} derive(Debug)

///|
/// A compact form. A union form selects `member` and binds fields of its payload; a structure form binds
/// fields of the structure itself.
pub struct Form {
  union_member : Int32?
  reader : ReaderGuard
  binding : FormBinding
  writer : WriterGuard?
} derive(Debug)

///|
pub(all) enum ShapeLayout {
  ObjectStructure
  TupleStructure(TupleLayout)
  UnionShape(UnionLayout)
  ListShape
  Dictionary(DictionaryLayout)
  Scalar(AdapterId?)
} derive(Debug)

///|
pub(all) enum ShapeKind {
  StructureKind
  UnionKind
  ListKind
  MapKind
  SimpleKind(@smithy.SimpleType)
  EnumKind
  IntEnumKind
} derive(Eq, Debug)

///|
pub struct ShapeContract {
  id : @smithy.ShapeId
  kind : ShapeKind
  /// Structure and union members in declaration order; the member of a list; the key and value of a map.
  members : Array[MemberContract]
  layout : ShapeLayout
  /// Reader-only alternative union layouts.
  accepts : Array[UnionLayout]
  /// A sparse list or map: a null element or value is kept as null.
  sparse : Bool
  forms : Array[Form]
} derive(Debug)

///|
pub fn ShapeContract::member_index(
  self : ShapeContract,
  name : String,
) -> Int32? {
  for i, m in self.members {
    if m.name == name {
      return Some(i)
    }
  }
  None
}

///|
/// The detached wire contract of one root under one profile. It holds no reference to the source model.
pub struct WireContract {
  root : @smithy.ShapeId
  profile : ProfileId?
  writer : WriterMode
  unknown_members : UnknownMemberPolicy
  /// The identity of the logical contract. Profiles of one model and root share it.
  fingerprint : String
  shapes : Map[@smithy.ShapeId, ShapeContract]
} derive(Debug)

///|
pub fn WireContract::shape(
  self : WireContract,
  id : @smithy.ShapeId,
) -> ShapeContract? {
  self.shapes.get(id)
}