// Adapted from Morphir.IR.Type in finos/morphir-elm.
// Copyright 2020 Morgan Stanley. Copyright 2026 FINOS.
// SPDX-License-Identifier: Apache-2.0

///|
#derive(Eq, Debug)
pub(all) enum Type[A] {
  Variable(A, Name)
  Reference(A, FQName, Array[Type[A]])
  Tuple(A, Array[Type[A]])
  Record(A, Array[Field[A]])
  ExtensibleRecord(A, Name, Array[Field[A]])
  Function(A, Type[A], Type[A])
  Unit(A)
}

///|
#derive(Eq, Debug)
pub(all) struct Field[A] {
  name : Name
  tpe : Type[A]
}

///|
pub type Constructors[A] = Map[Name, Array[(Name, Type[A])]]

///|
#derive(Eq, Debug)
pub(all) enum TypeSpecification[A] {
  TypeAliasSpecification(Array[Name], Type[A])
  OpaqueTypeSpecification(Array[Name])
  CustomTypeSpecification(Array[Name], Constructors[A])
  DerivedTypeSpecification(Array[Name], DerivedTypeDetails[A])
}

///|
#derive(Eq, Debug)
pub(all) struct DerivedTypeDetails[A] {
  base_type : Type[A]
  from_base_type : FQName
  to_base_type : FQName
}

///|
#derive(Eq, Debug)
pub(all) enum TypeDefinition[A] {
  TypeAliasDefinition(Array[Name], Type[A])
  CustomTypeDefinition(Array[Name], AccessControlled[Constructors[A]])
}

///|
/// Public constructors appear in the specification; private ones become opaque.
pub fn[A] TypeDefinition::to_specification(
  self : TypeDefinition[A],
) -> TypeSpecification[A] {
  match self {
    TypeAliasDefinition(params, body) => TypeAliasSpecification(params, body)
    CustomTypeDefinition(params, constructors) =>
      match constructors.access {
        Public => CustomTypeSpecification(params, constructors.value)
        Private => OpaqueTypeSpecification(params)
      }
  }
}

///|
pub fn[A, B] Type::map_attributes(self : Type[A], f : (A) -> B) -> Type[B] {
  match self {
    Variable(a, n) => Variable(f(a), n)
    Reference(a, n, args) =>
      Reference(f(a), n, args.map(t => t.map_attributes(f)))
    Tuple(a, items) => Tuple(f(a), items.map(t => t.map_attributes(f)))
    Record(a, fields) =>
      Record(
        f(a),
        fields.map(field => {
          name: field.name,
          tpe: field.tpe.map_attributes(f),
        }),
      )
    ExtensibleRecord(a, n, fields) =>
      ExtensibleRecord(
        f(a),
        n,
        fields.map(field => {
          name: field.name,
          tpe: field.tpe.map_attributes(f),
        }),
      )
    Function(a, parameter, result) =>
      Function(f(a), parameter.map_attributes(f), result.map_attributes(f))
    Unit(a) => Unit(f(a))
  }
}