///|
pub const STABLE_PROFILE : String = "moonbit-model-stable-v1"

///|
pub const EXPERIMENTAL_PROFILE : String = "moonbit-model-experimental-v1"

///|
/// Channels advance by promotion, never by adding another historical profile.
#derive(Eq, Debug)
pub(all) enum SourceChannel {
  Stable
  Experimental
}

///|
pub fn SourceChannel::profile(self : SourceChannel) -> String {
  match self {
    Stable => STABLE_PROFILE
    Experimental => EXPERIMENTAL_PROFILE
  }
}

///|
fn SourceChannel::require_scalar(
  self : SourceChannel,
  ty : LibraryTypeUse,
) -> Unit raise {
  if self == Stable &&
    ![LibraryScalar::Boolean, Text, SignedInt32].contains(ty.scalar) {
    library_error(
      "experimental_capability",
      "Select " +
      EXPERIMENTAL_PROFILE +
      " for " +
      ty.spelling +
      "; this capability has not been promoted to stable",
      Some(ty.source),
    )
  }
}

///|
/// Caller budgets may tighten, but cannot exceed, the profile's hard bounds.
#derive(Eq, Debug)
pub(all) struct LibraryLimits {
  max_bytes : Int32
  max_functions : Int32
  max_parameters : Int32
  max_lets : Int32
  max_expressions : Int32
  max_calls : Int32
  max_depth : Int32
  max_call_depth : Int32
}

///|
pub fn LibraryLimits::default() -> LibraryLimits {
  {
    max_bytes: 65536,
    max_functions: 64,
    max_parameters: 16,
    max_lets: 256,
    max_expressions: 4096,
    max_calls: 1024,
    max_depth: 64,
    max_call_depth: 32,
  }
}

///|
/// Entries are the canonical, sorted public values; an all-private unit has none.
pub(all) struct LibraryOutput {
  unit_id : String
  ir : @ir.IRFile
  entries : Array[@ir.FQName]
  metadata : Array[NodeMetadata]
  annotations : Array[@ion.SymbolToken]
  provenance : Array[@ion.IonValue]
  warnings : Array[@diagnostics.Warning]
}

///|
priv enum LibraryExpr {
  Boolean(Bool, @ir.SourceLocation)
  Number(String, String, LibraryScalar, @ir.SourceLocation)
  Floating(Double, String, LibraryScalar, @ir.SourceLocation)
  Text(String, String, @ir.SourceLocation)
  Constraint(LibraryExpr, LibraryTypeUse, @ir.SourceLocation)
  Identifier(String, @ir.SourceLocation)
  Negation(LibraryExpr, @ir.SourceLocation)
  NumericNegation(LibraryExpr, @ir.SourceLocation)
  StringLength(LibraryExpr, @ir.SourceLocation)
  StringSlice(LibraryExpr, LibraryExpr, LibraryExpr, @ir.SourceLocation)
  Binary(String, LibraryExpr, LibraryExpr, @ir.SourceLocation)
  Conditional(LibraryExpr, LibraryExpr, LibraryExpr, @ir.SourceLocation)
  Binding(
    String,
    @ir.SourceLocation,
    LibraryExpr,
    LibraryExpr,
    @ir.SourceLocation
  )
  Call(String, Array[LibraryExpr], @ir.SourceLocation)
  QualifiedCall(String, String, Array[LibraryExpr], @ir.SourceLocation)
  MethodCall(String, LibraryExpr, Array[LibraryExpr], @ir.SourceLocation)
}

///|
priv struct LibraryFunction {
  name : String
  doc : String?
  public : Bool
  parameters : Array[(String, @ir.SourceLocation)]
  input_types : Array[LibraryTypeUse]
  output_type : LibraryTypeUse
  effect : LibraryEffect
  body : LibraryExpr
  body_source : @ir.SourceLocation
  source : @ir.SourceLocation
}

///|
/// No parser AST escapes the adapter. Resolved trees hold identities, not spellings.
priv enum ResolvedExpr {
  Boolean(Bool, @ir.SourceLocation)
  Number(String, String, LibraryScalar, @ir.SourceLocation)
  Floating(Double, String, LibraryScalar, @ir.SourceLocation)
  Text(String, String, @ir.SourceLocation)
  Constraint(ResolvedExpr, LibraryTypeUse, @ir.SourceLocation)
  Variable(@ir.Name, @ir.SourceLocation)
  Negation(ResolvedExpr, @ir.SourceLocation)
  NumericNegation(ResolvedExpr, @ir.SourceLocation)
  StringLength(ResolvedExpr, @ir.SourceLocation)
  StringSlice(ResolvedExpr, ResolvedExpr, ResolvedExpr, @ir.SourceLocation)
  Binary(String, ResolvedExpr, ResolvedExpr, @ir.SourceLocation)
  Conditional(ResolvedExpr, ResolvedExpr, ResolvedExpr, @ir.SourceLocation)
  Binding(
    @ir.Name,
    String,
    @ir.SourceLocation,
    ResolvedExpr,
    ResolvedExpr,
    @ir.SourceLocation
  )
  Call(Int32, Array[ResolvedExpr], @ir.SourceLocation)
  SdkCall(Int32, Array[ResolvedExpr], @ir.SourceLocation)
}

