// Circuit Breaker State

///|
pub(all) enum State {
  Closed
  Open
  HalfOpen
} derive(Eq, Show)

// ConsecutiveResult represents consecutive successes or failures

///|
pub enum ConsecutiveResult {
  Successes(Int64)
  Failures(Int64)
} derive(Eq, Show)

// Counts holds the numbers of requests and their successes/failures

///|
pub struct Counts {
  mut requests : Int64
  mut total_successes : Int64
  mut total_failures : Int64
  mut consecutive : ConsecutiveResult
}

///|
pub fn Counts::new() -> Counts {
  {
    requests: 0L,
    total_successes: 0L,
    total_failures: 0L,
    consecutive: Successes(0L),
  }
}

///|
pub fn Counts::on_request(self : Counts) -> Unit {
  self.requests += 1L
}

///|
pub fn Counts::on_success(self : Counts) -> Unit {
  self.total_successes += 1L
  self.consecutive = match self.consecutive {
    Successes(n) => Successes(n + 1L)
    Failures(_) => Successes(1L)
  }
}

///|
pub fn Counts::on_failure(self : Counts) -> Unit {
  self.total_failures += 1L
  self.consecutive = match self.consecutive {
    Failures(n) => Failures(n + 1L)
    Successes(_) => Failures(1L)
  }
}

///|
pub fn Counts::clear(self : Counts) -> Unit {
  self.requests = 0L
  self.total_successes = 0L
  self.total_failures = 0L
  self.consecutive = Successes(0L)
}

// Helper methods to get consecutive counts

///|
pub fn Counts::consecutive_successes(self : Counts) -> Int64 {
  match self.consecutive {
    Successes(n) => n
    Failures(_) => 0L
  }
}

///|
pub fn Counts::consecutive_failures(self : Counts) -> Int64 {
  match self.consecutive {
    Failures(n) => n
    Successes(_) => 0L
  }
}

// Settings configures CircuitBreaker

///|
pub struct Settings[T, E] {
  name : String
  max_requests : Int64 // Maximum number of requests allowed in HalfOpen state
  interval : Int64 // Period in milliseconds to clear counts in Closed state
  timeout : Int64 // Period in milliseconds after which Open state transitions to HalfOpen
  ready_to_trip : (Counts) -> Bool // Function to determine if circuit should trip
  on_state_change : (String, State, State) -> Unit // Callback on state change
  is_successful : (Result[T, E]) -> Bool // Function to determine if result is successful
  get_now : () -> Int64 // Function to get current time in milliseconds
}

///|
pub fn[T, E] Settings::default() -> Settings[T, E] {
  {
    name: "CircuitBreaker",
    max_requests: 1L,
    interval: 0L,
    timeout: 60000L, // 60 seconds
    ready_to_trip: fn(counts) { counts.consecutive_failures() > 5L },
    on_state_change: fn(_name, _from, _to) { () },
    is_successful: fn(result) {
      match result {
        Ok(_) => true
        Err(_) => false
      }
    },
    get_now: fn() { get_current_time_ms() },
  }
}

///|
pub fn[T, E] Settings::with_name(
  self : Settings[T, E],
  name : String,
) -> Settings[T, E] {
  { ..self, name, }
}

///|
pub fn[T, E] Settings::with_max_requests(
  self : Settings[T, E],
  max_requests : Int64,
) -> Settings[T, E] {
  { ..self, max_requests, }
}

///|
pub fn[T, E] Settings::with_interval(
  self : Settings[T, E],
  interval : Int64,
) -> Settings[T, E] {
  { ..self, interval, }
}

///|
pub fn[T, E] Settings::with_timeout(
  self : Settings[T, E],
  timeout : Int64,
) -> Settings[T, E] {
  { ..self, timeout, }
}

///|
pub fn[T, E] Settings::with_get_now(
  self : Settings[T, E],
  get_now : () -> Int64,
) -> Settings[T, E] {
  { ..self, get_now, }
}

