// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Potentially incorporated Binaryen expression retains Apache-2.0 terms.
// Original Starshine contributions use the scoped MIT grant in ../../LICENSE.
// Modified for Starshine through 2026: implementation and validation use
// Starshine's representations and APIs; this is not an unchanged upstream file.
// Conservative notice retention does not establish copying or independence.

///|
// One workspace per module. Only own results and called parameter/result
// boundaries can consume expression edges in dae2_analyze_function. The
// original raw call summary is complete before any rewrite or HOT capture.
// Keep this contract beside solve_function; normal Graph.edge stays unchanged.
priv struct Dae2RawReplayState {
  graph : Dae2Graph
  source : Dae2Graph
  boundary_count : Int32
  sparse : Bool
  epochs : Array[Int32]
  touched : Array[Int32]
  mut epoch : Int32
  mut failed : Bool
}

///|
fn Dae2Graph::raw_rewrite_boundary_snapshot(
  self : Dae2Graph,
  boundary_count : Int32,
) -> Dae2Graph {
  // The module's solved expression mask can be large. The reusable state
  // outlives rewriting, so it must not keep that mask alive in finalization.
  {
    edges: [],
    next_edge: [],
    first_edge: [],
    last_edge: [],
    used: Array::makei(boundary_count, fn(location) { self.used[location] }),
    work: [],
    cursor: 0,
  }
}

///|
fn Dae2RawReplayState::new(
  source : Dae2Graph,
  boundary_count : Int32,
  sparse~ : Bool = true,
) -> Dae2RawReplayState {
  let source = source.raw_rewrite_boundary_snapshot(boundary_count)
  let graph = if sparse {
    let graph = Dae2Graph::new()
    ignore(graph.locations(boundary_count))
    // Copy the solved prefix once without allocating a queue of every live
    // module boundary. Subsequent successful resets retain these exact bits.
    for location in 0.. Unit {
  let graph = self.graph
  if !self.sparse || self.failed {
    // Failed qualification may have observed previously dead boundaries or
    // incomplete metadata. Restore every row only on this uncommon path.
    graph.reset_for_raw_rewrite(self.source, self.boundary_count)
    if self.sparse {
      graph.work.clear()
    }
  } else {
    for location in self.touched {
      graph.first_edge[location] = -1
      graph.last_edge[location] = -1
    }
    graph.used.resize(self.boundary_count, false)
    graph.first_edge.resize(self.boundary_count, -1)
    graph.last_edge.resize(self.boundary_count, -1)
    graph.edges.clear()
    graph.next_edge.clear()
    graph.work.clear()
    graph.cursor = 0
  }
  self.touched.clear()
  self.failed = false
  if self.epoch == 2147483647 {
    for location in 0.. Unit {
  if !self.sparse {
    return
  }
  if location < 0 || location >= self.boundary_count {
    self.failed = true
    return
  }
  if self.epochs[location] != self.epoch {
    self.epochs[location] = self.epoch
    self.touched.push(location)
  }
}

///|
fn Dae2RawReplayState::solve(self : Dae2RawReplayState) -> Bool {
  if self.failed {
    return false
  }
  if self.sparse {
    for location in self.touched {
      // Previously live boundaries never enter observe's queue again. Dead
      // boundaries newly observed by this body are already in the worklist.
      if self.source.used[location] && self.graph.first_edge[location] >= 0 {
        self.graph.work.push(location)
      }
    }
  }
  self.graph.solve()
  if self.sparse {
    // False-to-true changes must enter observe's queue. Original true bits
    // remain true, so checking this body's queue proves the same prefix mask
    // without scanning every function/type boundary again.
    for location in self.graph.work {
      if location < self.boundary_count && !self.source.used[location] {
        self.failed = true
        return false
      }
    }
  } else {
    for location in 0.. Bool {
  if self.sparse {
    if !info.call_infos_ready {
      self.failed = true
      return false
    }
    self.register(info.result_location)
    for index in info.call_infos {
      if index < 0 || index >= infos.length() {
        self.failed = true
        return false
      }
      let callee = infos[index]
      for location in callee.param_locations {
        self.register(location)
      }
      // ReturnCall adds the reverse result edge. Registering ordinary call
      // results too is cheap and keeps all call forms under one contract.
      self.register(callee.result_location)
    }
  }
  self.solve()
}