///|
/// Package-private fixed-dependency `Derived` fast path.
///
/// This is deliberately not a public API. Static derived cells reuse the
/// existing pull backend (`MemoData` / `PullMemo`) and target facade handle, but
/// install their fixed dependency list once and recompute without opening a
/// dynamic tracking frame or diffing dependencies.

///|
priv struct StaticDerivedSource[T] {
  rt : Runtime
  id : CellId
  read_declared : (CellId) -> T raise Failure
}

///|
fn Runtime::enter_static_recompute(self : Runtime) -> Unit {
  let previous_runtime_id = if self.core.tracking.stack.is_empty() &&
    self.static_recompute_depth == 0 {
    None
  } else {
    @kernel.get_current_computing_runtime_id()
  }
  self.static_recompute_depth = self.static_recompute_depth + 1
  self.static_recompute_tracking_floors.push(self.core.tracking.stack.length())
  self.static_recompute_previous_runtime_ids.push(previous_runtime_id)
  @kernel.set_current_computing_runtime_id(Some(self.core.runtime_id))
}

///|
fn Runtime::leave_static_recompute(self : Runtime) -> Unit {
  guard self.static_recompute_depth > 0 &&
    !self.static_recompute_tracking_floors.is_empty() &&
    !self.static_recompute_previous_runtime_ids.is_empty() else {
    abort("static recompute state underflow")
  }
  ignore(self.static_recompute_tracking_floors.pop())
  let previous_runtime_id = self.static_recompute_previous_runtime_ids
    .pop()
    .unwrap()
  @kernel.set_current_computing_runtime_id(previous_runtime_id)
  self.static_recompute_depth = self.static_recompute_depth - 1
}

///|
fn Runtime::normalize_static_derived_deps(
  self : Runtime,
  deps : Array[CellId],
) -> Array[CellId] {
  let seen : @hashset.HashSet[CellId] = @hashset.HashSet([])
  let normalized : Array[CellId] = []
  for dep in deps {
    self.validate_cell(dep, "static derived dependency")
    if !seen.contains(dep) {
      seen.add(dep)
      normalized.push(dep)
    }
  }
  normalized
}

///|
fn[T] Memo::_create_static(
  rt : Runtime,
  deps : Array[CellId],
  compute : (CellId) -> T raise Failure,
  label? : String,
  backdate_eq : (T, T) -> Bool,
) -> Memo[T] {
  let fixed_deps = rt.normalize_static_derived_deps(deps)
  let memo_idx = rt.pull.free_memos.pop().unwrap_or(rt.pull.memos.length())
  let cell_id = rt.alloc_cell_id(PullMemo(memo_idx))
  let memo : Memo[T] = {
    label,
    rt,
    cell_id,
    compute: () => compute(cell_id),
    is_static: true,
    backdate_eq,
    value: None,
  }
  let data : MemoData = {
    meta: {
      cell_id,
      label,
      changed_at: Revision::initial(),
      durability: compute_durability(rt, fixed_deps),
      subscribers: @hashset.HashSet([]),
      push_reachable_count: 0,
    },
    compute: () => memo.recompute_inner(),
    verified_at: Revision::initial(),
    dependencies: fixed_deps,
    in_progress: false,
    on_change: None,
    is_hybrid: false,
    accumulator_reads: @hashmap.HashMap([]),
    has_been_computed: false,
  }
  if memo_idx < rt.pull.memos.length() {
    rt.pull.memos[memo_idx] = data
  } else {
    rt.pull.memos.push(data)
  }
  let ops : &CellOps = rt.pull.memos[memo_idx]
  rt.core.cell_ops.push(ops)
  let lifecycle : &CellLifecycle = rt.pull.memos[memo_idx]
  rt.cell_lifecycle.push(lifecycle)
  for dep in fixed_deps {
    rt.add_subscriber(dep, cell_id)
  }
  memo
}

///|
fn[T] Runtime::install_static_derived(
  self : Runtime,
  deps : Array[CellId],
  compute : (CellId) -> T raise Failure,
  backdate_eq : (T, T) -> Bool,
  label? : String,
) -> Derived[T] {
  { inner: Memo::_create_static(self, deps, compute, label?, backdate_eq) }
}

