///|
pub struct ComponentContext {
  priv component_id : ElementId
  priv request_component_rebuild : (ElementId) -> Unit
  priv viewport_size : Size
  priv saveable_state : SaveableStateStore
  priv subscriptions : Array[StateSubscription]
  priv effects : Array[ComponentEffect]
  priv old_effects : Array[ComponentEffect]
  priv environment : Environment
}

///|
pub(all) struct ComponentRuntimeContextInput {
  component_id : ElementId
  request_component_rebuild : (ElementId) -> Unit
  viewport_size : Size
  saveable_state : SaveableStateStore
  subscriptions : Array[StateSubscription]
  effects : Array[ComponentEffect]
  old_effects : Array[ComponentEffect]
  environment : Environment
}

///|
pub fn ComponentContext::from_runtime(
  input : ComponentRuntimeContextInput,
) -> ComponentContext {
  {
    component_id: input.component_id,
    request_component_rebuild: input.request_component_rebuild,
    viewport_size: input.viewport_size,
    saveable_state: input.saveable_state,
    subscriptions: input.subscriptions,
    effects: input.effects,
    old_effects: input.old_effects,
    environment: input.environment,
  }
}

///|
pub fn[T] ComponentContext::watch(
  self : ComponentContext,
  state : State[T],
) -> T {
  let component_id = self.component_id
  let subscription = state.subscribe(() => {
    (self.request_component_rebuild)(component_id)
  })
  self.subscriptions.push(subscription)
  state.get()
}

///|
pub fn[T] ComponentContext::watch_derived(
  self : ComponentContext,
  state : DerivedState[T],
) -> T {
  let component_id = self.component_id
  for
    subscription in state.subscribe(() => {
      (self.request_component_rebuild)(component_id)
    }) {
    self.subscriptions.push(subscription)
  }
  state.get()
}

///|
pub fn[T] ComponentContext::binding(
  self : ComponentContext,
  state : State[T],
) -> Binding[T] {
  let component_id = self.component_id
  let subscription = state.subscribe(() => {
    (self.request_component_rebuild)(component_id)
  })
  self.subscriptions.push(subscription)
  state.binding()
}

///|
pub fn ComponentContext::view_environment(
  self : ComponentContext,
) -> ViewEnvironment {
  ViewEnvironment::new(
    viewport_size=self.viewport_size(),
    environment=self.environment,
  )
}

///|
pub fn ComponentContext::on_update(
  self : ComponentContext,
  key~ : String,
  deps~ : Array[String],
  effect : () -> (() -> Unit)?,
) -> Unit {
  let deps_key = deps.join("|")
  self.run_effect(key="update:\{key}:\{deps_key}", effect)
}

///|
pub fn ComponentContext::on_update_with_reason(
  self : ComponentContext,
  key~ : String,
  deps~ : Array[String],
  effect : () -> ((ComponentEffectCancelReason) -> Unit)?,
) -> Unit {
  let deps_key = deps.join("|")
  self.run_effect_with_reason(key="update:\{key}:\{deps_key}", effect)
}

///|
pub fn ComponentContext::on_mount(
  self : ComponentContext,
  key~ : String,
  effect : () -> Unit,
) -> Unit {
  self.run_effect(key="mount:\{key}", () => {
    effect()
    None
  })
}

///|
pub fn ComponentContext::on_unmount_with_reason(
  self : ComponentContext,
  key~ : String,
  cleanup : (ComponentEffectCancelReason) -> Unit,
) -> Unit {
  self.run_effect_with_reason(key="dispose:\{key}", () => Some(cleanup))
}

///|
pub fn ComponentContext::on_unmount(
  self : ComponentContext,
  key~ : String,
  cleanup : () -> Unit,
) -> Unit {
  self.on_dispose(key~, cleanup)
}

///|
pub fn ComponentContext::on_dispose(
  self : ComponentContext,
  key~ : String,
  cleanup : () -> Unit,
) -> Unit {
  self.run_effect(key="dispose:\{key}", () => Some(cleanup))
}

///|
fn ComponentContext::take_old_effect(
  self : ComponentContext,
  key : String,
) -> ComponentEffect? {
  for effect in self.old_effects {
    if !effect.reused && effect.key == key {
      return Some(effect)
    }
  }
  None
}

///|
pub fn ComponentContext::environment(self : ComponentContext) -> Environment {
  self.environment
}

///|
pub fn ComponentContext::theme(self : ComponentContext) -> Theme {
  self.environment.theme
}

///|
pub fn ComponentContext::color_scheme(self : ComponentContext) -> ColorScheme {
  self.environment.color_scheme()
}

///|
pub fn ComponentContext::scale_factor(self : ComponentContext) -> Double {
  self.environment.scale_factor
}

///|
pub fn ComponentContext::viewport_size(self : ComponentContext) -> Size {
  self.viewport_size
}

///|
pub fn ComponentContext::text_scale(self : ComponentContext) -> Double {
  self.environment.text_scale
}

///|
pub fn ComponentContext::locale(self : ComponentContext) -> String {
  self.environment.locale
}

///|
pub fn ComponentContext::layout_direction(
  self : ComponentContext,
) -> LayoutDirection {
  self.environment.layout_direction
}

///|
pub fn ComponentContext::accessibility_contrast(
  self : ComponentContext,
) -> AccessibilityContrast {
  self.environment.accessibility_contrast
}

///|
pub fn ComponentContext::reduced_motion(self : ComponentContext) -> Bool {
  self.environment.reduced_motion
}

///|
pub fn ComponentContext::content_size_category(
  self : ComponentContext,
) -> ContentSizeCategory {
  self.environment.content_size_category
}

///|
pub fn ComponentContext::enabled(self : ComponentContext) -> Bool {
  self.environment.enabled
}

///|
struct ViewEnvironment {
  viewport_size : Size
  environment : Environment
}

///|
pub fn ViewEnvironment::new(
  viewport_size~ : Size,
  environment~ : Environment,
) -> ViewEnvironment {
  { viewport_size, environment }
}

///|
pub fn ViewEnvironment::viewport_size(self : ViewEnvironment) -> Size {
  self.viewport_size
}

///|
pub fn ViewEnvironment::environment(self : ViewEnvironment) -> Environment {
  self.environment
}