///|
priv struct ResolvedFunction {
  declaration : LibrarySignature
  parameters : Array[@ir.Name]
  body : ResolvedExpr
  names : BinderNames
  types : Map[@ir.Name, LibraryTypeUse]
}

///|
/// Resolution drops the unresolved body; later phases retain only its signature.
priv struct LibrarySignature {
  name : String
  doc : String?
  public : Bool
  parameters : Array[(String, @ir.SourceLocation)]
  input_types : Array[LibraryTypeUse]
  output_type : LibraryTypeUse
  effect : LibraryEffect
  source : @ir.SourceLocation
  body_source : @ir.SourceLocation
}

///|
/// Constructed only after all bodies and the complete call graph pass checking.
priv struct CheckedLibrary {
  functions : Array[ResolvedFunction]
}

///|
fn library_error(
  code : String,
  detail : String,
  source : @ir.SourceLocation?,
) -> Unit raise FrontendError {
  raise Invalid({ code: "moonbit_frontend." + code, detail, source, })
}

///|
fn library_poll(cancelled : () -> Bool) -> Unit raise FrontendError {
  if cancelled() {
    library_error("cancelled", "Frontend cancelled", None)
  }
}

///|
pub extend LibraryLimits with Eq::{equal, not_equal}

///|
pub extend LibraryLimits with @debug.Debug::{to_repr}

///|
/// Only the named SDK model errors are admitted; this grants no host effects.
#derive(Eq)
priv enum LibraryEffect {
  Pure
  SdkError
  DecimalError
}

///|
fn LibraryEffect::identifier(self : LibraryEffect) -> String {
  match self {
    Pure => "pure"
    SdkError => "sdk.SdkError"
    DecimalError => "decimal.DecimalError"
  }
}

///|
/// A resolved source binding is selected by the host adapter. Its fields are
/// private so source text cannot register another native or SDK operation.
priv struct ResolvedSourceBinding {
  source_alias : String
  source_name : String
  source_package : String
  source_version : String
  source_tree : String
  source_signature : String
  canonical : String
  inputs : Array[LibraryScalar]
  output : LibraryScalar
  contract : SourceBindingContract
  effect : LibraryEffect
  origin : SourceBindingOrigin
  form : SourceBindingForm
}

///|
/// A String method takes its receiver as the first source input. The canonical
/// SDK value receives the same evaluated inputs in `canonical_order`.
priv enum SourceBindingForm {
  Qualified
  StringMethod(Array[Int32])
}

///|
fn ResolvedSourceBinding::authored(self : ResolvedSourceBinding) -> String {
  match self.form {
    Qualified => "@" + self.source_alias + "." + self.source_name
    StringMethod(_) => "String::" + self.source_name
  }
}

///|
fn ResolvedSourceBinding::canonical_order(
  self : ResolvedSourceBinding,
) -> Array[Int32] {
  match self.form {
    Qualified => Array::makei(self.inputs.length(), i => i)
    StringMethod(order) => order
  }
}

///|
priv enum SourceBindingOrigin {
  Classic
  NumericExtension
  DecimalExtension
}

///|
fn SourceBindingOrigin::identifier(self : SourceBindingOrigin) -> String {
  match self {
    Classic => "classic"
    NumericExtension => "morphir-sdk-moonbit-numeric-v1"
    DecimalExtension => "morphir-sdk-decimal-checked-v1"
  }
}

///|
priv enum SourceBindingContract {
  ExactBool
  DecimalExactConstruction
  DecimalArithmetic
  DecimalHalfEven
  DecimalComparison
  DecimalDivision
  DecimalFallback
  Binary64Sqrt
  Binary64Reference4Ulp
  ClassicFloatAbs
  ClassicIntegerAbs
  ClassicIntegerRounding(IntegerRoundingPolicy)
  RoundedIntegerToFloat64
  CheckedIntegerToFloat64
  CheckedFloat64ToInteger(IntegerConversionPolicy)
  Utf16Empty
  Utf16Substring
  Utf16Prefix
  Utf16Suffix
}

