// Cloudflare Workflows API bindings

///|
/// Workflow - represents a Workflow binding
#external
pub type Workflow

///|
pub fn Workflow::as_any(self : Workflow) -> @core.Any = "%identity"

///|
/// Create a new workflow instance
pub async fn Workflow::create(
  self : Workflow,
  options : WorkflowInstanceCreateOptions,
) -> WorkflowInstance {
  let promise : @core.Promise[WorkflowInstance] = self
    .as_any()
    ._call("create", [options.to_js()])
    .cast()
  promise.wait()
}

///|
/// Create a new workflow instance with just an ID
pub async fn Workflow::create_with_id(
  self : Workflow,
  id : String,
) -> WorkflowInstance {
  let opts = @core.new_object()
  opts["id"] = @core.any(id)
  let promise : @core.Promise[WorkflowInstance] = self
    .as_any()
    ._call("create", [opts])
    .cast()
  promise.wait()
}

///|
/// Create a new workflow instance with default options
pub async fn Workflow::create_default(self : Workflow) -> WorkflowInstance {
  let promise : @core.Promise[WorkflowInstance] = self
    .as_any()
    ._call("create", [])
    .cast()
  promise.wait()
}

///|
/// Create multiple workflow instances (up to 100)
pub async fn Workflow::create_batch(
  self : Workflow,
  batch : Array[WorkflowInstanceCreateOptions],
) -> Array[WorkflowInstance] {
  let batch_js = @core.new_array()
  let mut i = 0
  while i < batch.length() {
    @core.any(batch_js)._call("push", [batch[i].to_js()]) |> ignore
    i = i + 1
  }
  let promise : @core.Promise[@core.Any] = self
    .as_any()
    ._call("createBatch", [@core.any(batch_js)])
    .cast()
  let result = promise.wait()
  let len : Int = result["length"].cast()
  let instances : Array[WorkflowInstance] = []
  let mut j = 0
  while j < len {
    instances.push(result[j.to_string()].cast())
    j = j + 1
  }
  instances
}

///|
/// Get an existing workflow instance by ID
pub async fn Workflow::get(self : Workflow, id : String) -> WorkflowInstance {
  let promise : @core.Promise[WorkflowInstance] = self
    .as_any()
    ._call("get", [@core.any(id)])
    .cast()
  promise.wait()
}

///|
/// Options for creating a workflow instance
pub(all) struct WorkflowInstanceCreateOptions {
  id : String? // Unique identifier (up to 100 chars)
  params : @core.Any? // Event payload
}

///|
/// Convert WorkflowInstanceCreateOptions to JavaScript object
pub fn WorkflowInstanceCreateOptions::to_js(
  self : WorkflowInstanceCreateOptions,
) -> @core.Any {
  let obj = @core.new_object()
  if self.id is Some(id_val) {
    obj["id"] = @core.any(id_val)
  }
  if self.params is Some(p) {
    obj["params"] = p
  }
  obj
}

///|
/// Create options with just an ID
pub fn WorkflowInstanceCreateOptions::with_id(
  id : String,
) -> WorkflowInstanceCreateOptions {
  { id: Some(id), params: None }
}

///|
/// Create options with ID and params
pub fn WorkflowInstanceCreateOptions::with_id_and_params(
  id : String,
  params : @core.Any,
) -> WorkflowInstanceCreateOptions {
  { id: Some(id), params: Some(params) }
}

///|
/// Create options with just params (auto-generated ID)
pub fn WorkflowInstanceCreateOptions::with_params(
  params : @core.Any,
) -> WorkflowInstanceCreateOptions {
  { id: None, params: Some(params) }
}

///|
/// WorkflowInstance - represents a running workflow instance
#external
pub type WorkflowInstance

///|
pub fn WorkflowInstance::as_any(self : WorkflowInstance) -> @core.Any = "%identity"

///|
/// Get the instance ID
pub fn WorkflowInstance::id(self : WorkflowInstance) -> String {
  self.as_any()["id"].cast()
}

///|
/// Get the current status of the instance
pub async fn WorkflowInstance::status(
  self : WorkflowInstance,
) -> InstanceStatus {
  let promise : @core.Promise[InstanceStatus] = self
    .as_any()
    ._call("status", [])
    .cast()
  promise.wait()
}

