///|
pub(all) struct ServiceError {
  code : String
  message : String
} derive(Eq, Debug, ToJson)

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

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

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

///|
pub(all) enum ServiceTaskResult[T] {
  Success(T)
  Failure(ServiceError)
  Cancelled
} derive(Eq, Debug)

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

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

///|
pub struct ServiceTask[T] {
  key : String
  label : String
  start_fn : ((ServiceTaskResult[T]) -> Unit) -> (() -> Unit)?
}

///|
pub fn ServiceError::new(code~ : String, message~ : String) -> ServiceError {
  { code, message, }
}

///|
pub fn ServiceError::unavailable(message : String) -> ServiceError {
  ServiceError::new(code="unavailable", message~)
}

///|
pub fn[T] ServiceTask::new(
  key~ : String,
  start~ : ((ServiceTaskResult[T]) -> Unit) -> (() -> Unit)?,
  label? : String = "",
) -> ServiceTask[T] {
  { key, label, start_fn: start, }
}

///|
pub fn[T] ServiceTask::succeed(
  key~ : String,
  value~ : T,
  label? : String = "",
) -> ServiceTask[T] {
  ServiceTask::new(key~, label~, start=complete => {
    complete(Success(value))
    None
  })
}

///|
pub fn[T] ServiceTask::fail(
  key~ : String,
  error~ : ServiceError,
  label? : String = "",
) -> ServiceTask[T] {
  ServiceTask::new(key~, label~, start=complete => {
    complete(Failure(error))
    None
  })
}

///|
pub fn[T] ServiceTask::cancelled(
  key~ : String,
  label? : String = "",
) -> ServiceTask[T] {
  ServiceTask::new(key~, label~, start=complete => {
    complete(Cancelled)
    None
  })
}

///|
pub fn[T] ServiceTask::key(self : ServiceTask[T]) -> String {
  self.key
}

///|
pub fn[T] ServiceTask::label(self : ServiceTask[T]) -> String {
  self.label
}

///|
pub fn[T] ServiceTask::start(
  self : ServiceTask[T],
  complete : (ServiceTaskResult[T]) -> Unit,
) -> (() -> Unit)? {
  (self.start_fn)(complete)
}

///|
pub fn[T, U] ServiceTask::map(
  self : ServiceTask[T],
  map : (T) -> U,
) -> ServiceTask[U] {
  ServiceTask::new(key=self.key, label=self.label, start=complete => {
    self.start(result => {
      match result {
        Success(value) => complete(Success(map(value)))
        Failure(error) => complete(Failure(error))
        Cancelled => complete(Cancelled)
      }
    })
  })
}

///|
pub fn[T, Msg] ServiceTask::effect(
  self : ServiceTask[T],
  map~ : (ServiceTaskResult[T]) -> Msg,
) -> @core.Effect[Msg] {
  @core.Effect::service_task(key=self.key, label=self.label, start=dispatch => {
    self.start(result => dispatch(map(result)))
  })
}

///|
fn[T] unavailable_task(key : String, capability : String) -> ServiceTask[T] {
  ServiceTask::fail(
    key~,
    error=ServiceError::unavailable(capability + " is unavailable"),
    label=capability,
  )
}