// CAST to LIST, BAG, TUPLE and MAP (spec M4d2 ยง5.6, RFC 0104).

///|
fn to_collection(
  x : @value.Value,
  target : CastTarget,
) -> @value.Value raise OpFailure {
  match (x, target) {
    (List(_), List) | (Bag(_), Bag) | (Tuple(_), Tuple) => x
    // The stored order of the bag (RFC 0035).
    (Bag(b), List) =>
      @value.Value::from_list(Array::from_iter(b.items().iter()))
    (List(items), Bag) => @value.Value::from_bag(@value.Bag(items))
    _ => raise TypeFailure("CAST of \{kind_name(x)} to \{target_name(target)}")
  }
}

///|
/// MAP from a tuple (K a text type: field names are keys) or a map:
/// each key is cast to K and each value to V; a MISSING value drops its
/// entry; of two equal keys the last one wins (partiql-tests map.ion).
fn to_map(
  x : @value.Value,
  key : CastTarget,
  key_type : @value.MapType,
  value : CastTarget,
  value_type : @value.MapType,
  zone : ZoneOffset,
  today : () -> @value.Date raise OpFailure,
) -> @value.Value raise OpFailure {
  let entries : Array[(@value.Value, @value.Value)] = match x {
    Tuple(t) => {
      guard key is Text(_) else {
        raise TypeFailure("CAST of a tuple to a MAP whose key type is not text")
      }
      t
      .fields()
      .iter()
      .map(f => (@value.Value::from_string(f.0), f.1))
      .collect()
    }
    Map(m) => Array::from_iter(m.entries().iter())
    _ => raise TypeFailure("CAST of \{kind_name(x)} to MAP")
  }
  let out : Array[(@value.Value, @value.Value)] = []
  for entry in entries {
    let k = cast(entry.0, key, zone~, today~)
    let v = cast(entry.1, value, zone~, today~)
    if v is Missing {
      continue
    }
    match out.search_by(p => @value.eqg(p.0, k)) {
      Some(at) => out[at] = (k, v)
      None => out.push((k, v))
    }
  }
  let built = @value.MapValue(key_type~, value_type~, out) catch {
    _ => raise TypeFailure("a MAP key that is not valid for its type")
  }
  @value.Value::from_map(built)
}