///|
/// Dense-index writer used after a verified register-allocation plan has been
/// produced for this exact function.
///
/// The writer avoids reconstructing and revalidating owner-tagged handles for
/// every value, operand, and edit. Indices must come from the matching
/// function and stay within its published counts. Final allocation invariants
/// remain the authoritative validation boundary.
pub struct AllocationBuilder[Inst] {
  priv function : Function[Inst]
  priv allocation : Allocation
}

///|
pub fn[Inst] AllocationBuilder::new(
  function : Function[Inst],
) -> AllocationBuilder[Inst] {
  { function, allocation: Allocation::for_function(function), }
}

///|
/// Adopts canonical location tables produced for this exact selected function.
///
/// The caller must stop mutating the source tables after adoption. Shape,
/// location class, spill ownership, size, and alignment are checked here so
/// callers that disable the final allocation verifier retain the same local
/// construction guarantees as incremental builder writes.
pub fn[Inst] AllocationBuilder::from_plan_storage(
  function : Function[Inst],
  value_locations : Array[AllocationLocation?],
  operand_locations : Array[Array[AllocationLocation?]],
  spill_owners : Array[Int],
  spill_sizes : Array[Int],
  spill_alignments : Array[Int],
) -> AllocationBuilder[Inst]? {
  if value_locations.length() != function.values.length() ||
    operand_locations.length() != function.instructions.length() ||
    spill_owners.length() != spill_sizes.length() ||
    spill_owners.length() != spill_alignments.length() {
    return None
  }
  let stack_slots : Array[StackSlotData] = []
  for slot in 0..= function.values.length() {
      return None
    }
    let minimum = match function.values[owner].ty {
      I32 | F32 => 4
      I64 | F64 | Ptr64 | GcRef64 => 8
      V128 => 16
    }
    if spill_sizes[slot] < minimum ||
      spill_alignments[slot] < minimum ||
      (spill_alignments[slot] & (spill_alignments[slot] - 1)) != 0 {
      return None
    }
    stack_slots.push({
      ty: function.values[owner].ty,
      size: spill_sizes[slot],
      alignment: spill_alignments[slot],
    })
  }
  let allocation = Allocation::for_function_with_storage(
    function, value_locations, operand_locations, stack_slots,
  )
  for value in 0.. ValueType {
  self.function.values[value].ty
}

///|
pub fn[Inst] AllocationBuilder::operand_value_at(
  self : AllocationBuilder[Inst],
  instruction : Int,
  operand : Int,
) -> Int {
  let data = self.function.instructions[instruction]
  self.function.operands[data.operand_start + operand].vreg.id
}

///|
pub fn[Inst] AllocationBuilder::create_stack_slot(
  self : AllocationBuilder[Inst],
  value : Int,
  size : Int,
  alignment : Int,
) -> Int {
  let slot = self.allocation.create_stack_slot(
    self.function.values[value].ty,
    size,
    alignment,
  )
  slot.id
}

///|
pub fn[Inst] AllocationBuilder::assign_value(
  self : AllocationBuilder[Inst],
  value : Int,
  location : AllocationLocation,
) -> Bool {
  if value < 0 ||
    value >= self.function.values.length() ||
    !self.allocation.dense_location_matches(
      location,
      self.function.values[value].ty,
    ) {
    return false
  }
  self.allocation.value_locations[value] = Some(location)
  true
}

///|
pub fn[Inst] AllocationBuilder::value_location(
  self : AllocationBuilder[Inst],
  value : Int,
) -> AllocationLocation? {
  self.allocation.value_locations[value]
}

///|
pub fn[Inst] AllocationBuilder::assign_operand(
  self : AllocationBuilder[Inst],
  instruction : Int,
  operand : Int,
  location : AllocationLocation,
) -> Bool {
  if instruction < 0 || instruction >= self.function.instructions.length() {
    return false
  }
  let data = self.function.instructions[instruction]
  if operand < 0 || operand >= data.operand_count {
    return false
  }
  let value = self.function.operands[data.operand_start + operand].vreg.id
  if !self.allocation.dense_location_matches(
      location,
      self.function.values[value].ty,
    ) {
    return false
  }
  self.allocation.operand_locations[instruction][operand] = Some(location)
  true
}

