///|
/// Proposed only: original-writer cut for one fresh raw-origin rewrite lift.
priv struct Dae2WriterCut {
  revision : Int32
  source_local_count : Int32
  accesses : @ir.HotLiftLocalAccesses
  writer_demands : Array[Bool]
  captures : Array[(Int32, Int32)]
}

///|
fn dae2_writer_cut_prepare(
  source : Dae2Graph,
  info : Dae2Function,
  func : @ir.HotFunc,
  original : @lib.Func,
) -> Dae2WriterCut? {
  if !info.raw_analysis {
    return None
  }
  let @lib.Func(body_locals, _) = original
  let accesses = match func.lifted_local_accesses {
    Some(accesses) if accesses.revision == @ir.hot_revision_current(func) =>
      accesses
    _ => return None
  }
  let captures = match accesses.capture_sources {
    Some(captures) => captures
    None => return None
  }
  let writes = match info.raw_write_locations {
    Some(writes) => writes
    None => []
  }
  if accesses.writes.length() != writes.length() ||
    accesses.local_count != info.params.length() + body_locals.length() ||
    @ir.hot_local_count(func) != accesses.local_count + captures.length() {
    return None
  }
  let count = @ir.hot_node_count(func)
  let mut concrete_gets = 0
  let mut previous_get = -1
  for (local, node) in accesses.gets {
    if local < 0 || local >= accesses.local_count {
      return None
    }
    if node == -1 {
      continue
    }
    if node < 0 ||
      node >= @ir.hot_node_count(func) ||
      node <= previous_get ||
      !@ir.hot_node_is_live(func, node) {
      return None
    }
    let data = @ir.hot_node_get(func, node)
    if !(data.op is @ir.LocalGet) || data.imm0 != local {
      return None
    }
    previous_get = node
    concrete_gets += 1
  }
  if concrete_gets == 0 && captures.is_empty() {
    return None
  }
  let demands : Array[Bool] = []
  let mut previous = -1
  let mut concrete_writes = 0
  for index in 0..= accesses.local_count ||
      location < 0 ||
      location >= source.used.length() {
      return None
    }
    let used = source.used[location]
    demands.push(used)
    if node < 0 {
      if node != -1 || used {
        return None
      }
      continue
    }
    if node >= count || node <= previous || !@ir.hot_node_is_live(func, node) {
      return None
    }
    let data = @ir.hot_node_get(func, node)
    if (!(data.op is @ir.LocalSet) && !(data.op is @ir.LocalTee)) ||
      data.imm0 != local {
      return None
    }
    previous = node
    concrete_writes += 1
  }
  if concrete_writes == 0 && captures.is_empty() {
    return None
  }
  let mut previous_read = -1
  for index in 0..= count ||
      write < 0 ||
      write >= count ||
      read <= previous_read ||
      !@ir.hot_node_is_live(func, read) ||
      !@ir.hot_node_is_live(func, write) {
      return None
    }
    let get = @ir.hot_node_get(func, read)
    let set = @ir.hot_node_get(func, write)
    let local = accesses.local_count + index
    if !(get.op is @ir.LocalGet) ||
      !(set.op is @ir.LocalSet) ||
      get.imm0 != local ||
      set.imm0 != local ||
      get.child_count != 0 ||
      set.child_count != 1 ||
      write >= read {
      return None
    }
    previous_read = read
  }
  // Account for every live original writer and synthetic access. Fresh capture
  // locals are appended once, in journal order. Never infer a source from a
  // sole writer in arbitrary user-supplied HOT.
  let mut seen_original_gets = 0
  let mut seen_original_writes = 0
  let mut seen_capture_reads = 0
  let mut seen_capture_writes = 0
  for node in 0..= @ir.hot_local_count(func) {
        return None
      }
      if data.imm0 < accesses.local_count {
        if data.op is @ir.LocalGet {
          seen_original_gets += 1
        }
        if data.op is @ir.LocalSet || data.op is @ir.LocalTee {
          seen_original_writes += 1
        }
      } else {
        let pair = captures[data.imm0 - accesses.local_count]
        if data.op is @ir.LocalGet {
          if pair.0 != node {
            return None
          }
          seen_capture_reads += 1
        } else {
          if !(data.op is @ir.LocalSet) || pair.1 != node {
            return None
          }
          seen_capture_writes += 1
        }
      }
    }
  }
  if seen_original_gets != concrete_gets ||
    seen_original_writes != concrete_writes ||
    seen_capture_reads != captures.length() ||
    seen_capture_writes != captures.length() {
    return None
  }
  Some({
    revision: accesses.revision,
    source_local_count: accesses.local_count,
    accesses,
    writer_demands: demands,
    captures,
  })
}

