///|
/// Compile the current typed library subset without host access or entry inference.
pub fn compile_library(
  input : Input,
  channel~ : SourceChannel = Stable,
  bindings~ : PinnedSourceBindings? = None,
  limits~ : LibraryLimits = LibraryLimits::default(),
  max_string_code_units~ : Int32 = 1048576,
  strict_naming~ : Bool = false,
  cancelled~ : () -> Bool = fn() { false },
  scope~ : @observe.Scope = @observe.Observer::noop().scope(),
) -> LibraryOutput raise {
  scope.observe(
    @observe.Frontend,
    fn(scope) raise {
      let bindings = match bindings {
        Some(bindings) => bindings
        None if channel == Stable =>
          PinnedSourceBindings::sdk_basics_not(
            "finos/morphir-sdk/basics", "0.1.0", "9c83a100383570236b3d781362524008f76dd56d",
            "basics",
          ).with_string_predicates(
            MOONBIT_CORE_PACKAGE,
            MOONBIT_CORE_VERSION,
            MOONBIT_CORE_TREE,
          )
        None =>
          PinnedSourceBindings::sdk_decimal(
            "finos/morphir-sdk/decimal",
            "0.1.0",
            DECIMAL_SDK_TREE,
            "decimal",
          ).with_string_predicates(
            MOONBIT_CORE_PACKAGE,
            MOONBIT_CORE_VERSION,
            MOONBIT_CORE_TREE,
          )
      }
      let output = library_compile_scoped(
        input,
        limits,
        cancelled,
        scope,
        channel,
        bindings~,
        string_limit=max_string_code_units,
      )
      check_naming_warnings(output.warnings, strict_naming)
      output
    },
    is_cancelled=frontend_cancelled,
  )
}

///|
fn library_compile_scoped(
  input : Input,
  limits : LibraryLimits,
  cancelled : () -> Bool,
  scope : @observe.Scope,
  channel : SourceChannel,
  string_limit~ : Int32 = 1048576,
  bindings~ : PinnedSourceBindings = PinnedSourceBindings::empty(),
) -> LibraryOutput raise {
  limits.validate()
  if string_limit < 0 || string_limit > 1048576 {
    library_error(
      "invalid_limits",
      "String limit may only tighten the 1048576 code-unit bound",
      None,
    )
  }

  if input.unit_id == "" ||
    input.unit_id.length() > 256 ||
    input.unit_id.to_array().any(c => c.to_int() < 32 || c.to_int() == 127) {
    library_error(
      "invalid_identity",
      "A bounded logical source identity is required",
      None,
    )
  }
  library_poll(cancelled)
  if @utf8.encode(input.source).length() > limits.max_bytes {
    library_error("byte_limit", "Source exceeds the byte budget", None)
  }
  let package_name = logical_path(input.package_name)
  let module_name = logical_path(input.module_name)
  if package_name.to_canonical() == "morphir/SDK" ||
    package_name.to_canonical() == "morphir/moonbit-source" {
    library_error(
      "invalid_identity",
      "Source libraries cannot own reserved SDK or source-runtime packages",
      None,
    )
  }
  let nodes = scope.observe(
    @observe.Parse,
    fn(_) raise { library_parse(input, cancelled) },
    is_cancelled=frontend_cancelled,
  )
  let declarations = scope.observe(
    @observe.ProfileCheck,
    fn(_) raise { library_adapt(nodes, limits, cancelled, channel) },
    is_cancelled=frontend_cancelled,
  )
  let resolved = scope.observe(
    @observe.Resolve,
    fn(_) raise { library_resolve(declarations, cancelled, bindings) },
    is_cancelled=frontend_cancelled,
  )
  let checked = scope.observe(
    @observe.ProfileCheck,
    fn(_) raise {
      library_check(resolved, limits, cancelled, bindings, channel)
    },
    is_cancelled=frontend_cancelled,
  )
  scope.observe(
    @observe.Lower,
    fn(_) raise {
      library_lower(
        input, package_name, module_name, checked, cancelled, string_limit, bindings,
        channel,
      )
    },
    is_cancelled=frontend_cancelled,
  )
}

///|
/// Check library budgets without parsing a source document.
pub fn LibraryLimits::validate(
  self : LibraryLimits,
) -> Unit raise FrontendError {
  let hard = LibraryLimits::default()
  if self.max_bytes <= 0 ||
    self.max_bytes > hard.max_bytes ||
    self.max_functions <= 0 ||
    self.max_functions > hard.max_functions ||
    self.max_parameters < 0 ||
    self.max_parameters > hard.max_parameters ||
    self.max_lets < 0 ||
    self.max_lets > hard.max_lets ||
    self.max_expressions <= 0 ||
    self.max_expressions > hard.max_expressions ||
    self.max_calls < 0 ||
    self.max_calls > hard.max_calls ||
    self.max_depth < 0 ||
    self.max_depth > hard.max_depth ||
    self.max_call_depth < 0 ||
    self.max_call_depth > hard.max_call_depth {
    library_error(
      "invalid_limits",
      "Limits must stay within the supported library budgets",
      None,
    )
  }
}