///|
/// Identity established by a trusted server-side authenticator, never by request metadata.
pub(all) struct Principal {
  id : String
  scopes : Array[String]
  attributes : Map[String, Json]
} derive(Eq, Debug)

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

///|
pub extend Principal with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub fn Principal::new(
  id : String,
  scopes~ : Array[String] = [],
  attributes~ : Map[String, Json] = {},
) -> Principal {
  { id, scopes, attributes, }
}

///|
/// Request-local cooperative cancellation, shared with its transport and task owner.
pub struct CancellationToken {
  mut cancelled : Bool
  callbacks : Map[Int32, () -> Unit]
  mut next_callback_id : Int32
}

///|
pub fn CancellationToken::new() -> CancellationToken {
  { cancelled: false, callbacks: Map([]), next_callback_id: 0, }
}

///|
pub fn CancellationToken::is_cancelled(self : CancellationToken) -> Bool {
  self.cancelled
}

///|
/// Register runtime cleanup. Late registrations run immediately after cancellation.
pub fn CancellationToken::on_cancel(
  self : CancellationToken,
  callback : () -> Unit,
) -> CancellationRegistration {
  let id = self.next_callback_id
  self.next_callback_id += 1
  if self.cancelled {
    callback()
  } else {
    self.callbacks.set(id, callback)
  }
  { token: self, id, }
}

///|
/// Dispose registrations when their request/task completes to avoid retaining finished work.
pub struct CancellationRegistration {
  token : CancellationToken
  id : Int32
}

///|
pub fn CancellationRegistration::dispose(
  self : CancellationRegistration,
) -> Unit {
  self.token.callbacks.remove(self.id)
}

///|
pub fn CancellationToken::cancel(self : CancellationToken) -> Unit {
  if self.cancelled {
    return
  }
  self.cancelled = true
  let callbacks = self.callbacks.values().to_array()
  self.callbacks.clear()
  for callback in callbacks {
    callback()
  }
}

///|
pub(all) suberror RequestCancelled {
  RequestCancelled
} derive(Debug)

///|
pub extend RequestCancelled with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub fn CancellationToken::throw_if_cancelled(
  self : CancellationToken,
) -> Unit raise RequestCancelled {
  if self.cancelled {
    raise RequestCancelled
  }
}

///|
/// Reports progress through the current request's transport; direct dispatch defaults to a no-op.
pub struct ProgressReporter {
  send : async (Double, Double?, String?) -> Unit
  cancellation_token : CancellationToken
}

///|
pub fn ProgressReporter::new(
  send : async (Double, Double?, String?) -> Unit,
  cancellation_token~ : CancellationToken = CancellationToken::new(),
) -> ProgressReporter {
  { send, cancellation_token, }
}

///|
pub async fn ProgressReporter::report(
  self : ProgressReporter,
  progress : Double,
  total? : Double,
  message? : String,
) -> Unit {
  self.cancellation_token.throw_if_cancelled()
  (self.send)(progress, total, message)
}

///|
/// Runtime data stays separate from tool/prompt business arguments, including on MRTR retry.
pub(all) struct RequestContext {
  request_id : @types.RequestId
  meta : Map[String, Json]
  principal : Principal?
  input_responses : Map[String, Json]
  continuation_state : Json?
  cancellation_token : CancellationToken
  progress_reporter : ProgressReporter
}

///|
pub fn RequestContext::new(
  request_id : @types.RequestId,
  meta~ : Map[String, Json] = {},
  principal? : Principal,
  input_responses~ : Map[String, Json] = {},
  continuation_state? : Json,
  cancellation_token~ : CancellationToken = CancellationToken::new(),
  progress_reporter? : ProgressReporter,
) -> RequestContext {
  {
    request_id,
    meta: meta.copy(),
    principal,
    input_responses: input_responses.copy(),
    continuation_state,
    cancellation_token,
    progress_reporter: progress_reporter.unwrap_or_else(fn() {
      ProgressReporter::new((_, _, _) => (), cancellation_token~)
    }),
  }
}