///|
priv enum IntegerRoundingPolicy {
  Round
  Floor
  Ceiling
  Truncate
}

///|
priv enum IntegerConversionPolicy {
  Exact
  Truncated
  Floor
  Ceiling
  Rounded
}

///|
/// Stable admits only promoted contracts; experimental admits every contract.
fn SourceBindingContract::is_stable(self : SourceBindingContract) -> Bool {
  self is (ExactBool | Utf16Empty | Utf16Substring | Utf16Prefix | Utf16Suffix)
}

///|
fn SourceBindingContract::identifier(self : SourceBindingContract) -> String {
  match self {
    ExactBool => "exact-bool"
    DecimalExactConstruction =>
      "decimal-exact-construction-min-exponent-minus32"
    DecimalArithmetic => "decimal-normalized-truncate-min-exponent-minus32"
    DecimalHalfEven => "decimal-half-even-integer"
    DecimalComparison => "decimal-exact-comparison"
    DecimalDivision => "decimal-divide-toward-zero-minus32-typed-failures-v1"
    DecimalFallback => "decimal-divide-explicit-fallback-v1"
    Binary64Sqrt => "binary64-sqrt-exact-reference-bits"
    Binary64Reference4Ulp => "binary64-reference-4ulp"
    ClassicFloatAbs => "exact-classic-abs-signed-zero"
    ClassicIntegerAbs => "exact-integer-abs"
    ClassicIntegerRounding(policy) =>
      match policy {
        Round => "nearest-integer-ties-positive-infinity"
        Floor => "floor-integer"
        Ceiling => "ceiling-integer"
        Truncate => "truncate-integer-toward-zero"
      }
    RoundedIntegerToFloat64 => "integer-to-binary64-rounded"
    CheckedIntegerToFloat64 => "integer-to-binary64-checked-exact"
    CheckedFloat64ToInteger(policy) =>
      match policy {
        Exact => "binary64-to-integer-checked-exact"
        Truncated => "binary64-to-integer-checked-truncated"
        Floor => "binary64-to-integer-checked-floor"
        Ceiling => "binary64-to-integer-checked-ceiling"
        Rounded => "binary64-to-integer-checked-rounded"
      }
    Utf16Empty => "utf16-empty"
    Utf16Substring => "utf16-code-unit-substring"
    Utf16Prefix => "utf16-code-unit-prefix"
    Utf16Suffix => "utf16-code-unit-suffix"
  }
}

///|
/// The pure frontend receives this frozen mapping from its adapter.
pub struct PinnedSourceBindings {
  priv entries : Array[ResolvedSourceBinding]
  priv types : Array[SourceSdkType]
}

///|
#derive(Eq)
priv enum SourceSdkType {
  Integer
  Error
  Decimal
}

///|
pub fn PinnedSourceBindings::empty() -> PinnedSourceBindings {
  { entries: [], types: [], }
}

///|
/// The adapter supplies a resolved identity. This finite constructor refuses
/// any package, alias or tree that is not the audited SDK call.
pub fn PinnedSourceBindings::sdk_basics_not(
  source_package : String,
  source_version : String,
  source_tree : String,
  source_alias : String,
) -> PinnedSourceBindings raise FrontendError {
  if source_package != "finos/morphir-sdk/basics" ||
    source_version != "0.1.0" ||
    source_tree != "9c83a100383570236b3d781362524008f76dd56d" ||
    source_alias != "basics" {
    library_error(
      "binding_identity",
      "Resolved source binding does not match the audited SDK package, tree and alias",
      None,
    )
  }
  {
    types: [],
    entries: [
      {
        source_alias,
        source_name: "not_",
        source_package,
        source_version,
        source_tree,
        source_signature: "Bool -> Bool",
        canonical: "morphir/SDK:basics#not",
        inputs: [Boolean],
        output: Boolean,
        contract: ExactBool,
        effect: Pure,
        origin: Classic,
        form: Qualified,
      },
    ],
  }
}