///|
/// Pause the workflow instance
pub async fn WorkflowInstance::pause(self : WorkflowInstance) -> Unit {
  let promise : @core.Promise[Unit] = self.as_any()._call("pause", []).cast()
  promise.wait()
}

///|
/// Resume a paused workflow instance
pub async fn WorkflowInstance::resume_(self : WorkflowInstance) -> Unit {
  let promise : @core.Promise[Unit] = self.as_any()._call("resume", []).cast()
  promise.wait()
}

///|
/// Terminate the workflow instance
pub async fn WorkflowInstance::terminate(self : WorkflowInstance) -> Unit {
  let promise : @core.Promise[Unit] = self
    .as_any()
    ._call("terminate", [])
    .cast()
  promise.wait()
}

///|
/// Restart the workflow instance
pub async fn WorkflowInstance::restart(self : WorkflowInstance) -> Unit {
  let promise : @core.Promise[Unit] = self.as_any()._call("restart", []).cast()
  promise.wait()
}

///|
/// Send an event to the workflow instance
pub async fn WorkflowInstance::send_event(
  self : WorkflowInstance,
  event_type : String,
  payload : @core.Any,
) -> Unit {
  let opts = @core.new_object()
  opts["type"] = @core.any(event_type)
  opts["payload"] = payload
  let promise : @core.Promise[Unit] = self
    .as_any()
    ._call("sendEvent", [opts])
    .cast()
  promise.wait()
}

///|
/// Send an event without payload
pub async fn WorkflowInstance::send_event_simple(
  self : WorkflowInstance,
  event_type : String,
) -> Unit {
  let opts = @core.new_object()
  opts["type"] = @core.any(event_type)
  let promise : @core.Promise[Unit] = self
    .as_any()
    ._call("sendEvent", [opts])
    .cast()
  promise.wait()
}

///|
/// InstanceStatus - status information for a workflow instance
#external
pub type InstanceStatus

///|
pub fn InstanceStatus::as_any(self : InstanceStatus) -> @core.Any = "%identity"

///|
/// Get the status string
pub fn InstanceStatus::status(self : InstanceStatus) -> String {
  self.as_any()["status"].cast()
}

///|
/// Check if instance is queued
pub fn InstanceStatus::is_queued(self : InstanceStatus) -> Bool {
  self.status() == "queued"
}

///|
/// Check if instance is running
pub fn InstanceStatus::is_running(self : InstanceStatus) -> Bool {
  self.status() == "running"
}

///|
/// Check if instance is paused
pub fn InstanceStatus::is_paused(self : InstanceStatus) -> Bool {
  self.status() == "paused"
}

///|
/// Check if instance has errored
pub fn InstanceStatus::is_errored(self : InstanceStatus) -> Bool {
  self.status() == "errored"
}

///|
/// Check if instance is terminated
pub fn InstanceStatus::is_terminated(self : InstanceStatus) -> Bool {
  self.status() == "terminated"
}

///|
/// Check if instance is complete
pub fn InstanceStatus::is_complete(self : InstanceStatus) -> Bool {
  self.status() == "complete"
}

///|
/// Check if instance is waiting (sleeping or waiting for event)
pub fn InstanceStatus::is_waiting(self : InstanceStatus) -> Bool {
  self.status() == "waiting"
}

///|
/// Check if instance is waiting for pause
pub fn InstanceStatus::is_waiting_for_pause(self : InstanceStatus) -> Bool {
  self.status() == "waitingForPause"
}

///|
/// Get the error information if any
pub fn InstanceStatus::error(self : InstanceStatus) -> WorkflowError? {
  let err = self.as_any()["error"]
  if @core.is_null(err) || @core.typeof_(err) == "undefined" {
    None
  } else {
    Some(err.cast())
  }
}

///|
/// Get the output if complete
pub fn InstanceStatus::output(self : InstanceStatus) -> @core.Any? {
  let out = self.as_any()["output"]
  if @core.is_null(out) || @core.typeof_(out) == "undefined" {
    None
  } else {
    Some(out)
  }
}

///|
/// WorkflowError - error information from a workflow
#external
pub type WorkflowError

