// Based on finos/morphir's v4 semantic model and schema.
// Copyright 2026 FINOS. SPDX-License-Identifier: Apache-2.0

///|
#derive(Eq, Debug)
pub(all) struct SourceLocation {
  start_line : Int32
  start_column : Int32
  end_line : Int32
  end_column : Int32
}

///|
#derive(Eq, Debug, Default)
pub(all) struct TypeAttributes {
  source : SourceLocation?
  constraints : Map[String, Data]
  extensions : Map[String, Data]
}

///|
/// The extension key of the inference marker.
pub const INFERENCE_EXTENSION : String = "morphir.inference"

///|
/// The value of `INFERENCE_EXTENSION` on a type variable that inference may solve to any type.
pub const UNKNOWN_MARKER : String = "unknown"

///|
/// Whether the attributes carry the unknown marker. A variable with the marker is not rigid: inference may solve
/// it to any type. An extensible record with the marker has an unknown row variable.
pub fn TypeAttributes::is_unknown(self : TypeAttributes) -> Bool {
  self.extensions.get(INFERENCE_EXTENSION) is Some(Text(UNKNOWN_MARKER))
}

///|
/// The attributes with the unknown marker added. The other extensions stay.
pub fn TypeAttributes::with_unknown(self : TypeAttributes) -> TypeAttributes {
  let extensions = self.extensions.copy()
  extensions[INFERENCE_EXTENSION] = Text(UNKNOWN_MARKER)
  { ..self, extensions, }
}

///|
/// Whether `name` is a morphir-elm let placeholder: `t-` and one or more digits, as in `t-8`. Classic IR cannot
/// carry the unknown marker, so on such a variable the name has the same meaning.
pub fn is_classic_placeholder_name(name : Name) -> Bool {
  match name.to_canonical() {
    [.. "t-", .. rest] if rest.length() > 0 =>
      rest.iter().all(c => c >= '0' && c <= '9')
    _ => false
  }
}

///|
#derive(Eq, Debug, Default)
pub(all) struct ValueAttributes {
  source : SourceLocation?
  inferred_type : Type?
  extensions : Map[String, Data]
}

///|
#derive(Eq, Debug)
pub(all) enum Type {
  Variable(TypeAttributes, Name)
  Reference(TypeAttributes, FQName, Array[Type])
  Tuple(TypeAttributes, Array[Type])
  /// Inclusive alternatives, retaining authored order and duplicates. Codecs require at least two entries.
  Union(TypeAttributes, Array[Type])
  Record(TypeAttributes, Map[Name, Type])
  ExtensibleRecord(TypeAttributes, Name, Map[Name, Type])
  Function(TypeAttributes, Type, Type)
  Unit(TypeAttributes)
}

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

///|
#derive(Eq, Debug)
pub(all) enum Annotation {
  Compact(FQName, String?)
  Structured(FQName, Array[AnnotationArgument])
}

///|
#derive(Eq, Debug)
pub(all) enum AnnotationArgument {
  Positional(Value)
  Named(Name, Value)
}

///|
#derive(Eq, Debug)
pub(all) struct TypeSpecification {
  annotations : Array[Annotation]
  body : TypeSpecificationBody
}

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

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

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

///|
#derive(Eq, Debug)
pub(all) enum HoleReason {
  UnresolvedReference(FQName)
  DeletedDuringRefactor(String)
  TypeMismatch(String, String)
}

///|
#derive(Eq, Debug)
pub(all) enum Incompleteness {
  Hole(HoleReason, Type?)
  Draft
}