///|
/// A typed handle to a React Context object.
struct ReactContext[T] {
  js_value : @dom.JsObscure
  _default_value : T
}

///|
extern "js" fn react_create_context(
  default_value : @dom.JsObscure,
) -> @dom.JsObscure =
  #| (defaultValue) => globalThis.React.createContext(defaultValue)

///|
extern "js" fn react_use_context(context : @dom.JsObscure) -> @dom.JsObscure =
  #| (context) => globalThis.React.useContext(context)

///|
extern "js" fn react_context_provider(
  context : @dom.JsObscure,
  value : @dom.JsObscure,
  children : FixedArray[@dom.JsObscure],
) -> @dom.JsObscure =
  #| (context, value, children) => globalThis.React.createElement(context, { value }, ...children)

///|
/// Creates a typed React Context with the value returned when no matching
/// provider exists above a consumer.
///
/// Create contexts outside ordinary render paths, or memoize them when their
/// lifetime intentionally belongs to a component instance.
pub fn[T] create_context(default_value : T) -> ReactContext[T] {
  ReactContext::{
    js_value: react_create_context(@dom.v_to_js_obscure(default_value)),
    _default_value: default_value,
  }
}

///|
/// Reads and subscribes to the nearest provider value for `context`.
/// This is a React Hook and must only be called while rendering a component.
pub fn[T] use_context(context : ReactContext[T]) -> T {
  react_use_context(context.js_value) |> @dom.js_obscure_to_v
}

///|
/// Provides `value` to descendant components without adding a DOM wrapper.
pub fn[T] ReactContext::provider(
  self : ReactContext[T],
  value : T,
  children : Array[VirtualNode],
) -> VirtualNode {
  JsNode(
    react_context_provider(
      self.js_value,
      @dom.v_to_js_obscure(value),
      FixedArray::from_array(children.map(fn(child) { child.to_js_obscure() })),
    ),
  )
}

///|
/// Returns the underlying Context object for explicit JavaScript interoperation.
pub fn[T] ReactContext::to_js_obscure(self : ReactContext[T]) -> @dom.JsObscure {
  self.js_value
}