///|
pub fn WorkflowError::as_any(self : WorkflowError) -> @core.Any = "%identity"

///|
/// Get the error name
pub fn WorkflowError::name(self : WorkflowError) -> String {
  self.as_any()["name"].cast()
}

///|
/// Get the error message
pub fn WorkflowError::message(self : WorkflowError) -> String {
  self.as_any()["message"].cast()
}

// ============================================
// WorkflowStep - for use inside workflow run()
// ============================================

///|
/// WorkflowStep - provides step operations within a workflow
#external
pub type WorkflowStep

///|
pub fn WorkflowStep::as_any(self : WorkflowStep) -> @core.Any = "%identity"

///|
/// Execute a durable step with automatic retries
pub async fn WorkflowStep::do_(
  self : WorkflowStep,
  name : String,
  callback : async () -> @core.Any,
) -> @core.Any {
  let promise : @core.Promise[@core.Any] = self
    .as_any()
    ._call("do", [@core.any(name), @core.any(callback)])
    .cast()
  promise.wait()
}

///|
/// Execute a durable step with configuration
pub async fn WorkflowStep::do_with_config(
  self : WorkflowStep,
  name : String,
  config : WorkflowStepConfig,
  callback : async () -> @core.Any,
) -> @core.Any {
  let promise : @core.Promise[@core.Any] = self
    .as_any()
    ._call("do", [@core.any(name), config.to_js(), @core.any(callback)])
    .cast()
  promise.wait()
}

///|
/// Sleep for a specified duration
pub async fn WorkflowStep::sleep(
  self : WorkflowStep,
  name : String,
  duration : String,
) -> Unit {
  let promise : @core.Promise[Unit] = self
    .as_any()
    ._call("sleep", [@core.any(name), @core.any(duration)])
    .cast()
  promise.wait()
}

///|
/// Sleep until a specific timestamp
pub async fn WorkflowStep::sleep_until(
  self : WorkflowStep,
  name : String,
  timestamp : @core.Any, // Date object
) -> Unit {
  let promise : @core.Promise[Unit] = self
    .as_any()
    ._call("sleepUntil", [@core.any(name), timestamp])
    .cast()
  promise.wait()
}

///|
/// Wait for an external event
pub async fn WorkflowStep::wait_for_event(
  self : WorkflowStep,
  name : String,
  options : WaitForEventOptions,
) -> WorkflowEventPayload {
  let promise : @core.Promise[WorkflowEventPayload] = self
    .as_any()
    ._call("waitForEvent", [@core.any(name), options.to_js()])
    .cast()
  promise.wait()
}

///|
/// Options for waitForEvent
pub(all) struct WaitForEventOptions {
  event : String // Event type to wait for
  timeout : String? // Timeout duration (e.g., "24 hours")
}

///|
/// Convert WaitForEventOptions to JavaScript object
pub fn WaitForEventOptions::to_js(self : WaitForEventOptions) -> @core.Any {
  let obj = @core.new_object()
  obj["event"] = @core.any(self.event)
  if self.timeout is Some(t) {
    obj["timeout"] = @core.any(t)
  }
  obj
}

///|
/// Create wait options with just event type
pub fn WaitForEventOptions::new(event : String) -> WaitForEventOptions {
  { event, timeout: None }
}

///|
/// Create wait options with event and timeout
pub fn WaitForEventOptions::with_timeout(
  event : String,
  timeout : String,
) -> WaitForEventOptions {
  { event, timeout: Some(timeout) }
}

///|
/// WorkflowEventPayload - payload received from waitForEvent
#external
pub type WorkflowEventPayload

///|
pub fn WorkflowEventPayload::as_any(self : WorkflowEventPayload) -> @core.Any = "%identity"

///|
/// Get the event type
pub fn WorkflowEventPayload::event_type(self : WorkflowEventPayload) -> String {
  self.as_any()["type"].cast()
}

///|
/// Get the payload data
pub fn WorkflowEventPayload::payload(self : WorkflowEventPayload) -> @core.Any? {
  let p = self.as_any()["payload"]
  if @core.is_null(p) || @core.typeof_(p) == "undefined" {
    None
  } else {
    Some(p)
  }
}