///|
/// Finite N4 additions to the audited resolver. No source text can extend it.
pub fn PinnedSourceBindings::sdk_numeric_math(
  source_package : String,
  source_version : String,
  source_tree : String,
  source_alias : String,
) -> PinnedSourceBindings raise FrontendError {
  let bindings = PinnedSourceBindings::sdk_basics_not(
    source_package, source_version, source_tree, source_alias,
  )
  for
    (name, canonical, inputs, policy) in [
      ("sqrt", "sqrt", [Floating64], SourceBindingContract::Binary64Sqrt),
      ("sin", "sin", [Floating64], Binary64Reference4Ulp),
      ("cos", "cos", [Floating64], Binary64Reference4Ulp),
      ("power_float", "power", [Floating64, Floating64], Binary64Reference4Ulp),
      ("abs_float", "abs", [Floating64], ClassicFloatAbs),
    ] {
    bindings.entries.push({
      source_alias,
      source_name: name,
      source_package,
      source_version,
      source_tree,
      source_signature: if inputs.length() == 2 {
        "(Double, Double) -> Double"
      } else {
        "Double -> Double"
      },
      canonical: "morphir/SDK:basics#" + canonical,
      inputs,
      output: Floating64,
      contract: policy,
      effect: Pure,
      origin: Classic,
      form: Qualified,
    })
  }
  bindings
}

///|
/// R3 preserves the R2 factory and adds only audited SDK Integer call contracts.
pub fn PinnedSourceBindings::sdk_numeric_conversions(
  source_package : String,
  source_version : String,
  source_tree : String,
  source_alias : String,
) -> PinnedSourceBindings raise FrontendError {
  let bindings = PinnedSourceBindings::sdk_numeric_math(
    source_package, source_version, source_tree, source_alias,
  )
  bindings.types.append([SourceSdkType::Integer, Error])
  for
    (name, policy) in [
      ("round", IntegerRoundingPolicy::Round),
      ("floor", Floor),
      ("ceiling", Ceiling),
      ("truncate", Truncate),
    ] {
    bindings.entries.push({
      source_alias,
      source_name: name,
      source_package,
      source_version,
      source_tree,
      source_signature: "Double -> @integer.Integer raise @sdk.SdkError",
      canonical: "morphir/SDK:basics#" + name,
      inputs: [Floating64],
      output: SdkInteger,
      contract: ClassicIntegerRounding(policy),
      effect: SdkError,
      origin: Classic,
      form: Qualified,
    })
  }
  for
    (name, canonical, contract, output) in [
      (
        "to_float",
        "basics#to-float",
        SourceBindingContract::RoundedIntegerToFloat64,
        LibraryScalar::Floating64,
      ),
      ("abs_int", "basics#abs", ClassicIntegerAbs, SdkInteger),
    ] {
    bindings.entries.push({
      source_alias,
      source_name: name,
      source_package,
      source_version,
      source_tree,
      source_signature: "@integer.Integer -> " + output.source_name(),
      canonical: "morphir/SDK:" + canonical,
      inputs: [SdkInteger],
      output,
      contract,
      effect: Pure,
      origin: Classic,
      form: Qualified,
    })
  }
  for
    (name, policy) in [
      ("from_float64_exact", IntegerConversionPolicy::Exact),
      ("from_float64_truncated", Truncated),
      ("from_float64_floor", Floor),
      ("from_float64_ceiling", Ceiling),
      ("from_float64_rounded", Rounded),
    ] {
    bindings.entries.push({
      source_alias: "int",
      source_name: name,
      source_package: "finos/morphir-sdk/int",
      source_version,
      source_tree,
      source_signature: "Double -> @integer.Integer?",
      canonical: "morphir/SDK:int#" + name.split("_").join("-"),
      inputs: [Floating64],
      output: OptionalSdkInteger,
      contract: CheckedFloat64ToInteger(policy),
      effect: Pure,
      origin: NumericExtension,
      form: Qualified,
    })
  }
  for
    (name, output, contract) in [
      (
        "to_float64_exact",
        LibraryScalar::OptionalDouble,
        SourceBindingContract::CheckedIntegerToFloat64,
      ),
      ("to_float64_rounded", Floating64, RoundedIntegerToFloat64),
    ] {
    bindings.entries.push({
      source_alias: "int",
      source_name: name,
      source_package: "finos/morphir-sdk/int",
      source_version,
      source_tree,
      source_signature: "@integer.Integer -> " + output.source_name(),
      canonical: "morphir/SDK:int#" + name.split("_").join("-"),
      inputs: [SdkInteger],
      output,
      contract,
      effect: Pure,
      origin: NumericExtension,
      form: Qualified,
    })
  }
  bindings
}

///|
pub const MOONBIT_CORE_PACKAGE : String = "moonbitlang/core/builtin"

///|
pub const MOONBIT_CORE_VERSION : String = "0.10.20+65efd5b93"

///|
pub const MOONBIT_CORE_TREE : String = "156637b5d9149a4d2980bf9522c060f829667e1b"

