///|
/// Return the Canonical ABI flat core value types for one component value.
///
/// `pointer_type` must be `I32` or `I64` and represents the selected canonical
/// memory. `None` means the type index is invalid, recursive, or does not name
/// a value type.
pub fn canonical_flat_types(
  ty : ValType,
  types : Array[TypeDef?],
  pointer_type : @types.ValueType,
) -> Array[@types.ValueType]? {
  match pointer_type {
    I32 | I64 => ()
    _ => return None
  }
  let visiting : Map[Int, Unit] = Map([])
  fn join_flat(a : @types.ValueType, b : @types.ValueType) -> @types.ValueType {
    if a == b {
      return a
    }
    match (a, b) {
      (I64, _) | (_, I64) | (F64, _) | (_, F64) => I64
      (F32, I32) | (I32, F32) => I32
      (I32, _) | (_, I32) => I32
      _ => I64
    }
  }

  fn flat_primitive(p : PrimValType) -> Array[@types.ValueType] {
    match p {
      Bool | S8 | U8 | S16 | U16 | S32 | U32 | Char => [I32]
      S64 | U64 => [I64]
      F32 => [F32]
      F64 => [F64]
      String | ErrorContext => [pointer_type, pointer_type]
    }
  }

  fn flatten(value_type : ValType) -> Array[@types.ValueType]? {
    match value_type {
      Prim(p) => Some(flat_primitive(p))
      TypeIdx(idx) => {
        if idx < 0 || idx >= types.length() || visiting.get(idx) is Some(_) {
          return None
        }
        visiting.set(idx, ())
        let result = match types[idx] {
          Some(DefValType(p)) => Some(flat_primitive(p))
          Some(Tuple(values)) => {
            let output : Array[@types.ValueType] = []
            for value in values {
              match flatten(value) {
                Some(flat) =>
                  for core_type in flat {
                    output.push(core_type)
                  }
                None => return None
              }
            }
            Some(output)
          }
          Some(Record(fields)) => {
            let output : Array[@types.ValueType] = []
            for field in fields {
              match flatten(field.ty) {
                Some(flat) =>
                  for core_type in flat {
                    output.push(core_type)
                  }
                None => return None
              }
            }
            Some(output)
          }
          Some(Variant(cases)) => {
            let payloads : Array[Array[@types.ValueType]] = []
            let mut max_length = 0
            for case in cases {
              let payload = match case.ty {
                Some(case_type) =>
                  match flatten(case_type) {
                    Some(flat) => flat
                    None => return None
                  }
                None => []
              }
              if payload.length() > max_length {
                max_length = payload.length()
              }
              payloads.push(payload)
            }
            let joined : Array[@types.ValueType] = []
            for index in 0.. Some(join_flat(previous, payload[index]))
                    None => Some(payload[index])
                  }
                }
              }
              match current {
                Some(core_type) => joined.push(core_type)
                None => ()
              }
            }
            Some([I32] + joined)
          }
          Some(Option(payload_type)) =>
            match flatten(payload_type) {
              Some(payload) => Some([I32] + payload)
              None => None
            }
          Some(Result(ok_type, err_type)) => {
            let ok_payload = match ok_type {
              Some(value) =>
                match flatten(value) {
                  Some(flat) => flat
                  None => return None
                }
              None => []
            }
            let err_payload = match err_type {
              Some(value) =>
                match flatten(value) {
                  Some(flat) => flat
                  None => return None
                }
              None => []
            }
            let max_length = if ok_payload.length() > err_payload.length() {
              ok_payload.length()
            } else {
              err_payload.length()
            }
            let joined : Array[@types.ValueType] = []
            for index in 0.. Some(join_flat(ok, err))
                (Some(ok), None) => Some(ok)
                (None, Some(err)) => Some(err)
                (None, None) => None
              }
              match current {
                Some(core_type) => joined.push(core_type)
                None => ()
              }
            }
            Some([I32] + joined)
          }
          Some(Flags(labels)) =>
            Some(if labels.length() <= 32 { [I32] } else { [I64] })
          Some(Enum(_))
          | Some(Own(_))
          | Some(Borrow(_))
          | Some(ResourceType(_, _, _, _))
          | Some(Stream(_))
          | Some(Future(_)) => Some([I32])
          Some(List(_)) => Some([pointer_type, pointer_type])
          _ => None
        }
        visiting.remove(idx)
        result
      }
    }
  }

  flatten(ty)
}

///|
/// Return the Canonical ABI core signature produced by `canon lower`.
///
/// More than sixteen flat parameters use one indirect pointer. A result with
/// more than one flat value appends an indirect result pointer to the core
/// parameters.
pub fn canonical_lower_core_func_type(
  func_type : FuncType,
  types : Array[TypeDef?],
  pointer_type : @types.ValueType,
) -> @types.FuncType? {
  let flat_params : Array[@types.ValueType] = []
  for param in func_type.params {
    match canonical_flat_types(param.ty, types, pointer_type) {
      Some(flat) =>
        for core_type in flat {
          flat_params.push(core_type)
        }
      None => return None
    }
  }
  let flat_results = match func_type.result {
    Some(result) =>
      match canonical_flat_types(result, types, pointer_type) {
        Some(flat) => flat
        None => return None
      }
    None => []
  }
  let params : Array[@types.ValueType] = if flat_params.length() > 16 {
    [pointer_type]
  } else {
    flat_params
  }
  let results : Array[@types.ValueType] = if flat_results.length() == 1 {
    flat_results
  } else if flat_results.length() == 0 {
    []
  } else {
    params.push(pointer_type)
    []
  }
  Some({ params, results, })
}

///|
/// Return the Canonical ABI core signature consumed by `canon lift`.
///
/// More than sixteen flat parameters use one indirect pointer. A result with
/// more than one flat value is returned as a pointer to the result area.
pub fn canonical_lift_core_func_type(
  func_type : FuncType,
  types : Array[TypeDef?],
  pointer_type : @types.ValueType,
) -> @types.FuncType? {
  let flat_params : Array[@types.ValueType] = []
  for param in func_type.params {
    match canonical_flat_types(param.ty, types, pointer_type) {
      Some(flat) =>
        for core_type in flat {
          flat_params.push(core_type)
        }
      None => return None
    }
  }
  let flat_results = match func_type.result {
    Some(result) =>
      match canonical_flat_types(result, types, pointer_type) {
        Some(flat) => flat
        None => return None
      }
    None => []
  }
  let params = if flat_params.length() > 16 {
    [pointer_type]
  } else {
    flat_params
  }
  let results = if flat_results.length() > 1 {
    [pointer_type]
  } else {
    flat_results
  }
  Some({ params, results, })
}