///|
/// Get the timestamp
pub fn WorkflowEventPayload::timestamp(
  self : WorkflowEventPayload,
) -> @core.Any {
  self.as_any()["timestamp"]
}

///|
/// Configuration for workflow step retry behavior
pub(all) struct WorkflowStepConfig {
  retries : WorkflowRetryConfig?
  timeout : String? // e.g., "15 minutes"
}

///|
/// Convert WorkflowStepConfig to JavaScript object
pub fn WorkflowStepConfig::to_js(self : WorkflowStepConfig) -> @core.Any {
  let obj = @core.new_object()
  if self.retries is Some(r) {
    obj["retries"] = r.to_js()
  }
  if self.timeout is Some(t) {
    obj["timeout"] = @core.any(t)
  }
  obj
}

///|
/// Create step config with just timeout
pub fn WorkflowStepConfig::with_timeout(timeout : String) -> WorkflowStepConfig {
  { retries: None, timeout: Some(timeout) }
}

///|
/// Create step config with retries
pub fn WorkflowStepConfig::with_retries(
  retries : WorkflowRetryConfig,
) -> WorkflowStepConfig {
  { retries: Some(retries), timeout: None }
}

///|
/// Create step config with both retries and timeout
pub fn WorkflowStepConfig::new(
  retries : WorkflowRetryConfig,
  timeout : String,
) -> WorkflowStepConfig {
  { retries: Some(retries), timeout: Some(timeout) }
}

///|
/// Retry configuration for workflow steps
pub(all) struct WorkflowRetryConfig {
  limit : Int // Max number of retries
  delay : String // Delay between retries (e.g., "5 second")
  backoff : String // "constant" or "exponential"
}

///|
/// Convert WorkflowRetryConfig to JavaScript object
pub fn WorkflowRetryConfig::to_js(self : WorkflowRetryConfig) -> @core.Any {
  let obj = @core.new_object()
  obj["limit"] = @core.any(self.limit)
  obj["delay"] = @core.any(self.delay)
  obj["backoff"] = @core.any(self.backoff)
  obj
}

///|
/// Create constant backoff retry config
pub fn WorkflowRetryConfig::constant(
  limit : Int,
  delay : String,
) -> WorkflowRetryConfig {
  { limit, delay, backoff: "constant" }
}

///|
/// Create exponential backoff retry config
pub fn WorkflowRetryConfig::exponential(
  limit : Int,
  delay : String,
) -> WorkflowRetryConfig {
  { limit, delay, backoff: "exponential" }
}

// ============================================
// WorkflowEvent - event passed to workflow run()
// ============================================

///|
/// WorkflowEvent - the event object passed to a workflow's run method
#external
pub type WorkflowEvent

///|
pub fn WorkflowEvent::as_any(self : WorkflowEvent) -> @core.Any = "%identity"

///|
/// Get the payload data
pub fn WorkflowEvent::payload(self : WorkflowEvent) -> @core.Any {
  self.as_any()["payload"]
}

///|
/// Get the timestamp when the instance was created
pub fn WorkflowEvent::timestamp(self : WorkflowEvent) -> @core.Any {
  self.as_any()["timestamp"]
}

///|
/// Get the instance ID
pub fn WorkflowEvent::instance_id(self : WorkflowEvent) -> String {
  self.as_any()["instanceId"].cast()
}

// ============================================
// NonRetryableError - to stop retries
// ============================================

///|
/// Create a NonRetryableError to stop step retries
extern "js" fn create_non_retryable_error(message : String) -> @core.Any =
  #| (message) => new NonRetryableError(message)

///|
/// Create a NonRetryableError with a custom name
extern "js" fn create_non_retryable_error_with_name(
  message : String,
  name : String,
) -> @core.Any =
  #| (message, name) => new NonRetryableError(message, name)

///|
/// NonRetryableError - when thrown inside step.do(), stops retries
pub fn non_retryable_error(message : String) -> @core.Any {
  create_non_retryable_error(message)
}

///|
/// NonRetryableError with custom name
pub fn non_retryable_error_with_name(
  message : String,
  name : String,
) -> @core.Any {
  create_non_retryable_error_with_name(message, name)
}