///|
/// The four pinned String predicates admitted by the reviewed specification.
/// Names alone grant nothing: only this finite table is consulted.
let string_predicates : Array[
  (String, Array[LibraryScalar], SourceBindingContract, String, Array[Int32]),
] = [
  ("is_empty", [Text], Utf16Empty, "is-empty", [0]),
  ("contains", [Text, Text], Utf16Substring, "contains", [1, 0]),
  ("has_prefix", [Text, Text], Utf16Prefix, "starts-with", [1, 0]),
  ("has_suffix", [Text, Text], Utf16Suffix, "ends-with", [1, 0]),
]

///|
/// Add the pinned MoonBit core String predicates. The adapter supplies the core
/// identity; another package, compiler or source tree is refused.
pub fn PinnedSourceBindings::with_string_predicates(
  self : PinnedSourceBindings,
  core_package : String,
  core_version : String,
  core_tree : String,
) -> PinnedSourceBindings raise FrontendError {
  if core_package != MOONBIT_CORE_PACKAGE ||
    core_version != MOONBIT_CORE_VERSION ||
    core_tree != MOONBIT_CORE_TREE {
    library_error(
      "binding_identity",
      "Resolved String methods do not match the audited MoonBit core package, compiler and tree",
      None,
    )
  }
  if self.find_method("is_empty") is Some(_) {
    library_error(
      "binding_identity",
      "Pinned String methods are already supplied",
      None,
    )
  }
  let entries = self.entries.copy()
  for (name, inputs, contract, canonical, order) in string_predicates {
    entries.push({
      source_alias: "String",
      source_name: name,
      source_package: core_package,
      source_version: core_version,
      source_tree: core_tree,
      source_signature: if inputs.length() == 1 {
        "(String) -> Bool"
      } else {
        "(String, StringView) -> Bool"
      },
      canonical: "morphir/SDK:string#" + canonical,
      inputs,
      output: Boolean,
      contract,
      effect: Pure,
      origin: Classic,
      form: StringMethod(order),
    })
  }
  { entries, types: self.types.copy(), }
}

///|
fn PinnedSourceBindings::find(
  self : PinnedSourceBindings,
  package_alias : String,
  source_name : String,
) -> Int32? {
  for i = 0; i < self.entries.length(); i = i + 1 {
    if self.entries[i].form is Qualified &&
      self.entries[i].source_alias == package_alias &&
      self.entries[i].source_name == source_name {
      return Some(i)
    }
  }
  None
}

///|
fn PinnedSourceBindings::find_method(
  self : PinnedSourceBindings,
  source_name : String,
) -> Int32? {
  for i = 0; i < self.entries.length(); i = i + 1 {
    if self.entries[i].form is StringMethod(_) &&
      self.entries[i].source_name == source_name {
      return Some(i)
    }
  }
  None
}

///|
fn is_string_predicate(name : String) -> Bool {
  string_predicates.any(p => p.0 == name)
}

///|
#derive(Eq)
priv enum LibraryScalar {
  Boolean
  SignedInt32
  SignedInt16
  SignedInt64
  UnsignedByte
  UnsignedInt16
  UnsignedInt32
  UnsignedInt64
  UnboundedInteger
  SdkInteger
  SdkDecimal
  OptionalSdkInteger
  OptionalDouble
  Floating32
  Floating64
  Text
}

///|
fn LibraryScalar::source_name(self : LibraryScalar) -> String {
  match self {
    Boolean => "Bool"
    SignedInt16 => "Int16"
    SignedInt32 => "Int32"
    SignedInt64 => "Int64"
    UnsignedByte => "Byte"
    UnsignedInt16 => "UInt16"
    UnsignedInt32 => "UInt32"
    UnsignedInt64 => "UInt64"
    UnboundedInteger => "BigInt"
    SdkInteger => "@integer.Integer"
    SdkDecimal => "@decimal.Decimal"
    OptionalSdkInteger => "@integer.Integer?"
    OptionalDouble => "Double?"
    Floating32 => "Float"
    Floating64 => "Double"
    Text => "String"
  }
}

///|
fn LibraryScalar::is_integer(self : LibraryScalar) -> Bool {
  match self {
    SignedInt16
    | SignedInt32
    | SignedInt64
    | UnsignedByte
    | UnsignedInt16
    | UnsignedInt32
    | UnsignedInt64
    | UnboundedInteger => true
    _ => false
  }
}

///|
fn LibraryScalar::is_floating(self : LibraryScalar) -> Bool {
  self == Floating32 || self == Floating64
}

///|
fn LibraryScalar::has_integer_boundary(self : LibraryScalar) -> Bool {
  self == UnboundedInteger || self == SdkInteger || self == OptionalSdkInteger
}

///|
fn LibraryScalar::is_signed(self : LibraryScalar) -> Bool {
  self == SignedInt16 ||
  self == SignedInt32 ||
  self == SignedInt64 ||
  self == UnboundedInteger
}

