///|
/// Stable business categories. Do not branch on SdkFailure.message.
/// ProcessingIncomplete includes parser/structural limits without a finer code.
#derive(Eq, Debug)
pub(all) enum FailureCode {
  InvalidConfiguration
  UnsupportedDocument
  ProcessingIncomplete
  IntegrityFailure
  InputLimitExceeded
  OutputLimitExceeded
  Cancelled
  WorkBudgetExceeded
  InternalFailure
  AuthorizationDenied
}

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

///|
pub extend FailureCode with Debug::{to_repr}

///|
pub extend FailureCode with ToJson::{to_json}

///|
pub impl ToJson for FailureCode with fn to_json(self) -> Json {
  Json::string(self.label())
}

///|
pub fn FailureCode::label(self : FailureCode) -> String {
  match self {
    InvalidConfiguration => "invalid_configuration"
    UnsupportedDocument => "unsupported_document"
    ProcessingIncomplete => "processing_incomplete"
    IntegrityFailure => "integrity_failure"
    InputLimitExceeded => "input_limit_exceeded"
    OutputLimitExceeded => "output_limit_exceeded"
    Cancelled => "cancelled"
    WorkBudgetExceeded => "work_budget_exceeded"
    InternalFailure => "internal_failure"
    AuthorizationDenied => "authorization_denied"
  }
}

///|
#derive(Eq, Debug)
pub(all) enum SdkOperation {
  CleanDocument
  PrepareDocument
  ExecuteDocumentPlan
  VerifyDocument
  PreviewDocument
  AuthorizeDocument
  ExecuteAuthorizedDocument
}

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

///|
pub extend SdkOperation with Debug::{to_repr}

///|
pub extend SdkOperation with ToJson::{to_json}

///|
pub impl ToJson for SdkOperation with fn to_json(self) -> Json {
  Json::string(
    match self {
      CleanDocument => "clean_document"
      PrepareDocument => "prepare_document"
      ExecuteDocumentPlan => "execute_document_plan"
      VerifyDocument => "verify_document"
      PreviewDocument => "preview_document"
      AuthorizeDocument => "authorize_document"
      ExecuteAuthorizedDocument => "execute_authorized_document"
    },
  )
}

///|
/// Only an Err contains this diagnostic; never a partial document or receipt.
/// Message is explanatory and may change independently of the stable code.
#derive(Eq, Debug, ToJson)
pub(all) struct SdkFailure {
  code : FailureCode
  operation : SdkOperation
  message : String
}

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

///|
pub extend SdkFailure with Debug::{to_repr}

///|
pub extend SdkFailure with ToJson::{to_json}

///|
fn stopped_failure(
  operation : SdkOperation,
  control : WorkControl,
) -> SdkFailure? {
  match control.stop_kind() {
    Some(CancelRequested) =>
      Some({
        code: Cancelled,
        operation,
        message: control.stop_reason().unwrap(),
      })
    Some(AllowanceExhausted) =>
      Some({
        code: WorkBudgetExceeded,
        operation,
        message: control.stop_reason().unwrap(),
      })
    None => None
  }
}

///|
fn sdk_failure(
  error : Error,
  operation : SdkOperation,
  control : WorkControl,
) -> SdkFailure {
  match error {
    Refused(message) => { code: UnsupportedDocument, operation, message, }
    Invalid(message) => { code: IntegrityFailure, operation, message, }
    Incomplete(message) =>
      stopped_failure(operation, control).unwrap_or({
        code: ProcessingIncomplete,
        operation,
        message,
      })
    _ => { code: InternalFailure, operation, message: error.to_string(), }
  }
}

///|
fn sdk_preflight(
  input : Bytes,
  output : Bytes?,
  limits : Limits,
  control : WorkControl,
  operation : SdkOperation,
) -> SdkFailure? {
  limits.validate() catch {
    Refused(message) =>
      return Some({ code: InvalidConfiguration, operation, message, })
    error =>
      return Some({
        code: InternalFailure,
        operation,
        message: error.to_string(),
      })
  }
  if stopped_failure(operation, control) is Some(failure) {
    return Some(failure)
  }
  if input.length() > limits.input_bytes {
    return Some({
      code: InputLimitExceeded,
      operation,
      message: "input exceeds configured input_bytes",
    })
  }
  if output is Some(bytes) && bytes.length() > limits.output_bytes {
    return Some({
      code: OutputLimitExceeded,
      operation,
      message: "output exceeds configured output_bytes",
    })
  }
  None
}

///|
/// Default intake path: rebuild once and independently verify the actual bytes.
/// This includes the legacy implementation's verification, not an extra pass.
pub fn clean_document(
  input : Bytes,
  limits~ : Limits = Limits::default(),
  control~ : WorkControl = WorkControl::default(),
) -> Result[VerifiedRebuilt, SdkFailure] {
  let operation = CleanDocument
  if sdk_preflight(input, None, limits, control, operation) is Some(error) {
    return Err(error)
  }
  Ok(rebuild_verified(input, limits~, control~)) catch {
    error => Err(sdk_failure(error, operation, control))
  }
}

///|
/// Inspect/prepare without producing output; useful before caller confirmation.
pub fn prepare_document(
  input : Bytes,
  limits~ : Limits = Limits::default(),
  control~ : WorkControl = WorkControl::default(),
) -> Result[PreparedPlan, SdkFailure] {
  let operation = PrepareDocument
  if sdk_preflight(input, None, limits, control, operation) is Some(error) {
    return Err(error)
  }
  Ok(prepare(input, limits~, control~)) catch {
    error => Err(sdk_failure(error, operation, control))
  }
}

///|
/// Recomputes source bindings/plan constraints and verifies the actual result.
pub fn execute_document_plan(
  input : Bytes,
  plan : Json,
  limits~ : Limits = Limits::default(),
  control~ : WorkControl = WorkControl::default(),
) -> Result[VerifiedRebuilt, SdkFailure] {
  let operation = ExecuteDocumentPlan
  if sdk_preflight(input, None, limits, control, operation) is Some(error) {
    return Err(error)
  }
  Ok(rebuild_verified_with_plan(input, plan, limits~, control~)) catch {
    error => Err(sdk_failure(error, operation, control))
  }
}

///|
/// Archive path: reads actual output plus original; a supplied receipt is not proof.
pub fn verify_document(
  output : Bytes,
  original : Bytes,
  limits~ : Limits = Limits::default(),
  control~ : WorkControl = WorkControl::default(),
) -> Result[VerifiedReceipt, SdkFailure] {
  let operation = VerifyDocument
  if sdk_preflight(original, Some(output), limits, control, operation)
    is Some(error) {
    return Err(error)
  }
  Ok(verify_contract(output, original, limits~, control~)) catch {
    error => Err(sdk_failure(error, operation, control))
  }
}