///|
fn Dae2WriterCut::seed(
  self : Dae2WriterCut,
  graph : Dae2Graph,
  info : Dae2Function,
) -> Unit {
  for index in 0..= 0 && self.writer_demands[index] {
      graph.observe(dae2_node_location(info, node))
    }
  }
}

///|
/// Extra local writer gate; complete parameter/result gate remains unchanged.
fn Dae2WriterCut::matches_writers(
  self : Dae2WriterCut,
  graph : Dae2Graph,
  info : Dae2Function,
) -> Bool {
  for index in 0..= 0 &&
      graph.used[dae2_node_location(info, node)] != self.writer_demands[index] {
      return false
    }
  }
  true
}

///|
/// Shared production seed/analyze/solve/reset gate. A caller must keep the
/// prepared carrier in this private fresh-lift scope; it must never escape to lower.
fn dae2_writer_cut_analyze_prepared_replay(
  replay : Dae2RawReplayState,
  infos : Array[Dae2Function],
  index : Int32,
  type_infos : Array[Int32],
  func : @ir.HotFunc,
  cut : Dae2WriterCut?,
  local_graph_builds~ : Array[Int32]? = None,
  fallback_write_builds~ : Array[Int32]? = None,
  intrinsic_imports~ : Array[Bool]? = None,
) -> Bool {
  let info = infos[index]
  info.assign_node_locations(replay.graph, @ir.hot_node_count(func))
  dae2_analyze_function(
    replay.graph,
    infos,
    index,
    type_infos,
    func,
    local_graph_builds~,
    fallback_write_builds~,
    intrinsic_imports~,
    writer_cut=cut,
  )
  let mut replay_ok = replay.solve_function(info, infos)
  if replay_ok && cut is Some(cut) && !cut.matches_writers(replay.graph, info) {
    replay.reset()
    info.assign_node_locations(replay.graph, @ir.hot_node_count(func))
    dae2_analyze_function(
      replay.graph,
      infos,
      index,
      type_infos,
      func,
      local_graph_builds~,
      fallback_write_builds~,
      intrinsic_imports~,
    )
    replay_ok = replay.solve_function(info, infos)
  }
  replay_ok
}

///|
/// The carrier is owned only inside this callback-free function. Main gets a
/// Bool and a solved private replay, never a carrier or source-journal alias.
fn dae2_writer_cut_analyze_raw_replay(
  source : Dae2Graph,
  replay : Dae2RawReplayState,
  infos : Array[Dae2Function],
  index : Int32,
  type_infos : Array[Int32],
  func : @ir.HotFunc,
  original : @lib.Func,
  local_graph_builds~ : Array[Int32]? = None,
  fallback_write_builds~ : Array[Int32]? = None,
  intrinsic_imports~ : Array[Bool]? = None,
) -> Bool {
  let ok = {
    let cut = dae2_writer_cut_prepare(source, infos[index], func, original)
    dae2_writer_cut_analyze_prepared_replay(
      replay,
      infos,
      index,
      type_infos,
      func,
      cut,
      local_graph_builds~,
      fallback_write_builds~,
      intrinsic_imports~,
    )
  }
  @ir.hot_lift_release_local_accesses(func)
  ok
}

///|
/// Keep the actual rewrite lift and its source-recording policy shared with
/// bounded behavior and native controls. Lift/repair/errors remain unchanged.
fn dae2_lift_for_rewrite(
  mod_ : @lib.Module,
  context : @ir.HotModuleContext,
  index : Int32,
  info : Dae2Function,
) -> Result[@ir.HotFunc, String] {
  let record_sources = if info.raw_analysis {
    match info.raw_write_locations {
      Some(writes) => !writes.is_empty()
      None => false
    }
  } else {
    false
  }
  dae2_lift_for_analysis(
    mod_,
    context,
    index,
    record_local_accesses=record_sources,
    record_capture_sources=record_sources,
  )
}