// Core reactive system - link/unlink/propagate algorithms
// Port of alien-signals system.ts

// Global state using Ref for mutability

///|
let cycle : Ref[Int] = Ref::new(0)

///|
let batch_depth : Ref[Int] = Ref::new(0)

///|
let notify_index : Ref[Int] = Ref::new(0)

///|
let queued_length : Ref[Int] = Ref::new(0)

///|
let active_sub : Ref[ReactiveNode?] = Ref::new(None)

// Effect queue (fixed-size array for simplicity)

///|
let queued : Array[EffectNode?] = Array::make(1024, None)

// Callback-based effect queue for simpler effect tracking

///|
let callback_queue : Array[() -> Unit] = []

///|
let callback_queue_nodes : Array[ReactiveNode] = [] // Track which nodes are queued

///|
/// Get current active subscriber
pub fn get_active_sub() -> ReactiveNode? {
  active_sub.val
}

///|
/// Set active subscriber, returns previous
pub fn set_active_sub(sub : ReactiveNode?) -> ReactiveNode? {
  let prev = active_sub.val
  active_sub.val = sub
  prev
}

///|
/// Get current batch depth
pub fn get_batch_depth() -> Int {
  batch_depth.val
}

///|
/// Start a batch update
pub fn start_batch() -> Unit {
  batch_depth.val = batch_depth.val + 1
}

///|
/// End a batch update
pub fn end_batch() -> Unit {
  batch_depth.val = batch_depth.val - 1
  if batch_depth.val == 0 {
    flush()
  }
}

///|
/// Get current cycle
pub fn get_cycle() -> Int {
  cycle.val
}

///|
/// Increment cycle
pub fn inc_cycle() -> Unit {
  cycle.val = cycle.val + 1
}

// =============================================================================
// Link operations
// =============================================================================

///|
/// Create a link between dependency and subscriber
pub fn link(dep : ReactiveNode, sub : ReactiveNode, version : Int) -> Unit {
  let prev_dep = sub.deps_tail

  // Check if already linked to same dep (optimization)
  match prev_dep {
    Some(pd) => if physical_equal(pd.dep, dep) { return }
    None => ()
  }

  // Check next dep slot
  let next_dep = match prev_dep {
    Some(pd) => pd.next_dep
    None => sub.deps
  }
  match next_dep {
    Some(nd) =>
      if physical_equal(nd.dep, dep) {
        nd.version = version
        sub.deps_tail = Some(nd)
        return
      }
    None => ()
  }

  // Check if dep already has this sub with same version
  match dep.subs_tail {
    Some(ps) =>
      if ps.version == version && physical_equal(ps.sub, sub) {
        return
      }
    None => ()
  }

  // Create new link
  let new_link : Link = {
    version,
    dep,
    sub,
    prev_dep,
    next_dep,
    prev_sub: dep.subs_tail,
    next_sub: None,
  }

  // Update sub's dep list
  sub.deps_tail = Some(new_link)
  if next_dep is Some(nd) {
    nd.prev_dep = Some(new_link)
  }
  if prev_dep is Some(pd) {
    pd.next_dep = Some(new_link)
  } else {
    sub.deps = Some(new_link)
  }

  // Update dep's sub list
  if new_link.prev_sub is Some(ps) {
    ps.next_sub = Some(new_link)
  } else {
    dep.subs = Some(new_link)
  }
  dep.subs_tail = Some(new_link)
}

///|
/// Unlink a dependency link, returns next dep link
pub fn unlink(lnk : Link, sub : ReactiveNode) -> Link? {
  let dep = lnk.dep
  let prev_dep = lnk.prev_dep
  let next_dep = lnk.next_dep
  let next_sub = lnk.next_sub
  let prev_sub = lnk.prev_sub

  // Update sub's dep list
  if next_dep is Some(nd) {
    nd.prev_dep = prev_dep
  } else {
    sub.deps_tail = prev_dep
  }
  if prev_dep is Some(pd) {
    pd.next_dep = next_dep
  } else {
    sub.deps = next_dep
  }

  // Update dep's sub list
  if next_sub is Some(ns) {
    ns.prev_sub = prev_sub
  } else {
    dep.subs_tail = prev_sub
  }
  if prev_sub is Some(ps) {
    ps.next_sub = next_sub
  } else {
    dep.subs = next_sub
    // If no more subscribers, call unwatched
    if next_sub is None {
      unwatched(dep)
    }
  }
  next_dep
}