// CircuitBreaker is a state machine to prevent sending requests that are likely to fail

///|
pub struct CircuitBreaker[T, E] {
  settings : Settings[T, E]
  mut state : State
  mut generation : Int64
  counts : Counts
  mut expiry : Int64 // Timestamp in milliseconds
  get_now : () -> Int64 // Function to get current time in milliseconds
}

///|
pub fn[T, E] CircuitBreaker::new(
  settings : Settings[T, E],
) -> CircuitBreaker[T, E] {
  let now = (settings.get_now)()
  let mut expiry = 0L
  if settings.interval > 0L {
    expiry = now + settings.interval
  }
  {
    settings,
    state: Closed,
    generation: 0L,
    counts: Counts::new(),
    expiry,
    get_now: settings.get_now,
  }
}

///|
fn[T, E] CircuitBreaker::get_now(self : CircuitBreaker[T, E]) -> Int64 {
  (self.get_now)()
}

// Get current time in milliseconds (JS backend)
// For now, return 0 as a placeholder since FFI setup is complex

///|
pub fn[T, E] CircuitBreaker::state(self : CircuitBreaker[T, E]) -> State {
  self.state
}

///|
pub fn[T, E] CircuitBreaker::counts(self : CircuitBreaker[T, E]) -> Counts {
  self.counts
}

// CircuitBreakerError represents errors returned by CircuitBreaker

///|
pub suberror CircuitBreakerError {
  TooManyRequests
  OpenCircuit
} derive(Show)

// CircuitBreakerRunResult represents the result of running a function through the CircuitBreaker

///|
pub enum CircuitBreakerRunResult[T, E] {
  Success(T)
  Failure(E)
  Rejected(CircuitBreakerError)
} derive(Show)

///|
fn[T, E] CircuitBreakerRunResult::from_result(
  result : Result[Result[T, E], CircuitBreakerError],
) -> CircuitBreakerRunResult[T, E] {
  match result {
    Ok(value) =>
      match value {
        Ok(v) => Success(v)
        Err(err) => Failure(err)
      }
    Err(err) => Rejected(err)
  }
}

///|
pub fn[T, E : Error] CircuitBreakerRunResult::unwrap_or_error(
  self : CircuitBreakerRunResult[T, E],
) -> T raise {
  match self {
    Success(v) => v
    Failure(err) => raise err
    Rejected(err) => raise err
  }
}

///|
pub fn[T, E, U] CircuitBreakerRunResult::map(
  self : CircuitBreakerRunResult[T, E],
  f : (T) -> U,
) -> CircuitBreakerRunResult[U, E] {
  match self {
    Success(v) => Success(f(v))
    Failure(err) => Failure(err)
    Rejected(err) => Rejected(err)
  }
}

///|
pub fn[T, E, U] CircuitBreakerRunResult::map_errors(
  self : CircuitBreakerRunResult[T, E],
  map_failure : (E) -> U,
  map_rejected : (CircuitBreakerError) -> U,
) -> Result[T, U] {
  match self {
    Success(v) => Ok(v)
    Failure(err) => map_failure(err) |> Err
    Rejected(err) => map_rejected(err) |> Err
  }
}

// Execute runs the given function if the CircuitBreaker accepts it

///|
pub fn[T, E] CircuitBreaker::run_sync(
  self : CircuitBreaker[T, E],
  f : () -> Result[T, E],
) -> CircuitBreakerRunResult[T, E] {
  self
  .before_request()
  .map(fn(generation) {
    let result = f()
    let is_success = (self.settings.is_successful)(result)
    self.after_request(generation, is_success)
    result
  })
  |> CircuitBreakerRunResult::from_result
}

///|
pub fn[T, E : Error] CircuitBreaker::try_sync(
  self : CircuitBreaker[T, E],
  f : () -> T raise E,
) -> T raise {
  self.run_sync(fn() { try? f() }).unwrap_or_error()
}

