///|
pub struct ScrollState {
  offset_slot : State[Point]
  smooth_target : State[Point?]
  smooth_start : State[Point?]
  smooth_start_time_ms : State[Double?]
  smooth_duration_ms : State[Double?]
}

///|
pub struct FocusState {
  key_slot : State[String?]
}

///|
pub struct NavigationState {
  stack_slot : State[Array[String]]
}

///|
pub fn ScrollState::new(
  offset? : Point = Point::new(x=0.0, y=0.0),
) -> ScrollState {
  {
    offset_slot: State::new(offset),
    smooth_target: State::new(None),
    smooth_start: State::new(None),
    smooth_start_time_ms: State::new(None),
    smooth_duration_ms: State::new(None),
  }
}

///|
pub fn ScrollState::offset(self : ScrollState) -> Point {
  self.offset_slot.get()
}

///|
pub fn ScrollState::set_offset(self : ScrollState, offset : Point) -> Unit {
  self.offset_slot.set(offset)
}

///|
pub fn ScrollState::binding(self : ScrollState) -> Binding[Point] {
  self.offset_slot.binding()
}

///|
pub fn ScrollState::smooth_scroll_to(
  self : ScrollState,
  x~ : Double,
  y~ : Double,
  duration_ms? : Double = 220.0,
) -> Unit {
  let current = self.offset()
  self.smooth_start.set(Some(current))
  self.smooth_target.set(Some(Point::new(x~, y~)))
  self.smooth_start_time_ms.set(Some(0.0))
  self.smooth_duration_ms.set(Some(max_double(0.0, duration_ms)))
}

///|
pub fn ScrollState::tick(self : ScrollState, time_ms : Double) -> Unit {
  let target = self.smooth_target.get()
  let start = self.smooth_start.get()
  let start_time = self.smooth_start_time_ms.get()
  let duration = self.smooth_duration_ms.get()
  match (target, start, start_time, duration) {
    (Some(target), Some(start), Some(start_time), Some(duration)) =>
      if duration <= 0.0 {
        self.set_offset(target)
        self.smooth_target.set(None)
        self.smooth_start.set(None)
        self.smooth_start_time_ms.set(None)
        self.smooth_duration_ms.set(None)
      } else {
        let progress = clamp_double((time_ms - start_time) / duration, 0.0, 1.0)
        let eased = Easing::EaseOut.sample(progress)
        let x = start.x + (target.x - start.x) * eased
        let y = start.y + (target.y - start.y) * eased
        self.set_offset(Point::new(x~, y~))
        if progress >= 1.0 {
          self.smooth_target.set(None)
          self.smooth_start.set(None)
          self.smooth_start_time_ms.set(None)
          self.smooth_duration_ms.set(None)
        } else {
          self.smooth_start_time_ms.set(Some(time_ms))
          self.smooth_start.set(Some(self.offset()))
        }
      }
    _ => ()
  }
}

///|
pub fn ScrollState::is_smooth_scrolling(self : ScrollState) -> Bool {
  self.smooth_target.get() is Some(_)
}

///|
pub fn ScrollState::scroll_to(
  self : ScrollState,
  x~ : Double,
  y~ : Double,
) -> Unit {
  self.set_offset(Point::new(x~, y~))
}

///|
pub fn FocusState::new(key? : String? = None) -> FocusState {
  { key_slot: State::new(key) }
}

///|
pub fn FocusState::focused_key(self : FocusState) -> String? {
  self.key_slot.get()
}

///|
pub fn FocusState::focus(self : FocusState, key : String) -> Unit {
  self.key_slot.set(Some(key))
}

///|
pub fn FocusState::clear(self : FocusState) -> Unit {
  self.key_slot.set(None)
}

///|
pub fn FocusState::binding(self : FocusState) -> Binding[String?] {
  self.key_slot.binding()
}

///|
pub fn NavigationState::new(initial? : String = "root") -> NavigationState {
  { stack_slot: State::new([initial]) }
}

///|
pub fn NavigationState::stack(self : NavigationState) -> Array[String] {
  self.stack_slot.get()
}

///|
pub fn NavigationState::current(self : NavigationState) -> String {
  let stack = self.stack()
  if stack.length() == 0 {
    ""
  } else {
    stack[stack.length() - 1]
  }
}

///|
pub fn NavigationState::push(self : NavigationState, route : String) -> Unit {
  self.stack_slot.update(stack => {
    let next = stack.copy()
    next.push(route)
    next
  })
}

///|
pub fn NavigationState::pop(self : NavigationState) -> Bool {
  let stack = self.stack()
  if stack.length() <= 1 {
    false
  } else {
    let next : Array[String] = []
    for index, route in stack {
      if index + 1 < stack.length() {
        next.push(route)
      }
    }
    self.stack_slot.set(next)
    true
  }
}

///|
pub fn NavigationState::replace(self : NavigationState, route : String) -> Unit {
  self.stack_slot.set([route])
}

///|
pub fn NavigationState::binding(
  self : NavigationState,
) -> Binding[Array[String]] {
  self.stack_slot.binding()
}

///|
pub fn ComponentContext::watch_scroll(
  self : ComponentContext,
  state : ScrollState,
) -> Point {
  self.watch(state.offset_slot)
}

///|
pub fn ComponentContext::watch_focus(
  self : ComponentContext,
  state : FocusState,
) -> String? {
  self.watch(state.key_slot)
}

///|
pub fn ComponentContext::watch_navigation(
  self : ComponentContext,
  state : NavigationState,
) -> Array[String] {
  self.watch(state.stack_slot)
}