///|
/// Called when a node has no more subscribers
fn unwatched(node : ReactiveNode) -> Unit {
  let flags = node.flags.get_value()
  let is_mutable = (flags & ReactiveFlags::Mutable.to_int()) != 0
  if not(is_mutable) {
    // Effect scope - clean up
    cleanup_node(node)
  } else if node.deps_tail is Some(_) {
    // Computed - mark as lazy again
    node.deps_tail = None
    node.flags.set_value(
      ReactiveFlags::Mutable.to_int() | ReactiveFlags::Dirty.to_int(),
    )
    purge_deps(node)
  }
}

///|
/// Clean up a node (effect scope)
fn cleanup_node(node : ReactiveNode) -> Unit {
  node.deps_tail = None
  node.flags.set_value(ReactiveFlags::None.to_int())
  purge_deps(node)
  if node.subs is Some(sub) {
    let _ = unlink(sub, sub.sub)

  }
}

///|
/// Purge unused deps after deps_tail
pub fn purge_deps(sub : ReactiveNode) -> Unit {
  let mut current_dep = match sub.deps_tail {
    Some(dt) => dt.next_dep
    None => sub.deps
  }
  while current_dep is Some(d) {
    current_dep = unlink(d, sub)
  }
}

// =============================================================================
// Propagation
// =============================================================================

///|
/// Propagate changes through the dependency graph (simplified version)
pub fn propagate(start_link : Link) -> Unit {
  let link_ref : Ref[Link?] = Ref::new(Some(start_link))
  let next_ref : Ref[Link?] = Ref::new(start_link.next_sub)
  let stack : Array[(Link?, Link?)] = []
  let mut running = true
  while running {
    match link_ref.val {
      None =>
        // Try to pop from stack
        if stack.length() > 0 {
          match stack.pop() {
            Some((l, _)) => {
              link_ref.val = l
              // Restore next_ref from the popped link's next_sub
              next_ref.val = match l {
                Some(lnk) => lnk.next_sub
                None => None
              }
            }
            None => running = false
          }
        } else {
          running = false
        }
      Some(lnk) => {
        let sub = lnk.sub
        let flags = sub.flags.get_value()
        let mutable_flag = ReactiveFlags::Mutable.to_int()
        let watching_flag = ReactiveFlags::Watching.to_int()
        let recursed_check_flag = ReactiveFlags::RecursedCheck.to_int()
        let recursed_flag = ReactiveFlags::Recursed.to_int()
        let dirty_flag = ReactiveFlags::Dirty.to_int()
        let pending_flag = ReactiveFlags::Pending.to_int()
        let mut new_flags = flags
        let mut should_notify = false
        let mut should_propagate_subs = false
        if (
            flags &
            (recursed_check_flag | recursed_flag | dirty_flag | pending_flag)
          ) ==
          0 {
          new_flags = flags | pending_flag
          sub.flags.set_value(new_flags)
        } else if (flags & (recursed_check_flag | recursed_flag)) == 0 {
          new_flags = ReactiveFlags::None.to_int()
        } else if (flags & recursed_check_flag) == 0 {
          new_flags = (flags & (recursed_flag ^ -1)) | pending_flag
          sub.flags.set_value(new_flags)
        } else if (flags & (dirty_flag | pending_flag)) == 0 &&
          is_valid_link(lnk, sub) {
          new_flags = flags | recursed_flag | pending_flag
          sub.flags.set_value(new_flags)
          new_flags = new_flags & mutable_flag
        } else {
          new_flags = ReactiveFlags::None.to_int()
        }
        if (new_flags & watching_flag) != 0 {
          should_notify = true
        }
        if (new_flags & mutable_flag) != 0 {
          should_propagate_subs = true
        }
        if should_notify {
          notify_effect(sub)
        }
        if should_propagate_subs {
          match sub.subs {
            Some(sub_subs) => {
              match sub_subs.next_sub {
                Some(_) => {
                  stack.push((next_ref.val, None))
                  next_ref.val = sub_subs.next_sub
                }
                None => ()
              }
              link_ref.val = Some(sub_subs)
            }
            None => {
              link_ref.val = next_ref.val
              match link_ref.val {
                Some(l) => next_ref.val = l.next_sub
                None => next_ref.val = None
              }
            }
          }
        } else {
          link_ref.val = next_ref.val
          match link_ref.val {
            Some(l) => next_ref.val = l.next_sub
            None => next_ref.val = None
          }
        }
      }
    }
  }
}

///|
/// Check if link is valid for the subscriber
fn is_valid_link(check_link : Link, sub : ReactiveNode) -> Bool {
  let mut current_link = sub.deps_tail
  while current_link is Some(l) {
    if physical_equal(l, check_link) {
      return true
    }
    current_link = l.prev_dep
  }
  false
}

