// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

// The bridge between a description and the wire.
//
// A `@moonmodel.Type` says what a value is in the smallest set of words every
// projection recognises; this format says what a value is with a type ID from a
// fixed table. The mapping is total in one direction and partial in the other,
// and the partial half is where a description this format cannot carry is
// refused instead of guessed at.

///|
/// Every type ID, by the name it is written under.
///
/// A `Raw` description names a wire type in a form no projection reads inside,
/// which is why the table is a list rather than a chain of matches: a name and
/// its type ID are data, and the two directions read from the same list.
let names : Array[(String, TypeId)] = [
  ("unknown", Unknown),
  ("bool", Bool),
  ("int8", Int8),
  ("int16", Int16),
  ("int32", Int32),
  ("var_int32", VarInt32),
  ("int64", Int64),
  ("var_int64", VarInt64),
  ("tagged_int64", TaggedInt64),
  ("uint8", UInt8),
  ("uint16", UInt16),
  ("uint32", UInt32),
  ("var_uint32", VarUInt32),
  ("uint64", UInt64),
  ("var_uint64", VarUInt64),
  ("tagged_uint64", TaggedUInt64),
  ("float8", Float8),
  ("float16", Float16),
  ("bfloat16", BFloat16),
  ("float32", Float32),
  ("float64", Float64),
  ("string", String),
  ("list", List),
  ("set", Set),
  ("map", Map),
  ("enum", Enum),
  ("named_enum", NamedEnum),
  ("struct", Struct),
  ("compatible_struct", CompatibleStruct),
  ("named_struct", NamedStruct),
  ("named_compatible_struct", NamedCompatibleStruct),
  ("ext", Ext),
  ("named_ext", NamedExt),
  ("union", Union),
  ("typed_union", TypedUnion),
  ("named_union", NamedUnion),
  ("none", NoneType),
  ("duration", Duration),
  ("timestamp", Timestamp),
  ("date", Date),
  ("decimal", Decimal),
  ("binary", Binary),
  ("array", Array),
  ("bool_array", BoolArray),
  ("int8_array", Int8Array),
  ("int16_array", Int16Array),
  ("int32_array", Int32Array),
  ("int64_array", Int64Array),
  ("uint8_array", UInt8Array),
  ("uint16_array", UInt16Array),
  ("uint32_array", UInt32Array),
  ("uint64_array", UInt64Array),
  ("float8_array", Float8Array),
  ("float16_array", Float16Array),
  ("bfloat16_array", BFloat16Array),
  ("float32_array", Float32Array),
  ("float64_array", Float64Array),
]

///|
/// The name a type ID is written under, in the form a `Raw` description carries.
fn name_of(ty : TypeId) -> String {
  for (name, one) in names {
    if one == ty {
      return name
    }
  }
  "unknown"
}

///|
/// The type ID a name stands for, when the table has one.
fn ty_of_name(name : String) -> TypeId? {
  for (one, ty) in names {
    if one == name {
      return Some(ty)
    }
  }
  None
}

///|
/// The type ID a kind goes out as.
///
/// The widths are the compact ones — a field that is `Int` goes out as a
/// `VARINT32` and a `Big` as a `VARINT64` — because that is what every peer's
/// own mapping table chooses for the same word, and a field only interoperates
/// when the two sides agree on the type ID as well as on the value.
///
/// A kind the format cannot carry — `Any`, which has no type ID at all — is
/// refused by name.
pub fn TypeId::of_kind(kind : @moonmodel.Type) -> TypeId raise {
  match kind {
    Bool => TypeId::Bool
    Int => VarInt32
    Big => VarInt64
    Real => Float64
    Text => String
    Bytes => Binary
    Stamp => Timestamp
    List(_) => List
    Dict(_) => Map
    Ref(_) => Struct
    Nest(_) => Struct
    Any => raise Refused::Unsupported(kind="model kind any")
    Raw(raw) =>
      match raw {
        Object(fields) =>
          match fields.get("fory") {
            Some(String(name)) =>
              match ty_of_name(name) {
                Some(ty) => ty
                None => raise Refused::Unsupported(kind="model kind \{name}")
              }
            _ => raise Refused::Unsupported(kind="model kind raw")
          }
        _ => raise Refused::Unsupported(kind="model kind raw")
      }
  }
}

///|
/// The kind a type ID stands for, which is how a wire type is described to a
/// projection that has never heard of this format.
///
/// The widths collapse — every signed 32-bit type is `Int`, every 64-bit one a
/// `Big` — because the description is not the place a width is decided. What
/// the vocabulary has no word for is a `Raw` naming the wire type, which is
/// what `Raw` is for; nothing reads inside it, and the wire record beside the
/// description still holds the type ID itself.
pub fn TypeId::kind(self : TypeId) -> @moonmodel.Type {
  match self {
    Bool => @moonmodel.Bool
    Int8 | Int16 | Int32 | VarInt32 | UInt8 | UInt16 | UInt32 | VarUInt32 =>
      @moonmodel.Int
    Int64 | VarInt64 | TaggedInt64 | UInt64 | VarUInt64 | TaggedUInt64 =>
      @moonmodel.Big
    Float8 | Float16 | BFloat16 | Float32 | Float64 => @moonmodel.Real
    String => @moonmodel.Text
    Binary => @moonmodel.Bytes
    Timestamp | Date => @moonmodel.Stamp
    List => @moonmodel.List(@moonmodel.Any)
    Set => @moonmodel.List(@moonmodel.Any)
    Map => @moonmodel.Dict(@moonmodel.Any)
    _ =>
      match self.element() {
        Some(one) => @moonmodel.List(one.kind())
        None => raw_kind(self)
      }
  }
}

///|
/// A description of a wire type the vocabulary has no word for: `{"fory": …}`.
fn raw_kind(ty : TypeId) -> @moonmodel.Type {
  @moonmodel.Raw(Json::object({ "fory": Json::string(name_of(ty)) }))
}