///|
fn ion_field(value : @ion.IonValue, name : String) -> @ion.IonValue {
  match value.kind() {
    Struct(fields) => {
      for field in fields {
        if field.name().text() == Some(name) {
          return field.value()
        }
      }
      @ion.IonValue::untyped_null()
    }
    _ => @ion.IonValue::untyped_null()
  }
}

///|
fn ion_name(value : @ion.IonValue) -> String {
  match ion_field(value, "name").kind() {
    String(name) => name
    _ => ""
  }
}

///|
fn source_names(
  metadata : Array[@data.NodeMetadata],
  entry : String,
) -> (String, Array[String])? {
  for item in metadata {
    if item.node_id == "value:" + entry {
      let name = ion_name(ion_field(item.data, "declaration"))
      let parameters = match ion_field(item.data, "parameters").kind() {
        List(values) => values.map(ion_name)
        _ => []
      }
      if name != "" {
        return Some((name, parameters))
      }
    }
  }
  None
}

///|
/// Source spelling labels every declaration; canonical IR identities stay intact.
fn source_ui(
  fields : Array[(String, Json)],
  metadata : Array[@data.NodeMetadata],
) -> Unit {
  fn inputs(
    rows : Array[Json],
    parameters : Array[String],
    key : String,
  ) -> Json {
    Json::array(
      rows.mapi((index, input) => {
        if input is Object(row) {
          let row = row.copy()
          row[key] = @wire.text(
            parameters.get(index).unwrap_or(node_text(input, key)),
          )
          Json::object(row)
        } else {
          input
        }
      }),
    )
  }
  for i, (key, value) in fields {
    if key == "nodes" && value is Array(nodes) {
      fields[i] = (
        key,
        Json::array(
          nodes.map(node => {
            if node is Object(data) &&
              source_names(metadata, node_text(node, "id"))
              is Some((name, parameters)) {
              let data = data.copy()
              let original = node_text(node, "label")
              data["label"] = @wire.text(name)
              data["signature"] = @wire.text(
                node_text(node, "signature").replace_all(old=original, new=name),
              )
              if data.get("inputs") is Some(Array(rows)) {
                data["inputs"] = inputs(rows, parameters, "name")
              }
              Json::object(data)
            } else {
              node
            }
          }),
        ),
      )
    } else if key == "typedEvaluations" &&
      value is Object(profiles) &&
      profiles.get("entries") is Some(Object(entries)) {
      let entries = entries.copy()
      for entry, value in entries {
        if value is Object(profile) &&
          profile.get("inputs") is Some(Array(rows)) &&
          source_names(metadata, entry) is Some((_, parameters)) {
          let profile = profile.copy()
          profile["inputs"] = inputs(rows, parameters, "label")
          entries[entry] = Json::object(profile)
        }
      }
      fields[i] = (key, @wire.obj([("entries", Json::object(entries))]))
    }
  }
}