///|
fn LibraryScalar::bits(self : LibraryScalar) -> Int32 {
  match self {
    SignedInt16 | UnsignedInt16 => 16
    SignedInt32 | UnsignedInt32 => 32
    SignedInt64 | UnsignedInt64 => 64
    UnsignedByte => 8
    _ => 0
  }
}

///|
fn LibraryScalar::source_module(self : LibraryScalar) -> String {
  match self {
    SignedInt16 => "int16"
    SignedInt32 => "int32"
    SignedInt64 => "int64"
    UnsignedByte => "byte"
    UnsignedInt16 => "uint16"
    UnsignedInt32 => "uint32"
    UnsignedInt64 => "uint64"
    _ => ""
  }
}

///|
priv struct LibraryTypeUse {
  scalar : LibraryScalar
  spelling : String
  source : @ir.SourceLocation
  profile : String
}

///|
fn LibraryTypeUse::to_ir(
  self : LibraryTypeUse,
  metadata : Bool,
) -> @ir.Type raise {
  let attrs = @ir.TypeAttributes::default()
  if metadata {
    attrs.extensions[if self.scalar == Text {
      "moonbit-source-string-v1"
    } else {
      "moonbit-source-scalar-v1"
    }] = @ir.Data::Object(
      Map::from_array([
        ("authored", @ir.Data::Text(self.spelling)),
        ("coordinate_profile", @ir.Data::Text("moonbit-parser-position-v1")),
        ("start_line", @ir.Data::Number(self.source.start_line.to_string())),
        ("start_column", @ir.Data::Number(self.source.start_column.to_string())),
        ("end_line", @ir.Data::Number(self.source.end_line.to_string())),
        ("end_column", @ir.Data::Number(self.source.end_column.to_string())),
        (
          "representation",
          @ir.Data::Text(
            match self.scalar {
              Boolean => "bool"
              SignedInt32 => "signed-32"
              SignedInt16 => "signed-16"
              SignedInt64 => "signed-64"
              UnsignedByte => "unsigned-8"
              UnsignedInt16 => "unsigned-16"
              UnsignedInt32 => "unsigned-32"
              UnsignedInt64 => "unsigned-64"
              UnboundedInteger => "signed-arbitrary"
              SdkInteger => "sdk-integer"
              SdkDecimal => "sdk-decimal-coefficient-exponent"
              OptionalSdkInteger => "optional-sdk-integer"
              OptionalDouble => "optional-ieee-binary64"
              Floating32 => "ieee-binary32"
              Floating64 => "ieee-binary64"
              Text => "well-formed-utf16"
            },
          ),
        ),
      ]),
    )
    if self.profile == EXPERIMENTAL_PROFILE &&
      (self.scalar == SdkInteger || self.scalar == OptionalSdkInteger) {
      attrs.extensions["moonbit-source-sdk-type-v1"] = @ir.Data::Object(
        Map::from_array([
          ("source_alias", @ir.Data::Text("integer")),
          ("source_name", @ir.Data::Text("Integer")),
          ("source_package", @ir.Data::Text("finos/morphir-sdk/integer")),
          ("source_version", @ir.Data::Text("0.1.0")),
          (
            "source_tree",
            @ir.Data::Text("9c83a100383570236b3d781362524008f76dd56d"),
          ),
          ("profile", @ir.Data::Text(self.profile)),
        ]),
      )
    }
    if (
        self.scalar.is_integer() ||
        self.scalar.is_floating() ||
        self.scalar == SdkInteger
      ) &&
      self.profile == EXPERIMENTAL_PROFILE {
      let facts = Map::from_array([
        ("kind", @ir.Data::Text("type-use")),
        ("language", @ir.Data::Text("moonbit")),
        ("authored", @ir.Data::Text(self.spelling)),
        ("canonical_type", @ir.Data::Text(self.scalar.source_name())),
        (
          "signedness",
          @ir.Data::Text(
            if self.scalar.is_floating() {
              "signed-ieee"
            } else if self.scalar.is_signed() || self.scalar == SdkInteger {
              "signed"
            } else {
              "unsigned"
            },
          ),
        ),
        ("compiler", @ir.Data::Text("0.10.20+65efd5b93")),
        ("profile", @ir.Data::Text(self.profile)),
      ])
      if self.scalar.is_floating() {
        facts["width"] = @ir.Data::Number(
          if self.scalar == Floating32 {
            "32"
          } else {
            "64"
          },
        )
        facts["precision"] = @ir.Data::Number(
          if self.scalar == Floating32 {
            "24"
          } else {
            "53"
          },
        )
      } else if self.scalar.bits() == 0 {
        facts["precision"] = @ir.Data::Text("arbitrary")
      } else {
        facts["width"] = @ir.Data::Number(self.scalar.bits().to_string())
      }
      attrs.extensions["moonbit-source-number-v1"] = @ir.Data::Object(facts)
    }
  }
  if self.scalar == SdkDecimal && metadata {
    attrs.extensions["moonbit-source-decimal-v1"] = @ir.Data::Object(
      Map::from_array([
        ("canonical_type", @ir.Data::Text("@decimal.Decimal")),
        ("source_package", @ir.Data::Text("finos/morphir-sdk/decimal")),
        ("source_version", @ir.Data::Text("0.1.0")),
        ("source_tree", @ir.Data::Text(DECIMAL_SDK_TREE)),
        ("minimum_exponent", @ir.Data::Number("-32")),
        ("coefficient", @ir.Data::Text("arbitrary-precision-integer")),
        ("rounding", @ir.Data::Text("half-even-integer")),
        ("profile", @ir.Data::Text(self.profile)),
      ]),
    )
  }
  if self.scalar == OptionalSdkInteger || self.scalar == OptionalDouble {
    let item = LibraryTypeUse::{
      ..self,
      scalar: if self.scalar == OptionalSdkInteger {
        SdkInteger
      } else {
        Floating64
      },
      spelling: if self.scalar == OptionalSdkInteger {
        "@integer.Integer"
      } else {
        "Double"
      },
    }.to_ir(false)
    return @ir.Type::Reference(
      attrs,
      @ir.FQName::parse("morphir/SDK:maybe#maybe"),
      [item],
    )
  }
  @ir.Type::Reference(
    attrs,
    @ir.FQName::parse(
      match self.scalar {
        Boolean => "morphir/SDK:basics#bool"
        SignedInt32 => "morphir/SDK:int#int-32"
        SignedInt16 => "morphir/SDK:int#int-16"
        SignedInt64 => "morphir/SDK:int#int-64"
        UnsignedByte => "morphir/SDK:int#byte"
        UnsignedInt16 => "morphir/SDK:int#uint-16"
        UnsignedInt32 => "morphir/SDK:int#uint-32"
        UnsignedInt64 => "morphir/SDK:int#uint-64"
        SdkDecimal => "morphir/SDK:decimal#decimal"
        UnboundedInteger | SdkInteger => "morphir/SDK:basics#int"
        OptionalSdkInteger | OptionalDouble => abort("optional handled above")
        Floating32 => "morphir/moonbit-source:float32#float32"
        Floating64 => "morphir/SDK:basics#float"
        Text => "morphir/SDK:string#string"
      },
    ),
    [],
  )
}