///|
fn[T] static_derived(
  rt : Runtime,
  deps : Array[CellId],
  compute : () -> T raise Failure,
  backdate_eq : (T, T) -> Bool,
  label? : String,
) -> Derived[T] {
  rt.install_static_derived(deps, _dependent => compute(), backdate_eq, label?)
}

///|
fn[T] static_source_input(source : Input[T]) -> StaticDerivedSource[T] {
  {
    rt: source.inner.rt,
    id: source.inner.id(),
    read_declared: _dependent => source.peek(),
  }
}

///|
fn[T] static_source_input_field(
  source : InputField[T],
) -> StaticDerivedSource[T] {
  {
    rt: source.inner.signal.rt,
    id: source.inner.id(),
    read_declared: _dependent => source.peek(),
  }
}

///|
fn[T] static_source_derived(source : Derived[T]) -> StaticDerivedSource[T] {
  {
    rt: source.inner.rt,
    id: source.id(),
    read_declared: dependent => {
      source.inner.read_declared_fixed_dep_or_abort(dependent)
    },
  }
}

///|
fn[A, T : Eq] static_derived_map_source1(
  source : StaticDerivedSource[A],
  f : (A) -> T raise Failure,
  label? : String,
) -> Derived[T] {
  source.rt.install_static_derived(
    [source.id],
    dependent => f((source.read_declared)(dependent)),
    (a, b) => a == b,
    label?,
  )
}

///|
fn[A, B, T : Eq] static_derived_map_sources2(
  left : StaticDerivedSource[A],
  right : StaticDerivedSource[B],
  f : (A, B) -> T raise Failure,
  label? : String,
) -> Derived[T] {
  left.rt.install_static_derived(
    [left.id, right.id],
    dependent => {
      f((left.read_declared)(dependent), (right.read_declared)(dependent))
    },
    (a, b) => a == b,
    label?,
  )
}

///|
fn[A, B, C, T : Eq] static_derived_map_sources3(
  first : StaticDerivedSource[A],
  second : StaticDerivedSource[B],
  third : StaticDerivedSource[C],
  f : (A, B, C) -> T raise Failure,
  label? : String,
) -> Derived[T] {
  first.rt.install_static_derived(
    [first.id, second.id, third.id],
    dependent => {
      f(
        (first.read_declared)(dependent),
        (second.read_declared)(dependent),
        (third.read_declared)(dependent),
      )
    },
    (a, b) => a == b,
    label?,
  )
}

///|
fn[A, T : Eq] static_derived_map1(
  source : Input[A],
  f : (A) -> T raise Failure,
  label? : String,
) -> Derived[T] {
  static_derived_map_source1(static_source_input(source), f, label?)
}

///|
fn[A, T : Eq] static_derived_map_input_field1(
  source : InputField[A],
  f : (A) -> T raise Failure,
  label? : String,
) -> Derived[T] {
  static_derived_map_source1(static_source_input_field(source), f, label?)
}

///|
fn[A, T : Eq] static_derived_map_derived1(
  source : Derived[A],
  f : (A) -> T raise Failure,
  label? : String,
) -> Derived[T] {
  static_derived_map_source1(static_source_derived(source), f, label?)
}

///|
fn[A, B, T : Eq] static_derived_map2(
  left : Input[A],
  right : Input[B],
  f : (A, B) -> T raise Failure,
  label? : String,
) -> Derived[T] {
  static_derived_map_sources2(
    static_source_input(left),
    static_source_input(right),
    f,
    label?,
  )
}

///|
fn[A, B, C, T : Eq] static_derived_map3(
  first : Input[A],
  second : Input[B],
  third : Input[C],
  f : (A, B, C) -> T raise Failure,
  label? : String,
) -> Derived[T] {
  static_derived_map_sources3(
    static_source_input(first),
    static_source_input(second),
    static_source_input(third),
    f,
    label?,
  )
}