///|
pub async fn[T, E] CircuitBreaker::run_async(
  self : CircuitBreaker[T, E],
  f : async () -> Result[T, E] noraise,
) -> CircuitBreakerRunResult[T, E] noraise {
  match self.before_request() {
    Err(e) => Rejected(e)
    Ok(generation) => {
      let result = f()
      let is_success = (self.settings.is_successful)(result)
      self.after_request(generation, is_success)
      match result {
        Ok(v) => Success(v)
        Err(err) => Failure(err)
      }
    }
  }
}

///|
pub async fn[T, E : Error] CircuitBreaker::try_async(
  self : CircuitBreaker[T, E],
  f : async () -> T raise E,
) -> T {
  self
  .run_async(async fn() -> Result[T, E] noraise { try? f() })
  .unwrap_or_error()
}

///|
fn[T, E] CircuitBreaker::state_transition_on_before_request(
  self : CircuitBreaker[T, E],
  now : Int64,
) -> Unit {
  match self.state {
    Open =>
      // Check if we need to transition from Open to HalfOpen
      if self.expiry <= now {
        self.change_state(HalfOpen, now)
      }
    HalfOpen => ()
    Closed =>
      // Check if interval has passed in Closed state
      if self.settings.interval > 0L && self.expiry <= now {
        self.to_new_generation(now)
      }
  }
}

///|
fn[T, E] CircuitBreaker::before_request(
  self : CircuitBreaker[T, E],
) -> Result[Int64, CircuitBreakerError] {
  let now = self.get_now()
  self.state_transition_on_before_request(now)
  let generation = self.generation
  let result = match self.state {
    Open => Err(CircuitBreakerError::OpenCircuit)
    HalfOpen =>
      if self.counts.requests >= self.settings.max_requests {
        CircuitBreakerError::TooManyRequests |> Err
      } else {
        Ok(generation)
      }
    Closed => Ok(generation)
  }
  if result is Ok(_) {
    self.counts.on_request()
  }
  result
}

///|
fn[T, E] CircuitBreaker::after_request(
  self : CircuitBreaker[T, E],
  before : Int64,
  success : Bool,
) -> Unit {
  let now = self.get_now()
  let state = self.state

  // Ignore if generation has changed
  if before != self.generation {
    return
  }
  if success {
    self.on_success(state, now)
  } else {
    self.on_failure(state, now)
  }
}

///|
fn[T, E] CircuitBreaker::on_success(
  self : CircuitBreaker[T, E],
  state : State,
  now : Int64,
) -> Unit {
  self.counts.on_success()
  match state {
    Closed => ()
    HalfOpen =>
      if self.counts.consecutive_successes() >= self.settings.max_requests {
        self.change_state(Closed, now)
      }
    Open => ()
  }
}

///|
fn[T, E] CircuitBreaker::on_failure(
  self : CircuitBreaker[T, E],
  state : State,
  now : Int64,
) -> Unit {
  self.counts.on_failure()
  match state {
    Closed =>
      if (self.settings.ready_to_trip)(self.counts) {
        self.change_state(Open, now)
      }
    HalfOpen => self.change_state(Open, now)
    Open => ()
  }
}

///|
fn[T, E] CircuitBreaker::change_state(
  self : CircuitBreaker[T, E],
  new_state : State,
  now : Int64,
) -> Unit {
  if self.state == new_state {
    return
  }
  let prev = self.state
  self.state = new_state
  self.to_new_generation(now)
  (self.settings.on_state_change)(self.settings.name, prev, new_state)
}

///|
fn[T, E] CircuitBreaker::to_new_generation(
  self : CircuitBreaker[T, E],
  now : Int64,
) -> Unit {
  self.generation += 1L
  self.counts.clear()
  match self.state {
    Closed =>
      if self.settings.interval > 0L {
        self.expiry = now + self.settings.interval
      } else {
        self.expiry = 0L
      }
    Open => self.expiry = now + self.settings.timeout
    HalfOpen => self.expiry = 0L
  }
}