///|
pub struct BuildContext {
  runtime : RuntimeState
  component_id : ElementId
  subscriptions : Array[StateSubscription]
  effects : Array[ComponentEffect]
  old_effects : Array[ComponentEffect]
  environment : Environment
}

///|
priv struct Component {
  build : (BuildContext) -> ViewSpec
}

///|
priv struct ComponentBuildResult {
  child_spec : ViewSpec
  subscriptions : Array[StateSubscription]
  effects : Array[ComponentEffect]
}

///|
fn Component::new(build : (BuildContext) -> ViewSpec) -> Component {
  { build, }
}

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

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

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

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

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

///|
pub fn BuildContext::on_update_with_reason(
  self : BuildContext,
  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 BuildContext::on_mount(
  self : BuildContext,
  key~ : String,
  effect : () -> Unit,
) -> Unit {
  self.run_effect(key="mount:\{key}", () => {
    effect()
    None
  })
}

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

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

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

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

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

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

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

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

///|
pub fn BuildContext::viewport_size(self : BuildContext) -> Size {
  self.runtime.size
}

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

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

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

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

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

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

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

///|
fn build_component_spec(
  component : Component,
  runtime : RuntimeState,
  component_id : ElementId,
  old_subscriptions : Array[StateSubscription],
  old_effects : Array[ComponentEffect],
) -> ComponentBuildResult {
  for subscription in old_subscriptions {
    subscription.cancel()
  }
  let subscriptions : Array[StateSubscription] = []
  let effects : Array[ComponentEffect] = []
  let context = BuildContext::{
    runtime,
    component_id,
    subscriptions,
    effects,
    old_effects,
    environment: runtime.environment,
  }
  let child_spec = (component.build)(context)
  for effect in old_effects {
    if !effect.reused {
      effect.cancel_with_reason(ComponentEffectCancelReason::EffectKeyChanged)
    }
    effect.reused = false
  }
  { child_spec, subscriptions, effects }
}