// Reactive Context - Tracks current running effect for auto-subscription

///|
/// Global effect ID counter
let effect_id_counter : Ref[Int] = Ref::new(0)

///|
/// Generate a new unique effect ID
pub fn new_effect_id() -> Int {
  let id = effect_id_counter.val
  effect_id_counter.val = id + 1
  id
}

///|
/// Owner - manages lifecycle of reactive computations (Solid.js style)
pub struct Owner {
  id : Int
  parent : Owner?
  children : Array[Owner]
  cleanups : Array[() -> Unit]
  disposers : Array[() -> Unit]
  mut disposed : Bool
}

///|
/// Create a new Owner
pub fn Owner::new(parent : Owner?) -> Owner {
  let owner : Owner = {
    id: new_effect_id(),
    parent,
    children: [],
    cleanups: [],
    disposers: [],
    disposed: false,
  }
  // Register with parent
  if parent is Some(p) {
    p.children.push(owner)
  }
  owner
}

///|
/// Dispose this owner and all its children
pub fn Owner::dispose(self : Owner) -> Unit {
  if self.disposed {
    return
  }
  self.disposed = true
  // Dispose children first (depth-first)
  for i = self.children.length() - 1; i >= 0; i = i - 1 {
    self.children[i].dispose()
  }
  // Run disposers (for effects)
  for i = self.disposers.length() - 1; i >= 0; i = i - 1 {
    self.disposers[i]()
  }
  // Run cleanups
  for i = self.cleanups.length() - 1; i >= 0; i = i - 1 {
    self.cleanups[i]()
  }
  // Clear arrays
  self.children.clear()
  self.disposers.clear()
  self.cleanups.clear()
  // Remove from parent's children
  if self.parent is Some(p) {
    p.children.retain(fn(child) { child.id != self.id })
  }
}

///|
/// Reactive context - tracks currently running computation
priv struct ReactiveContext {
  mut current_owner : Owner?
  mut current_cleanups : Array[() -> Unit]?
  mut batch_depth : Int
}

///|
/// Global reactive context instance
let reactive_context : ReactiveContext = {
  current_owner: None,
  current_cleanups: None,
  batch_depth: 0,
}

///|
/// Run a function without tracking (useful for avoiding circular deps).
/// Signal reads inside this function won't create subscriptions.
pub fn[T] untracked(f : () -> T) -> T {
  // Disable core tracking
  let prev_core = set_active_sub(None)
  let result = f()
  let _ = set_active_sub(prev_core)
  result
}

///|
/// Start a batch update - effects won't run until batch ends
pub fn batch_start() -> Unit {
  reactive_context.batch_depth = reactive_context.batch_depth + 1
  start_batch()
}

///|
/// End a batch update - run all pending effects
pub fn batch_end() -> Unit {
  reactive_context.batch_depth = reactive_context.batch_depth - 1
  end_batch()
}

///|
/// Run a function in a batch - all signal updates are batched.
/// Effects only run once after all updates complete.
pub fn[T] batch(f : () -> T) -> T {
  batch_start()
  let result = f()
  batch_end()
  result
}

///|
/// Check if we're currently inside a batch
pub fn is_batching() -> Bool {
  reactive_context.batch_depth > 0
}

///|
/// Register a cleanup function to run when the current scope disposes.
/// Works in:
/// - Effects: cleanup runs before effect re-runs or when effect disposes
/// - Components: cleanup runs when component unmounts
/// (Solid.js style - can be called directly in component body)
pub fn on_cleanup(cleanup : () -> Unit) -> Unit {
  // First priority: effect-level cleanup tracking
  if reactive_context.current_cleanups is Some(cleanups) {
    cleanups.push(cleanup)
  } else if reactive_context.current_owner is Some(owner) {
    // Second priority: owner-level cleanup (for component body)
    owner.cleanups.push(cleanup)
  }
  // No tracking scope, ignore
}

///|
/// Set the current cleanup array (used internally by effect)
pub fn set_current_cleanups(
  cleanups : Array[() -> Unit]?,
) -> Array[() -> Unit]? {
  let prev = reactive_context.current_cleanups
  reactive_context.current_cleanups = cleanups
  prev
}

///|
/// Run a function with cleanup tracking enabled
pub fn[T] run_with_cleanup_tracking(
  cleanups : Array[() -> Unit],
  f : () -> T,
) -> T {
  let prev = set_current_cleanups(Some(cleanups))
  let result = f()
  reactive_context.current_cleanups = prev
  result
}

// ============================================================================
// Owner-based scope management (Solid.js style)
// ============================================================================

///|
/// Get the current owner (if any)
pub fn get_owner() -> Owner? {
  reactive_context.current_owner
}

///|
/// Run a function with a specific owner as current
pub fn[T] run_with_owner(owner : Owner, f : () -> T) -> T {
  let prev = reactive_context.current_owner
  reactive_context.current_owner = Some(owner)
  let result = f()
  reactive_context.current_owner = prev
  result
}

///|
/// Create a new reactive root scope.
/// The function receives a dispose callback that cleans up all effects.
/// Returns the result of the function.
pub fn[T] create_root(f : (() -> Unit) -> T) -> T {
  let owner = Owner::new(reactive_context.current_owner)
  let dispose = fn() { owner.dispose() }
  run_with_owner(owner, fn() { f(dispose) })
}

///|
/// Create a reactive root and return both result and dispose function
pub fn[T] create_root_with_dispose(f : () -> T) -> (T, () -> Unit) {
  let owner = Owner::new(reactive_context.current_owner)
  let dispose = fn() { owner.dispose() }
  let result = run_with_owner(owner, f)
  (result, dispose)
}

///|
/// Register a disposer with the current owner
/// Called internally by effect() to register its dispose function
pub fn register_disposer(disposer : () -> Unit) -> Unit {
  if reactive_context.current_owner is Some(owner) {
    owner.disposers.push(disposer)
  }
}

///|
/// Register a cleanup with the current owner (alternative to onCleanup in effect)
pub fn register_owner_cleanup(cleanup : () -> Unit) -> Unit {
  if reactive_context.current_owner is Some(owner) {
    owner.cleanups.push(cleanup)
  }
}

///|
/// Check if currently inside an owner scope
pub fn has_owner() -> Bool {
  reactive_context.current_owner is Some(_)
}

///|
/// Run a function with a captured parent owner context (if any)
/// This is useful for rendering child components that need to inherit the parent's owner.
/// If captured_owner is Some, runs with that owner as context.
/// If captured_owner is None, runs directly without owner context.
pub fn[T] with_parent_owner(captured_owner : Owner?, f : () -> T) -> T {
  match captured_owner {
    Some(parent) => run_with_owner(parent, f)
    None => f()
  }
}