// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
priv enum State[A] {
  Pending(Array[Handler[A]])
  Fulfilled(A)
  Rejected(Error)
}

///|
priv struct Handler[A]((State[A]) -> Unit)

///|
struct T[A] {
  mut state : State[A]
}

///|
struct Resolver[A](T[A])

///|
pub fn[A] make() -> Resolver[A] {
  Resolver({ state: Pending([]) })
}

///|
pub fn[A] resolve(a : A) -> T[A] {
  { state: Fulfilled(a) }
}

///|
pub fn[A] reject(err : Error) -> T[A] {
  { state: Rejected(err) }
}

///|
pub fn[A] T::get(self : T[A]) -> Result[A, Error]? {
  match self.state {
    Fulfilled(result) => Some(Ok(result))
    Rejected(error) => Some(Err(error))
    _ => None
  }
}

///|
pub fn[A] Resolver::try_complete(
  self : Resolver[A],
  result : () -> A raise,
) -> Bool {
  guard self.state is Pending(_) else { false }
  try result() catch {
    err => self.fullfill_or_reject(Rejected(err))
  } noraise {
    result => self.fullfill_or_reject(Fulfilled(result))
  }
  true
}

///|
pub fn[A] Resolver::resolve(self : Resolver[A], result : A) -> Unit {
  self.fullfill_or_reject(Fulfilled(result))
}

///|
pub fn[A] Resolver::reject(self : Resolver[A], err : Error) -> Unit {
  self.fullfill_or_reject(Rejected(err))
}

///|
fn[A] Resolver::fullfill_or_reject(
  self : Resolver[A],
  state : State[A],
) -> Unit {
  match self.state {
    Pending(handlers) => {
      self.state = state
      for handler in handlers {
        handler(state)
      }
    }
    _ => ()
  }
}

///|
pub fn[A] Resolver::future(self : Resolver[A]) -> T[A] {
  self.0
}

///|
pub fn[A] T::on_success(self : T[A], callback : (A) -> Unit) -> Unit {
  match self.state {
    Fulfilled(a) => callback(a)
    Rejected(_) => ()
    Pending(handlers) =>
      handlers.push(state => if state is Fulfilled(a) { callback(a) })
  }
}

///|
pub fn[A] T::on_failure(self : T[A], callback : (Error) -> Unit) -> Unit {
  match self.state {
    Rejected(err) => callback(err)
    Fulfilled(_) => ()
    Pending(handlers) =>
      handlers.push(state => if state is Rejected(err) { callback(err) })
  }
}

///|
pub fn[A, B] T::bind(self : T[A], callback : (A) -> T[B]) -> T[B] {
  match self.state {
    Fulfilled(a) => callback(a)
    Rejected(err) => reject(err)
    Pending(handlers) => {
      let promise : Resolver[B] = make()
      handlers.push(state => match state {
        Fulfilled(a) => {
          let future = callback(a)
          future.on_success(fn(b) { promise.resolve(b) }) |> ignore
          future.on_failure(fn(err) { promise.reject(err) }) |> ignore
        }
        Rejected(err) => promise.reject(err)
        _ => ()
      })
      promise.future()
    }
  }
}

///|
pub fn[A] T::finally(self : T[A], callback : () -> Unit) -> T[A] {
  match self.state {
    Fulfilled(_) | Rejected(_) => callback()
    Pending(handlers) =>
      handlers.push(state => if state is (Fulfilled(_) | Rejected(_)) {
        callback()
      })
  }
  self
}

///|
pub fn[A] all(promises : Array[T[A]]) -> T[Array[A]] {
  let promise = make()
  let mut count = promises.length()
  let results = Array::make(count, None)
  for i in 0.. ignore
    promises[i].on_failure(fn(err) { promise.reject(err) }) |> ignore
  }
  promise.future()
}

///|
pub suberror AggregateError Array[Error]

///|
pub fn[A] any(promises : Array[T[A]]) -> T[A] {
  let promise = make()
  let mut count = promises.length()
  let errors = Array::make(count, None)
  for i in 0.. ignore
    promises[i].on_failure(fn(err) {
      errors[i] = Some(err)
      count -= 1
      if count == 0 {
        promise.reject(AggregateError(errors.map(Option::unwrap)))
      }
    })
    |> ignore
  }
  promise.future()
}

///|
pub fn[A, B] T::zip(self : T[A], that : T[B]) -> T[(A, B)] {
  self.bind(fn(a) { that.bind(fn(b) { resolve((a, b)) }) })
}

///|
pub fn[A, B, C] T::zip_with(self : T[A], that : T[B], f : (A, B) -> C) -> T[C] {
  self.bind(fn(a) { that.bind(fn(b) { resolve(f(a, b)) }) })
}

///|
async fn[A] shift(f : ((A) -> Unit, (Error) -> Unit) -> Unit) -> A = "%async.suspend"

///|
fn reset(f : async () -> Unit noraise) -> Unit = "%async.run"

///|
pub async fn[A] T::await(self : T[A]) -> A {
  shift(fn(resolve : (A) -> Unit, reject : (Error) -> Unit) -> Unit {
    match self.state {
      Fulfilled(a) => resolve(a)
      Rejected(err) => reject(err)
      Pending(handlers) =>
        handlers.push(state => match state {
          Fulfilled(a) => resolve(a)
          Rejected(err) => reject(err)
          _ => ()
        })
    }
  })
}

///|
pub fn[A] spawn(cb : async () -> A) -> T[A] {
  let promise = make()
  reset(async fn() noraise {
    try cb() catch {
      e => promise.reject(e)
    } noraise {
      result => promise.resolve(result)
    }
  })
  promise.future()
}