///|
/// Shallow propagate - mark direct subscribers as dirty
pub fn shallow_propagate(start_link : Link) -> Unit {
  let mut current_link : Link? = Some(start_link)
  while current_link is Some(lnk) {
    let sub = lnk.sub
    let flags = sub.flags.get_value()
    let pending_flag = ReactiveFlags::Pending.to_int()
    let dirty_flag = ReactiveFlags::Dirty.to_int()
    let watching_flag = ReactiveFlags::Watching.to_int()
    let recursed_check_flag = ReactiveFlags::RecursedCheck.to_int()
    if (flags & (pending_flag | dirty_flag)) == pending_flag {
      sub.flags.set_value(flags | dirty_flag)
      if (flags & (watching_flag | recursed_check_flag)) == watching_flag {
        notify_effect(sub)
      }
    }
    current_link = lnk.next_sub
  }
}

// =============================================================================
// Check Dirty (simplified - doesn't recurse)
// =============================================================================

///|
/// Check if any dependency is dirty and needs update
pub fn check_dirty(
  start_link : Link,
  sub : ReactiveNode,
  update_fn : (ReactiveNode) -> Bool,
) -> Bool {
  let mut current_link : Link? = Some(start_link)
  let dirty_flag = ReactiveFlags::Dirty.to_int()
  let mutable_flag = ReactiveFlags::Mutable.to_int()
  while current_link is Some(lnk) {
    let dep = lnk.dep
    let flags = dep.flags.get_value()

    // Check if sub is dirty
    if (sub.flags.get_value() & dirty_flag) != 0 {
      return true
    }

    // Dep is mutable and dirty - update it
    if (flags & (mutable_flag | dirty_flag)) == (mutable_flag | dirty_flag) {
      if update_fn(dep) {
        if dep.subs is Some(subs) && subs.next_sub is Some(_) {
          shallow_propagate(subs)
        }
        return true
      }
    }
    current_link = lnk.next_dep
  }
  false
}

// =============================================================================
// Effect Queue
// =============================================================================

///|
/// Notify an effect - queue it for execution
fn notify_effect(node : ReactiveNode) -> Unit {
  if node.effect_callback is Some(callback) {
    // Check if already queued (avoid duplicates)
    let mut already_queued = false
    for n in callback_queue_nodes {
      if physical_equal(n, node) {
        already_queued = true
        break
      }
    }
    if not(already_queued) {
      callback_queue.push(callback)
      callback_queue_nodes.push(node)
      node.flags.unset(Watching)
    }
  }
}

///|
/// Queue an effect for execution
pub fn queue_effect(eff : EffectNode) -> Unit {
  let insert_index = queued_length.val
  queued[insert_index] = Some(eff)
  eff.node.flags.unset(Watching)
  queued_length.val = queued_length.val + 1
}

///|
/// Flush the effect queue
pub fn flush() -> Unit {
  // Run EffectNode queue (legacy)
  while notify_index.val < queued_length.val {
    match queued[notify_index.val] {
      Some(eff) => {
        queued[notify_index.val] = None
        notify_index.val = notify_index.val + 1
        run_effect(eff)
      }
      None => notify_index.val = notify_index.val + 1
    }
  }

  // Reset legacy queue
  notify_index.val = 0
  queued_length.val = 0

  // Run callback queue
  while callback_queue.length() > 0 {
    let callback = callback_queue.remove(0)
    let _ = callback_queue_nodes.remove(0)
    callback()
  }
}

///|
/// Run an effect
fn run_effect(e : EffectNode) -> Unit {
  let flags = e.node.flags.get_value()
  let dirty_flag = ReactiveFlags::Dirty.to_int()
  let pending_flag = ReactiveFlags::Pending.to_int()
  let is_deps_dirty = match e.node.deps {
    Some(deps) => check_dirty(deps, e.node, fn(_) { false })
    None => false
  }
  let should_run = (flags & dirty_flag) != 0 ||
    ((flags & pending_flag) != 0 && is_deps_dirty)
  if should_run {
    inc_cycle()
    e.node.deps_tail = None
    e.node.flags.set_value(
      ReactiveFlags::Watching.to_int() | ReactiveFlags::RecursedCheck.to_int(),
    )
    let prev_sub = set_active_sub(Some(e.node))
    (e.run_fn)()
    let _ = set_active_sub(prev_sub)
    let current_flags = e.node.flags.get_value()
    e.node.flags.set_value(
      current_flags & (ReactiveFlags::RecursedCheck.to_int() ^ -1),
    )
    purge_deps(e.node)
  } else {
    e.node.flags.set_value(ReactiveFlags::Watching.to_int())
  }
}