///|
/// High-level STIX object family.
pub(all) enum ObjectKind {
  Sdo
  Sro
  Sco
  Meta
  Bundle
  Custom
} derive(Eq, Debug)

///|
/// Expected JSON shape of one property.
pub(all) enum ValueShape {
  String
  Boolean
  Integer
  NonNegativeInteger
  Timestamp
  Identifier
  IdentifierOf(String)
  StringList
  IdentifierList
  IdentifierListOf(String)
  OpenVocab(String)
  ClosedVocab(String)
  OpenVocabList(String)
  Hashes
  Dictionary
  ObjectList
  ExternalReferences
  KillChainPhases
  GranularMarkings
  Extensions
  Pattern
  HexBinary
  IPv4
  IPv6
  Mac
  Email
  Url
  Any
} derive(Eq, Debug)

///|
/// One named property in a type table.
pub(all) struct FieldSpec {
  name : String
  required : Bool
  shape : ValueShape
} derive(Eq, Debug)

///|
/// Per-type property table used by the validator.
pub(all) struct TypeSpec {
  type_name : String
  kind : ObjectKind
  fields : Array[FieldSpec]
  require_any : Array[String]
} derive(Eq, Debug)

///|
fn field(name : String, required : Bool, shape : ValueShape) -> FieldSpec {
  { name, required, shape, }
}

///|
fn spec(
  type_name : String,
  kind : ObjectKind,
  fields : Array[FieldSpec],
  require_any : Array[String],
) -> TypeSpec {
  { type_name, kind, fields, require_any, }
}

///|
fn common_sdo_fields() -> Array[FieldSpec] {
  [
    field("type", true, String),
    field("spec_version", false, String),
    field("id", true, Identifier),
    field("created_by_ref", false, IdentifierOf("identity")),
    field("created", true, Timestamp),
    field("modified", true, Timestamp),
    field("revoked", false, Boolean),
    field("labels", false, StringList),
    field("confidence", false, NonNegativeInteger),
    field("lang", false, String),
    field("external_references", false, ExternalReferences),
    field("object_marking_refs", false, IdentifierListOf("marking-definition")),
    field("granular_markings", false, GranularMarkings),
    field("defanged", false, Boolean),
    field("extensions", false, Extensions),
  ]
}

///|
fn common_sco_fields() -> Array[FieldSpec] {
  [
    field("type", true, String),
    field("spec_version", false, String),
    field("id", true, Identifier),
    field("object_marking_refs", false, IdentifierListOf("marking-definition")),
    field("granular_markings", false, GranularMarkings),
    field("defanged", false, Boolean),
    field("extensions", false, Extensions),
  ]
}

///|
fn common_sro_fields() -> Array[FieldSpec] {
  common_sdo_fields()
}

///|
fn merge_fields(
  base : Array[FieldSpec],
  extra : Array[FieldSpec],
) -> Array[FieldSpec] {
  let out : Array[FieldSpec] = []
  base.each(fn(item) { out.push(item) })
  extra.each(fn(item) { out.push(item) })
  out
}

///|
fn field_by_name(spec : TypeSpec, name : String) -> FieldSpec? {
  let mut found : FieldSpec? = None
  spec.fields.each(fn(item) { if item.name == name { found = Some(item) } })
  found
}

///|
pub fn lookup_type_spec(type_name : String) -> TypeSpec? {
  type_spec_map().get(type_name)
}

///|
fn type_spec_map() -> Map[String, TypeSpec] {
  let table : Map[String, TypeSpec] = Map([])
  sdo_specs().each(fn(item) { table.set(item.type_name, item) })
  sco_specs().each(fn(item) { table.set(item.type_name, item) })
  sro_meta_specs().each(fn(item) { table.set(item.type_name, item) })
  table
}

///|
pub fn known_type_names() -> Array[String] {
  let names : Array[String] = []
  type_spec_map().each(fn(key, _value) { names.push(key) })
  names
}

///|
pub fn object_kind_of(type_name : String) -> ObjectKind {
  match lookup_type_spec(type_name) {
    Some(spec) => spec.kind
    None => if type_name.has_prefix("x-") { Custom } else { Sdo }
  }
}