// A declared type on the wire.
//
// One codec, in `core`, because both ends are here: the card compiler writes
// this into a manifest and the dynamic-component host reads it, and a format
// written twice is a format that disagrees with itself the first time either
// end grows a case.
//
// It replaces a FLAT TABLE of interned entries, each referring to others by
// index, with the tree the type already is. The table was not an optimization
// that went wrong — it was a shape that could not say what it meant: a
// `ty-list` entry had an `elem` field and the writer never filled it, so every
// list, option and map in every manifest crossed as "a list of anything". The
// tree has nowhere to leave the element out.

///|
/// This type as JSON. `k` is the case; the rest of the object is that case's
/// payload, which is nothing for most of them.
pub fn Ty::to_json(self : Ty) -> Json {
  let k = (name : String) => Json::object({ "k": Json::string(name) })
  let named = (name : String, n : String) => {
    Json::object({ "k": Json::string(name), "name": Json::string(n) })
  }
  let wrapping = (name : String, inner : Ty) => {
    Json::object({ "k": Json::string(name), "of": inner.to_json() })
  }
  match self {
    TyBool => k("bool")
    TyInt(width~, signed~) =>
      Json::object({
        "k": Json::string("int"),
        "width": Json::number(width.to_double()),
        "signed": Json::boolean(signed),
      })
    TyFloat => k("float")
    TyText => k("text")
    TySet => k("set")
    TyTable => k("table")
    TyAny => k("any")
    TyList(e) => wrapping("list", e)
    TyOption(e) => wrapping("option", e)
    TyOMap(v) => wrapping("omap", v)
    TyTuple(ts) =>
      Json::object({
        "k": Json::string("tuple"),
        "items": Json::array(ts.map(t => t.to_json())),
      })
    TyRecord(n) => named("record", n)
    TyEnum(n) => named("enum", n)
    TyVariant(n) => named("variant", n)
    TyFlags(n, ms) =>
      Json::object({
        "k": Json::string("flags"),
        "name": Json::string(n),
        "members": Json::array(ms.map(Json::string)),
      })
    // "" is the bare `component` marker: a slot whose component the guest did
    // not name.
    TyComp(c) => named("comp", c.unwrap_or(""))
    TyCompProtocols(ids) =>
      Json::object({
        "k": Json::string("compProtocols"),
        "members": Json::array(ids.map(Json::string)),
      })
  }
}

///|
/// A declared type read back, or `TyAny` for anything this cannot make sense
/// of.
///
/// `TyAny` rather than a failure, and the same answer the flat table gave for
/// an unknown kind: a manifest crossed a trust boundary, and the honest reading
/// of a type a host does not recognize is "cannot say" — which is exactly what
/// `any` means everywhere else here.
pub fn Ty::of_json(j : Json) -> Ty {
  guard j is Object(m) else { return TyAny }
  guard m.get("k") is Some(String(kind)) else { return TyAny }
  let name = match m.get("name") {
    Some(String(s)) => s
    _ => ""
  }
  let inner = () => {
    match m.get("of") {
      Some(of) => Ty::of_json(of)
      None => TyAny
    }
  }
  let members = () => {
    let out : Array[String] = []
    if m.get("members") is Some(Array(items)) {
      for item in items {
        if item is String(s) {
          out.push(s)
        }
      }
    }
    out
  }
  match kind {
    "bool" => TyBool
    "int" => {
      let width = match m.get("width") {
        Some(Number(n, ..)) => n.to_int()
        _ => 32
      }
      TyInt(width~, signed=!(m.get("signed") is Some(False)))
    }
    "float" => TyFloat
    "text" => TyText
    "set" => TySet
    "table" => TyTable
    "list" => TyList(inner())
    "option" => TyOption(inner())
    "omap" => TyOMap(inner())
    "tuple" => {
      let out : Array[Ty] = []
      if m.get("items") is Some(Array(items)) {
        for item in items {
          out.push(Ty::of_json(item))
        }
      }
      TyTuple(out)
    }
    "record" => TyRecord(name)
    "enum" => TyEnum(name)
    "variant" => TyVariant(name)
    "flags" => TyFlags(name, members())
    "comp" => TyComp(if name == "" { None } else { Some(name) })
    "compProtocols" => TyCompProtocols(members())
    _ => TyAny
  }
}