///|
pub fn[Inst] AllocationBuilder::add_transfer(
  self : AllocationBuilder[Inst],
  instruction : Int,
  placement : PointPlacement,
  value : Int,
  from : AllocationLocation,
  to : AllocationLocation,
) -> Bool {
  if instruction < 0 ||
    instruction >= self.function.instructions.length() ||
    value < 0 ||
    value >= self.function.values.length() ||
    !self.allocation.dense_location_matches(
      from,
      self.function.values[value].ty,
    ) ||
    !self.allocation.dense_location_matches(to, self.function.values[value].ty) {
    return false
  }
  let value = Value::new(self.function.owner, value)
  let instruction = Instruction::new(self.function.owner, instruction)
  let point = ProgramPoint::new(self.function.owner, instruction, placement)
  let edit = match (from.register(), to.register()) {
    (Some(from_reg), Some(to_reg)) =>
      Edit::register_move(point, value, from_reg, to_reg)
    (Some(reg), None) => {
      let slot = to.spill_index().unwrap()
      Edit::spill(
        point,
        value,
        reg,
        StackSlot::new(self.allocation.owner, slot),
      )
    }
    (None, Some(reg)) => {
      let slot = from.spill_index().unwrap()
      Edit::reload(
        point,
        value,
        StackSlot::new(self.allocation.owner, slot),
        reg,
      )
    }
    (None, None) => return false
  }
  self.allocation.append_edit(edit)
  true
}

///|
pub fn[Inst] AllocationBuilder::add_edge_transfer(
  self : AllocationBuilder[Inst],
  source_block : Int,
  successor : Int,
  value : Int,
  from : AllocationLocation,
  to : AllocationLocation,
) -> Bool {
  if source_block < 0 ||
    source_block >= self.function.blocks.length() ||
    successor < 0 ||
    value < 0 ||
    value >= self.function.values.length() ||
    !self.allocation.dense_location_matches(
      from,
      self.function.values[value].ty,
    ) ||
    !self.allocation.dense_location_matches(to, self.function.values[value].ty) {
    return false
  }
  self.allocation.append_edit(
    Edit::edge_move(
      Block::new(self.function.owner, source_block),
      successor,
      Value::new(self.function.owner, value),
      self.allocation.export_location(from).unwrap(),
      self.allocation.export_location(to).unwrap(),
    ),
  )
  true
}

///|
pub fn[Inst] AllocationBuilder::add_safepoint_root(
  self : AllocationBuilder[Inst],
  instruction : Int,
  value : Int,
) -> Bool {
  if instruction < 0 ||
    instruction >= self.function.instructions.length() ||
    value < 0 ||
    value >= self.function.values.length() ||
    self.allocation.value_locations[value] is None {
    return false
  }
  self.allocation.safepoint_roots.push({
    instruction: Instruction::new(self.function.owner, instruction),
    value: Value::new(self.function.owner, value),
    location: self.allocation.value_locations[value].unwrap(),
  })
  true
}

///|
/// Adds every selected VCode safepoint root through dense internal ids.
///
/// Selected-function validation already established metadata ownership and
/// root types; this method retains allocation-location checks without copying
/// layouts, instruction metadata, or owner-tagged root snapshots.
pub fn[Inst] AllocationBuilder::add_function_safepoint_roots(
  self : AllocationBuilder[Inst],
) -> Bool {
  for instruction, data in self.function.instructions {
    for root in data.metadata.live_gc_roots {
      if !self.function.owns_value(root) {
        return false
      }
      guard self.allocation.value_locations[root.id] is Some(location) else {
        return false
      }
      self.allocation.safepoint_roots.push({
        instruction: Instruction::new(self.function.owner, instruction),
        value: root,
        location,
      })
    }
  }
  true
}

///|
pub fn[Inst] AllocationBuilder::finish(
  self : AllocationBuilder[Inst],
) -> Allocation {
  self.allocation
}