///|
fn library_type(
  ty : @ast.Type?,
  loc : @location.Location,
  channel : SourceChannel,
) -> LibraryTypeUse raise {
  if ty is Some(@ast.Option(ty=inner, ..)) {
    let inner = library_type(Some(inner), inner.loc(), channel)
    let scalar = match inner.scalar {
      SdkInteger => LibraryScalar::OptionalSdkInteger
      Floating64 => OptionalDouble
      _ => {
        reject(
          "unsupported_type", "Only SDK Integer? and Double? are qualified optional boundaries",
          loc,
        )
        abort("unreachable")
      }
    }
    return {
      scalar,
      spelling: inner.spelling + "?",
      source: span(loc),
      profile: channel.profile(),
    }
  }
  guard ty is Some(@ast.Name(constr_id~, tys~, ..)) && tys.is_empty() else {
    reject(
      "unsupported_type", "An explicit supported scalar annotation is required",
      loc,
    )
    abort("unreachable")
  }
  let name = match constr_id.id {
    @ast.Ident(name~) => name
    @ast.Dot(pkg="integer", id="Integer") => "@integer.Integer"
    @ast.Dot(pkg="decimal", id="Decimal") => "@decimal.Decimal"
    _ => {
      reject("unsupported_type", "An audited source type is required", loc)
      abort("unreachable")
    }
  }
  let scalar = match name {
    "@integer.Integer" => LibraryScalar::SdkInteger
    "@decimal.Decimal" => LibraryScalar::SdkDecimal
    "Bool" => LibraryScalar::Boolean
    "String" => LibraryScalar::Text
    "Int32" | "Int" => LibraryScalar::SignedInt32
    "Int16" => LibraryScalar::SignedInt16
    "Int64" => LibraryScalar::SignedInt64
    "Byte" => LibraryScalar::UnsignedByte
    "UInt16" => LibraryScalar::UnsignedInt16
    "UInt32" | "UInt" => LibraryScalar::UnsignedInt32
    "UInt64" => LibraryScalar::UnsignedInt64
    "BigInt" => LibraryScalar::UnboundedInteger
    "Float" => LibraryScalar::Floating32
    "Double" => LibraryScalar::Floating64
    _ => {
      reject(
        "unsupported_type",
        "Type is outside the selected source profile: " + name,
        loc,
      )
      abort("unreachable")
    }
  }
  let result : LibraryTypeUse = {
    scalar,
    spelling: name,
    source: span(loc),
    profile: channel.profile(),
  }
  channel.require_scalar(result)
  result
}

