///|
pub struct State[T] {
  priv slot : StateSlot[T]
}

///|
pub struct Binding[T] {
  priv get_value : () -> T
  priv set_value : (T) -> Unit
}

///|
pub struct DerivedState[T] {
  priv get_value : () -> T
  priv subscribe_value : (() -> Unit) -> Array[StateSubscription]
}

///|
pub fn[T] State::new(value : T) -> State[T] {
  { slot: StateSlot::new(value) }
}

///|
pub fn[T] State::get(self : State[T]) -> T {
  self.slot.get()
}

///|
pub fn[T] State::set(self : State[T], value : T) -> Unit {
  self.slot.set(value)
}

///|
pub fn[T] State::update(self : State[T], f : (T) -> T raise?) -> Unit raise? {
  self.slot.update(f)
}

///|
pub fn[T] State::binding(self : State[T]) -> Binding[T] {
  Binding::new(get=() => self.get(), set=value => self.set(value))
}

///|
pub fn[T, U] State::derived(
  self : State[T],
  project : (T) -> U,
) -> DerivedState[U] {
  DerivedState::from_state(self, project)
}

///|
pub fn[T] State::version(self : State[T]) -> Int {
  self.slot.version
}

///|
pub fn[T] State::subscribe(
  self : State[T],
  callback : () -> Unit,
) -> StateSubscription {
  self.slot.subscribe(callback)
}

///|
pub fn[T] Binding::get(self : Binding[T]) -> T {
  (self.get_value)()
}

///|
pub fn[T] Binding::set(self : Binding[T], value : T) -> Unit {
  (self.set_value)(value)
}

///|
pub fn[T] Binding::update(
  self : Binding[T],
  f : (T) -> T raise?,
) -> Unit raise? {
  self.set(f(self.get()))
}

///|
pub fn[T] Binding::new(get~ : () -> T, set~ : (T) -> Unit) -> Binding[T] {
  { get_value: get, set_value: set }
}

///|
pub fn[T] DerivedState::new(
  get~ : () -> T,
  subscribe~ : (() -> Unit) -> Array[StateSubscription],
) -> DerivedState[T] {
  { get_value: get, subscribe_value: subscribe }
}

///|
pub fn[T] DerivedState::get(self : DerivedState[T]) -> T {
  (self.get_value)()
}

///|
pub fn[T] DerivedState::subscribe(
  self : DerivedState[T],
  callback : () -> Unit,
) -> Array[StateSubscription] {
  (self.subscribe_value)(callback)
}

///|
pub fn[T, U] DerivedState::from_state(
  state : State[T],
  project : (T) -> U,
) -> DerivedState[U] {
  DerivedState::new(get=() => project(state.get()), subscribe=callback => {
    [state.subscribe(callback)]
  })
}

///|
pub fn[A, B, T] DerivedState::combine(
  left : State[A],
  right : State[B],
  project : (A, B) -> T,
) -> DerivedState[T] {
  DerivedState::new(get=() => project(left.get(), right.get()), subscribe=callback => {
    [left.subscribe(callback), right.subscribe(callback)]
  })
}