///|
fn representation_json(value : Representation) -> Json {
  match value {
    Text => { "type": "text" }
    Decimal => { "type": "decimal" }
    Integer => { "type": "integer" }
    Period => { "type": "period" }
    Coded(codes) => {
      let values : Array[Json] = codes
        .entries()
        .map(pair => { "id": pair.0, "label": pair.1 })
      { "type": "coded", "id": codes.id, "codes": values }
    }
  }
}

///|
pub fn Structure::to_profile_json(self : Structure) -> String raise SdmxError {
  let components : Array[Json] = self
    .components()
    .map(c => {
      let (role, attachment) = match c.role {
        Dimension => ("dimension", [])
        TimeDimension => ("time", [])
        Measure => ("measure", [])
        Attribute(keys) => ("attribute", keys)
      }
      {
        "id": c.id,
        "name": c.name,
        "role": role,
        "attachment": attachment.map(Json::string),
        "required": Json::boolean(c.required),
        "representation": representation_json(c.representation),
      }
    })
  canonical_json(
    {
      "profile": "moonsdmx.structure/1",
      "reference": self.reference.format(),
      "components": components,
    },
    self.limits,
  )
}

///|
fn read_representation(
  value : Json,
  limits : Limits,
) -> Representation raise SdmxError {
  let object = j_object(value, "representation")
  j_known(object, ["type", "id", "codes"], "representation")
  let tag = j_string(j_required(object, "type"), "representation.type")
  match tag {
    "text" => Text
    "decimal" => Decimal
    "integer" => Integer
    "period" => Period
    "coded" => {
      let values = j_array(j_required(object, "codes"), "codes")
      if values.length() > limits.max_codes {
        raise Limit(resource="codes", maximum=limits.max_codes)
      }
      let entries : Array[(String, String)] = values.map(v => {
        let entry = j_object(v, "code")
        j_known(entry, ["id", "label"], "code")
        (
          j_string(j_required(entry, "id"), "code.id"),
          j_optional_string(entry, "label", ""),
        )
      })
      Coded(
        CodeList::new(
          j_string(j_required(object, "id"), "codelist.id"),
          entries,
          limits~,
        ),
      )
    }
    _ => raise Unsupported(feature="representation: \{tag}")
  }
}

///|
pub fn Structure::from_profile_json(
  text : String,
  limits~ : Limits = Limits::standard(),
) -> Structure raise SdmxError {
  let object = j_object(parse_bounded_json(text, limits), "structure profile")
  j_known(object, ["profile", "reference", "components"], "structure profile")
  if j_string(j_required(object, "profile"), "profile") !=
    "moonsdmx.structure/1" {
    raise Unsupported(feature="structure profile version")
  }
  let reference = StructureRef::parse(
    j_string(j_required(object, "reference"), "reference"),
  )
  let values = j_array(j_required(object, "components"), "components")
  if values.length() > limits.max_columns {
    raise Limit(resource="components", maximum=limits.max_columns)
  }
  let components = values.map(value => {
    let object = j_object(value, "component")
    j_known(
      object,
      ["id", "name", "role", "attachment", "required", "representation"],
      "component",
    )
    let tag = j_string(j_required(object, "role"), "role")
    let attachment = match object.get("attachment") {
      Some(value) => j_strings(value, "attachment")
      None => []
    }
    if tag != "attribute" && !attachment.is_empty() {
      raise Invalid(
        path="attachment",
        reason="only attributes have attachments",
      )
    }
    let role = match tag {
      "dimension" => Dimension
      "time" => TimeDimension
      "measure" => Measure
      "attribute" => Attribute(attachment)
      _ => raise Unsupported(feature="component role: \{tag}")
    }
    Component::new(
      j_string(j_required(object, "id"), "id"),
      role,
      read_representation(j_required(object, "representation"), limits),
      name=j_optional_string(object, "name", ""),
      required=j_optional_bool(object, "required", false),
    )
  })
  Structure::new(reference, components, limits~)
}