///|
pub const DECIMAL_SDK_TREE : String = "642fd13ea74bc46c3f5344844c9cca4ffbae368a"

///|
pub fn PinnedSourceBindings::sdk_decimal(
  source_package : String,
  source_version : String,
  source_tree : String,
  source_alias : String,
) -> PinnedSourceBindings raise FrontendError {
  if source_package != "finos/morphir-sdk/decimal" ||
    source_version != "0.1.0" ||
    source_tree != DECIMAL_SDK_TREE ||
    source_alias != "decimal" {
    library_error(
      "binding_identity",
      "Expected the audited checked Decimal SDK identity",
      None,
    )
  }
  let bindings = PinnedSourceBindings::sdk_numeric_conversions(
    "finos/morphir-sdk/basics", "0.1.0", "9c83a100383570236b3d781362524008f76dd56d",
    "basics",
  )
  bindings.types.push(SourceSdkType::Decimal)
  for
    (name, inputs, output, contract, effect, origin) in [
      (
        "from_string_exact",
        [LibraryScalar::Text],
        LibraryScalar::SdkDecimal,
        SourceBindingContract::DecimalExactConstruction,
        LibraryEffect::DecimalError,
        SourceBindingOrigin::DecimalExtension,
      ),
      (
        "divide",
        [SdkDecimal, SdkDecimal],
        SdkDecimal,
        DecimalDivision,
        DecimalError,
        SourceBindingOrigin::DecimalExtension,
      ),
      (
        "divide_with_default",
        [SdkDecimal, SdkDecimal, SdkDecimal],
        SdkDecimal,
        DecimalFallback,
        Pure,
        SourceBindingOrigin::DecimalExtension,
      ),
      (
        "add",
        [SdkDecimal, SdkDecimal],
        SdkDecimal,
        DecimalArithmetic,
        Pure,
        Classic,
      ),
      (
        "sub",
        [SdkDecimal, SdkDecimal],
        SdkDecimal,
        DecimalArithmetic,
        Pure,
        Classic,
      ),
      (
        "mul",
        [SdkDecimal, SdkDecimal],
        SdkDecimal,
        DecimalArithmetic,
        Pure,
        Classic,
      ),
      ("round", [SdkDecimal], SdkDecimal, DecimalHalfEven, Pure, Classic),
      (
        "eq",
        [SdkDecimal, SdkDecimal],
        Boolean,
        DecimalComparison,
        Pure,
        Classic,
      ),
      (
        "neq",
        [SdkDecimal, SdkDecimal],
        Boolean,
        DecimalComparison,
        Pure,
        Classic,
      ),
      (
        "lt",
        [SdkDecimal, SdkDecimal],
        Boolean,
        DecimalComparison,
        Pure,
        Classic,
      ),
      (
        "lte",
        [SdkDecimal, SdkDecimal],
        Boolean,
        DecimalComparison,
        Pure,
        Classic,
      ),
      (
        "gt",
        [SdkDecimal, SdkDecimal],
        Boolean,
        DecimalComparison,
        Pure,
        Classic,
      ),
      (
        "gte",
        [SdkDecimal, SdkDecimal],
        Boolean,
        DecimalComparison,
        Pure,
        Classic,
      ),
    ] {
    bindings.entries.push({
      source_alias,
      source_name: name,
      source_package,
      source_version,
      source_tree,
      source_signature: "(" +
      inputs.map(LibraryScalar::source_name).join(", ") +
      ") -> " +
      output.source_name() +
      (if effect == DecimalError { " raise @decimal.DecimalError" } else { "" }),
      canonical: "morphir/SDK:" +
      (match origin {
        DecimalExtension => "decimal-checked-v1#"
        Classic => "decimal#"
        NumericExtension => abort("not a Decimal contract")
      }) +
      name.split("_").join("-"),
      inputs,
      output,
      contract,
      effect,
      origin,
      form: Qualified,
    })
  }
  bindings
}

///|
pub extend SourceChannel with Eq::{equal, not_equal}

///|
pub extend SourceChannel with @debug.Debug::{to_repr}