///| One effect frontier owns this memo. Only negative forwarding outcomes can

///|
/// be stored; every commit must discard them before any follow-up work.
priv struct DaeForwardingNoChangeMemo {
  entries : @hashmap.HashMap[Int32, Array[Int32]]
  mut context : DaeForwardingNoChangeContext?
}

///|
priv struct DaeForwardingNoChangeContext {
  original : DaeOriginalBoundarySnapshot
  source : @lib.Module
  snapshot : DaeModuleBoundarySnapshot
  dependencies : DaeBoundaryDependencyGraph
  touched : Array[Bool]
  changed : Bool
  module_epoch : Int32
  call_epoch : Int32
  type_epoch : Int32
  function_epochs : Array[(Int32, Int32)]
}

///|
fn DaeForwardingNoChangeMemo::new() -> DaeForwardingNoChangeMemo {
  { entries: @hashmap.new(), context: None, }
}

///|
fn DaeForwardingNoChangeMemo::invalidate(
  self : DaeForwardingNoChangeMemo,
) -> Unit {
  self.entries.clear()
  self.context = None
}

///|
fn dae_forwarding_no_change_context_matches(
  context : DaeForwardingNoChangeContext,
  original : DaeOriginalBoundarySnapshot,
  current : @lib.Module,
  touched : Array[Bool],
  changed : Bool,
  graph : DaeCurrentBoundaryGraph,
  dependencies : DaeBoundaryDependencyGraph,
) -> Bool {
  if !physical_equal(context.original, original) ||
    !physical_equal(context.source, current) ||
    !physical_equal(context.snapshot, graph.snapshot) ||
    !physical_equal(context.dependencies, dependencies) ||
    context.touched != touched ||
    context.changed != changed ||
    context.module_epoch != graph.module_epoch ||
    context.call_epoch != graph.call_epoch ||
    context.type_epoch != graph.type_epoch ||
    context.function_epochs.length() != graph.function_epochs.length() {
    return false
  }
  for def_idx in 0.. DaeWorkOutcome {
  // Forwarding currently reads the complete component and frontier context;
  // it does not read uniform-value options. Nondefault items use the full path
  // so a future option extension cannot silently reuse an older proof.
  let cacheable = item.family == DaeForwardingWork &&
    item.component_id >= 0 &&
    !item.component_members.is_empty() &&
    item.uniform_values.is_empty() &&
    !item.allow_immutable_global_get &&
    !item.allow_immediate_i32_suffix_fallback &&
    physical_equal(graph.mod_, current) &&
    dae_work_item_epoch_is_current(graph, item)
  if cacheable {
    let context_current = match memo.context {
      Some(context) =>
        dae_forwarding_no_change_context_matches(
          context, original, current, touched, changed, graph, dependencies,
        )
      None => false
    }
    if !context_current {
      memo.invalidate()
      memo.context = Some({
        original,
        source: current,
        snapshot: graph.snapshot,
        dependencies,
        touched: touched.copy(),
        changed,
        module_epoch: graph.module_epoch,
        call_epoch: graph.call_epoch,
        type_epoch: graph.type_epoch,
        function_epochs: graph.function_epochs.map(fn(epochs) {
          (epochs.signature_epoch, epochs.body_epoch)
        }),
      })
    }
    match memo.entries.get(item.component_id) {
      Some(members) if members == item.component_members => {
        // Each target still owns its queue item and diagnostics. A negative
        // proof supplies no candidate, primary definition, or reused counters.
        diagnostics.boundaries_analyzed += 1
        dae_diagnostics_record_reason(diagnostics, DaeNoStrictImprovement)
        return DaeWorkNoChange(DaeNoStrictImprovement)
      }
      _ => ()
    }
  }
  match analysis_attempts {
    Some(count) if !count.is_empty() => count[0] += 1
    _ => ()
  }
  let outcome = dae_process_work_item(
    original, current, touched, changed, graph, dependencies, item, diagnostics,
  )
  match outcome {
    DaeWorkNoChange(DaeNoStrictImprovement) if cacheable =>
      match memo.context {
        Some(context) if dae_forwarding_no_change_context_matches(
            context, original, current, touched, changed, graph, dependencies,
          ) =>
          memo.entries.set(item.component_id, item.component_members.copy())
        _ => ()
      }
    // Every caller uses this rule, including direct tests and successful
    // fallback transactions, before any later work can query the memo.
    DaeWorkCommitted(_) => memo.invalidate()
    _ => ()
  }
  outcome
}