// Copyright (c) 2026 BigSaltyMan. BSL-1.0; see LICENSE.
// Portions adapted from Clipper2, Copyright (c) Angus Johnson 2010-2025.
// Index-based adaptation of Clipper2 engine concepts; see docs/upstream.md.

///|
type VertexId = Int

///|
#warnings("-unused_value")
priv struct EdgeId {
  slot : Int
  generation : Int
} derive(Eq, Debug)

///|
#warnings("-unused_value")
priv enum PathRole {
  Subject
  Clip
} derive(Eq, Debug)

///|
priv struct SourceId {
  internal_input : Bool
  role : PathRole
  path : Int
  global_path : Int
  vertex : Int
}

///|
priv struct Vertex {
  point : Point64
  source : SourceId
  mut prev : VertexId?
  mut next : VertexId?
  mut enter : Bool
  mut exit_anchor : VertexId?
}

///|
priv struct PreparedPath {
  role : PathRole
  path : Int
  raw_count : Int
  vertices : Array[VertexId]
  scannable : Bool
}

///|
priv struct LocalMinima {
  vertex : VertexId
  mut backward : Int?
  mut forward : Int?
}

///|
priv struct BoundaryChain {
  minima : Int
  direction : Int
  vertices : Array[VertexId]
  mut left_bound : Bool
}

///|
#warnings("-unused_value")
priv enum HorizontalDirection {
  NonHorizontal
  ToLeft
  ToRight
} derive(Eq, Debug)

///|
priv struct ActiveEdge {
  chain : Int
  mut cursor : Int
  mut bot : VertexId
  mut top : VertexId
  mut current_x : Int64
  mut horizontal_retrace : Bool
  mut horizontal : HorizontalDirection
  mut prev_ael : EdgeId?
  mut next_ael : EdgeId?
  mut prev_sel : EdgeId?
  mut next_sel : EdgeId?
  mut in_ael : Bool
  mut in_sel : Bool
  mut queued_horizontal : Bool
  mut at_exit : Bool
  mut joined : EdgeId?
  wind_dx : Int
  mut wind_count : Int
  mut wind_other : Int
  mut output_end : OutputEnd?
}

///|
priv struct EdgeSlot {
  mut generation : Int
  mut edge : ActiveEdge?
}

///|
priv struct ExitEvent {
  vertex : VertexId
  first_chain : Int
  second_chain : Int
}

///|
#warnings("-unused_value")
priv enum ScanPhase {
  Ready
  EnterMinima
  Horizontals
  PrepareBand
  AwaitIntersections(Int64)
  AwaitEntryContact(VertexId, EdgeId, EdgeId)
  TopVertices
  AwaitHorizontal(EdgeId)
  AwaitMaxima(VertexId, EdgeId, EdgeId)
  Complete
  Failed
} derive(Eq, Debug)

///|
/// Keep input Limits source-compatible. These additional limits are internal.
priv struct ScanLimits {
  input : Limits
  max_nodes : Int
  max_work : Int
}

///|
impl Default for ScanLimits with fn default() {
  { input: Default::default(), max_nodes: 500_000, max_work: 5_000_000, }
}

///|
#warnings("-unused_value")
priv struct ScanStats {
  mut nodes : Int
  mut work : Int
  mut peak_active : Int
  mut heights : Int
  mut comparisons : Int
  mut horizontal_steps : Int
  mut intersection_events : Int
  mut horizontal_joins : Int
  mut output_records : Int
  mut output_points : Int
} derive(Eq, Debug)

///|
// One cumulative ledger; ScanState aliases these immutable limits and mutable stats.
// Releasing a phase never refunds prior work or allocation charges.
priv struct CallBudget {
  generate_stage : Stage
  cleanup_stage : Stage
  limits : ScanLimits
  stats : ScanStats
}

///|
priv struct ScanState {
  budget : CallBudget
  internal_input : Bool
  limits : ScanLimits
  stats : ScanStats
  vertices : Array[Vertex]
  paths : Array[PreparedPath]
  minima : Array[LocalMinima]
  entries : Map[Int64, Array[Int]]
  chains : Array[BoundaryChain]
  heap : Array[Int64]
  heights : Set[Int64]
  slots : Array[EdgeSlot]
  free_slots : Array[Int]
  horizontal_queue : Array[EdgeId]
  exits : Array[ExitEvent]
  mut preserve_collinear : Bool
  mut boolean_rule : WindingRule
  mut boolean_operation : ClipType?
  output : OutputState
  intersections : Array[IntersectEvent]
  mut intersection_cursor : Int
  mut band_built : Int64?
  pending_connections : Array[EngineEvent]
  mut connection_cursor : Int
  mut entry_contact : (VertexId, EdgeId, EdgeId)?
  mut horizontal_started : Bool
  mut held_horizontal : EdgeId?
  mut ael_head : EdgeId?
  mut sel_head : EdgeId?
  mut live_edges : Int
  mut ael_count : Int
  mut current_y : Int64?
  mut last_popped : Int64?
  mut needs_entries : Bool
  mut phase : ScanPhase
  mut failure : ClipError?
  mut cleaned : Bool
}

///|
priv suberror ScanAbort {
  ScanAbort(ClipError)
}

///|
fn new_call_budget(
  limits : ScanLimits,
  generate_stage? : Stage = OffsetGenerate,
  cleanup_stage? : Stage = OffsetCleanup,
) -> CallBudget {
  {
    generate_stage,
    cleanup_stage,
    limits,
    stats: {
      nodes: 0,
      work: 0,
      peak_active: 0,
      heights: 0,
      comparisons: 0,
      horizontal_steps: 0,
      intersection_events: 0,
      horizontal_joins: 0,
      output_records: 0,
      output_points: 0,
    },
  }
}

///|
fn new_scan_state(
  limits : ScanLimits,
  budget? : CallBudget,
  internal_input? : Bool = false,
) -> ScanState {
  let budget = match budget {
    Some(value) => value
    None => new_call_budget(limits)
  }
  {
    budget,
    internal_input,
    limits: budget.limits,
    stats: budget.stats,
    vertices: [],
    paths: [],
    minima: [],
    entries: Map([]),
    chains: [],
    heap: [],
    heights: Default::default(),
    slots: [],
    free_slots: [],
    horizontal_queue: [],
    exits: [],
    preserve_collinear: false,
    boolean_rule: Parity,
    boolean_operation: None,
    output: new_output_state(),
    intersections: [],
    intersection_cursor: 0,
    band_built: None,
    pending_connections: [],
    connection_cursor: 0,
    entry_contact: None,
    horizontal_started: false,
    held_horizontal: None,
    ael_head: None,
    sel_head: None,
    live_edges: 0,
    ael_count: 0,
    current_y: None,
    last_popped: None,
    needs_entries: false,
    phase: Ready,
    failure: None,
    cleaned: false,
  }
}

///|
fn[T] scan_abort(state : ScanState, error : ClipError) -> T raise ScanAbort {
  state.failure = Some(error)
  state.phase = Failed
  raise ScanAbort(error)
}

///|
fn scan_live(state : ScanState) -> Unit raise ScanAbort {
  if state.failure is Some(error) {
    raise ScanAbort(error)
  }
  if state.cleaned || state.phase == Complete {
    scan_abort(
      state,
      InvalidInput(location(ScanEvents, None, None), "Scan state has ended"),
    )
  }
}

///|
fn scan_location(
  state : ScanState,
  vertex : VertexId?,
  stage : Stage,
) -> ErrorLocation {
  match vertex {
    Some(id) => {
      let source = state.vertices[id].source
      if state.internal_input {
        location(stage, None, None)
      } else {
        location(stage, Some(source.global_path), Some(source.vertex))
      }
    }
    None => location(stage, None, None)
  }
}

///|
/// Check a whole associated allocation before consuming any part of it.
fn scan_reserve(
  state : ScanState,
  nodes : Int,
  work : Int,
  loc : ErrorLocation,
) -> Unit raise ScanAbort {
  scan_live(state)
  if budget_error(state.budget, nodes, work, loc) is Some(error) {
    scan_abort(state, error)
  }
}

///|
fn scan_spend(
  state : ScanState,
  nodes : Int,
  work : Int,
  loc : ErrorLocation,
) -> Unit raise ScanAbort {
  scan_reserve(state, nodes, work, loc)
  state.stats.nodes += nodes
  state.stats.work += work
}

///|
fn edge_peek(state : ScanState, id : EdgeId) -> ActiveEdge? {
  if id.slot < 0 || id.slot >= state.slots.length() {
    return None
  }
  let slot = state.slots[id.slot]
  if slot.generation != id.generation {
    return None
  }
  slot.edge
}

///|
fn edge_get(state : ScanState, id : EdgeId) -> ActiveEdge raise ScanAbort {
  scan_live(state)
  match edge_peek(state, id) {
    Some(edge) => edge
    None =>
      scan_abort(
        state,
        InvalidInput(
          location(ActiveEdges, None, None),
          "Stale or invalid active edge handle (slot \{id.slot}, generation \{id.generation})",
        ),
      )
  }
}

///|
fn horizontal_direction(bot : Point64, top : Point64) -> HorizontalDirection {
  if bot.y != top.y {
    NonHorizontal
  } else if top.x < bot.x {
    ToLeft
  } else {
    ToRight
  }
}

///|
fn edge_allocate(state : ScanState, chain_id : Int) -> EdgeId raise ScanAbort {
  scan_live(state)
  guard chain_id >= 0 && chain_id < state.chains.length() else {
    scan_abort(
      state,
      InvalidInput(location(ActiveEdges, None, None), "Invalid boundary chain"),
    )
  }
  let chain = state.chains[chain_id]
  let bot = chain.vertices[0]
  let top = chain.vertices[1]
  scan_spend(state, 1, 1, scan_location(state, Some(bot), ActiveEdges))
  let edge : ActiveEdge = {
    chain: chain_id,
    cursor: 0,
    bot,
    top,
    current_x: state.vertices[bot].point.x,
    horizontal_retrace: false,
    horizontal: horizontal_direction(
      state.vertices[bot].point,
      state.vertices[top].point,
    ),
    prev_ael: None,
    next_ael: None,
    prev_sel: None,
    next_sel: None,
    in_ael: false,
    in_sel: false,
    queued_horizontal: false,
    at_exit: false,
    joined: None,
    wind_dx: chain.direction,
    wind_count: 0,
    wind_other: 0,
    output_end: None,
  }
  let index = match state.free_slots.pop() {
    Some(index) => index
    None => {
      let index = state.slots.length()
      state.slots.push({ generation: 0, edge: None, })
      index
    }
  }
  let slot = state.slots[index]
  slot.edge = Some(edge)
  state.live_edges += 1
  { slot: index, generation: slot.generation, }
}

///|
fn edge_release(state : ScanState, id : EdgeId) -> Unit raise ScanAbort {
  let edge = edge_get(state, id)
  let loc = scan_location(state, Some(edge.bot), ActiveEdges)
  let entry_reference = match state.entry_contact {
    Some((_, a, b)) => id == a || id == b
    None => false
  }
  let phase_reference = match state.phase {
    AwaitMaxima(_, a, b) => id == a || id == b
    _ => false
  }
  if edge.output_end is Some(_) ||
    state.intersection_cursor < state.intersections.length() ||
    state.connection_cursor < state.pending_connections.length() ||
    entry_reference ||
    phase_reference ||
    edge.prev_ael is Some(_) ||
    edge.next_ael is Some(_) ||
    edge.prev_sel is Some(_) ||
    edge.next_sel is Some(_) ||
    edge.in_ael ||
    edge.in_sel ||
    edge.queued_horizontal ||
    state.held_horizontal == Some(id) {
    scan_abort(
      state,
      InvalidInput(
        loc, "Detach active edge from all chains and pending events before release",
      ),
    )
  }
  let slot = state.slots[id.slot]
  if slot.generation == 2147483647 {
    scan_abort(state, NumericFailure(loc, "Active edge generation exhausted"))
  }
  scan_spend(state, 0, 1, loc)
  slot.edge = None
  slot.generation += 1
  state.free_slots.push(id.slot)
  state.live_edges -= 1
}

///|
fn ael_remove(state : ScanState, id : EdgeId) -> Unit raise ScanAbort {
  let edge = edge_get(state, id)
  let loc = scan_location(state, Some(edge.bot), ActiveEdges)
  let entry_reference = match state.entry_contact {
    Some((_, a, b)) => id == a || id == b
    None => false
  }
  let pending = match state.phase {
    AwaitMaxima(_, a, b) => id == a || id == b
    _ => false
  }
  if entry_reference ||
    pending ||
    edge.queued_horizontal ||
    state.held_horizontal == Some(id) {
    scan_abort(
      state,
      InvalidInput(loc, "Consume pending events before active edge removal"),
    )
  }
  if !edge.in_ael {
    scan_abort(state, InvalidInput(loc, "Edge is not in the active chain"))
  }
  scan_spend(state, 0, 1, loc)
  match edge.prev_ael {
    Some(prev) => edge_get(state, prev).next_ael = edge.next_ael
    None => state.ael_head = edge.next_ael
  }
  if edge.next_ael is Some(next) {
    edge_get(state, next).prev_ael = edge.prev_ael
  }
  edge.prev_ael = None
  edge.next_ael = None
  edge.in_ael = false
  state.ael_count -= 1
}

///|
/// Only adjacent swaps; SEL is intentionally untouched.
#warnings("-unused_value")
fn ael_swap(
  state : ScanState,
  left : EdgeId,
  right : EdgeId,
) -> Unit raise ScanAbort {
  let a = edge_get(state, left)
  let b = edge_get(state, right)
  let loc = scan_location(state, Some(a.bot), ActiveEdges)
  if !a.in_ael ||
    !b.in_ael ||
    a.next_ael != Some(right) ||
    b.prev_ael != Some(left) {
    scan_abort(
      state,
      InvalidInput(
        loc, "Active edge swap requires adjacent left and right handles",
      ),
    )
  }
  scan_spend(state, 0, 1, loc)
  let prev = a.prev_ael
  let next = b.next_ael
  match prev {
    Some(p) => edge_get(state, p).next_ael = Some(right)
    None => state.ael_head = Some(right)
  }
  if next is Some(n) {
    edge_get(state, n).prev_ael = Some(left)
  }
  b.prev_ael = prev
  b.next_ael = Some(left)
  a.prev_ael = Some(right)
  a.next_ael = next
}

///|
fn sel_clear(state : ScanState) -> Unit raise ScanAbort {
  let mut cursor = state.sel_head
  while cursor is Some(id) {
    let edge = edge_get(state, id)
    scan_spend(state, 0, 1, scan_location(state, Some(edge.bot), ActiveEdges))
    cursor = edge.next_sel
    edge.prev_sel = None
    edge.next_sel = None
    edge.in_sel = false
  }
  state.sel_head = None
}

///|
/// A separate linked snapshot. It is never sorted to silently reorder AEL.
fn sel_snapshot(state : ScanState) -> Unit raise ScanAbort {
  sel_clear(state)
  let mut cursor = state.ael_head
  let mut prev : EdgeId? = None
  while cursor is Some(id) {
    let edge = edge_get(state, id)
    scan_spend(state, 0, 1, scan_location(state, Some(edge.bot), ActiveEdges))
    edge.prev_sel = prev
    edge.next_sel = edge.next_ael
    edge.in_sel = true
    if prev is None {
      state.sel_head = Some(id)
    }
    prev = Some(id)
    cursor = edge.next_ael
  }
}

///|
/// Cleanup is bounded by allocated storage, even after work exhaustion.
/// It cannot resume a failed context or reuse its old handles.
#warnings("-unused_value")
fn scan_cleanup(state : ScanState) -> Unit {
  for slot in state.slots {
    slot.edge = None
  }
  state.free_slots.clear()
  state.horizontal_queue.clear()
  state.heap.clear()
  state.heights.clear()
  state.entries.clear()
  state.intersections.clear()
  state.pending_connections.clear()
  state.intersection_cursor = 0
  state.connection_cursor = 0
  state.band_built = None
  state.output.points.clear()
  state.output.records.clear()
  state.output.horizontals.clear()
  state.output.joins.clear()
  state.ael_head = None
  state.sel_head = None
  state.held_horizontal = None
  state.entry_contact = None
  state.live_edges = 0
  state.ael_count = 0
  state.cleaned = true
  if state.failure is None {
    state.phase = Complete
  }
}

///|
/// Read-only diagnostics bounded by allocated vertices/slots, including on
/// failed states. No mutation or work-budget reset is performed by diagnostics.
#warnings("-unused_value")
fn scan_invariants(
  state : ScanState,
  stable? : Bool = false,
  metered? : Bool = false,
) -> Result[Unit, String] {
  if state.stats.nodes < 0 ||
    state.stats.nodes > state.limits.max_nodes ||
    state.stats.work < 0 ||
    state.stats.work > state.limits.max_work ||
    state.stats.peak_active < state.ael_count ||
    state.stats.comparisons > state.stats.work ||
    state.stats.heights > state.stats.work ||
    state.stats.horizontal_steps > state.stats.work {
    return Err("Invalid execution statistics")
  }
  match validation_charge(state, metered, 1, 0) {
    Ok(_) => ()
    Err(e) => return Err(e)
  }
  let owned : Set[Int] = Default::default()
  for p = 0; p < state.paths.length(); p = p + 1 {
    match validation_charge(state, metered, 0, 1) {
      Ok(_) => ()
      Err(e) => return Err(e)
    }
    let path = state.paths[p]
    for i = 0; i < path.vertices.length(); i = i + 1 {
      match validation_charge(state, metered, 0, 1) {
        Ok(_) => ()
        Err(e) => return Err(e)
      }
      let id = path.vertices[i]
      if id < 0 || id >= state.vertices.length() || owned.contains(id) {
        return Err("Invalid or multiply owned vertex index")
      }
      match validation_charge(state, metered, 1, 0) {
        Ok(_) => ()
        Err(e) => return Err(e)
      }
      owned.add(id)
      let vertex = state.vertices[id]
      if vertex.source.role != path.role ||
        vertex.source.path != path.path ||
        vertex.source.global_path != p ||
        vertex.source.vertex < 0 ||
        vertex.source.vertex >= path.raw_count {
        return Err("Invalid original input provenance")
      }
      let previous = path.vertices[(i + path.vertices.length() - 1) %
        path.vertices.length()]
      let next = path.vertices[(i + 1) % path.vertices.length()]
      if vertex.prev != Some(previous) || vertex.next != Some(next) {
        return Err("Incorrect closed vertex neighbors")
      }
    }
  }
  if owned.length() != state.vertices.length() {
    return Err("Unowned internal vertices")
  }
  for minimum in state.minima {
    match validation_charge(state, metered, 0, 1) {
      Ok(_) => ()
      Err(e) => return Err(e)
    }
    if minimum.vertex < 0 ||
      minimum.vertex >= state.vertices.length() ||
      !state.vertices[minimum.vertex].enter {
      return Err("Invalid local-enter vertex")
    }
  }
  for chain in state.chains {
    match validation_charge(state, metered, 0, 1) {
      Ok(_) => ()
      Err(e) => return Err(e)
    }
    if chain.minima < 0 ||
      chain.minima >= state.minima.length() ||
      chain.vertices.length() < 2 ||
      (chain.direction != 1 && chain.direction != -1) {
      return Err("Invalid boundary chain record")
    }
    if chain.vertices[0] != state.minima[chain.minima].vertex {
      return Err("Boundary origin mismatch")
    }
    for i = 0; i < chain.vertices.length(); i = i + 1 {
      match validation_charge(state, metered, 0, 1) {
        Ok(_) => ()
        Err(e) => return Err(e)
      }
      let id = chain.vertices[i]
      if id < 0 || id >= state.vertices.length() {
        return Err("Invalid boundary vertex reference")
      }
      if i > 0 {
        let before = state.vertices[chain.vertices[i - 1]]
        if (if chain.direction > 0 { before.next } else { before.prev }) !=
          Some(id) {
          return Err("Boundary traversal direction mismatch")
        }
      }
    }
    let end = chain.vertices[chain.vertices.length() - 1]
    if state.vertices[end].exit_anchor != Some(end) {
      return Err("Boundary does not end at an exit anchor")
    }
  }
  // Validate slot payloads before following chain identifiers in linked lists.
  for slot in state.slots {
    match validation_charge(state, metered, 0, 1) {
      Ok(_) => ()
      Err(e) => return Err(e)
    }
    if slot.edge is Some(edge) {
      if edge.chain < 0 || edge.chain >= state.chains.length() {
        return Err("Invalid active boundary chain")
      }
      let chain = state.chains[edge.chain]
      if edge.cursor < 0 ||
        edge.cursor + 1 >= chain.vertices.length() ||
        edge.bot != chain.vertices[edge.cursor] ||
        edge.top != chain.vertices[edge.cursor + 1] {
        return Err("Invalid boundary cursor")
      }
    }
  }
  let mut alive = 0
  for slot in state.slots {
    match validation_charge(state, metered, 0, 1) {
      Ok(_) => ()
      Err(e) => return Err(e)
    }
    if slot.edge is Some(_) {
      alive += 1
    }
  }
  if alive != state.live_edges {
    return Err("Live edge count mismatch")
  }
  match validation_charge(state, metered, 1, 0) {
    Ok(_) => ()
    Err(e) => return Err(e)
  }
  let free : Set[Int] = Default::default()
  for index in state.free_slots {
    match validation_charge(state, metered, 0, 1) {
      Ok(_) => ()
      Err(e) => return Err(e)
    }
    if index < 0 ||
      index >= state.slots.length() ||
      state.slots[index].edge is Some(_) ||
      free.contains(index) {
      return Err("Invalid free slot list")
    }
    match validation_charge(state, metered, 1, 0) {
      Ok(_) => ()
      Err(e) => return Err(e)
    }
    free.add(index)
  }
  match validation_charge(state, metered, 1, 0) {
    Ok(_) => ()
    Err(e) => return Err(e)
  }
  let ael : Set[Int] = Default::default()
  match validation_charge(state, metered, 1, 0) {
    Ok(_) => ()
    Err(e) => return Err(e)
  }
  let sel : Set[Int] = Default::default()
  match validation_charge(state, metered, 1, 0) {
    Ok(_) => ()
    Err(e) => return Err(e)
  }
  let queued : Set[Int] = Default::default()
  match validation_charge(state, metered, 3, 0) {
    Ok(_) => ()
    Err(e) => return Err(e)
  }
  for auxiliary in [false, true] {
    match validation_charge(state, metered, 0, 1) {
      Ok(_) => ()
      Err(e) => return Err(e)
    }
    let seen = if auxiliary { sel } else { ael }
    let mut previous : EdgeId? = None
    let mut cursor = if auxiliary { state.sel_head } else { state.ael_head }
    let mut previous_x : Int64? = None
    let mut wind = 0
    while cursor is Some(id) {
      match validation_charge(state, metered, 0, 1) {
        Ok(_) => ()
        Err(e) => return Err(e)
      }
      if seen.contains(id.slot) {
        return Err("Cycle or duplicate edge in linked chain")
      }
      guard edge_peek(state, id) is Some(edge) else {
        return Err("Linked chain contains stale handle")
      }
      match validation_charge(state, metered, 1, 0) {
        Ok(_) => ()
        Err(e) => return Err(e)
      }
      seen.add(id.slot)
      if auxiliary {
        if !edge.in_sel || edge.prev_sel != previous {
          return Err("Nonreciprocal auxiliary links")
        }
      } else {
        if !edge.in_ael || edge.prev_ael != previous {
          return Err("Nonreciprocal active links")
        }
        if stable && previous_x is Some(x) && x > edge.current_x {
          return Err("Unordered stable active chain")
        }
        if stable && previous is Some(previous_id) {
          let earlier = edge_peek(state, previous_id).unwrap()
          let both_ending = state.current_y ==
            Some(state.vertices[earlier.top].point.y) &&
            state.current_y == Some(state.vertices[edge.top].point.y)
          if !both_ending && edge_position_compare(state, earlier, edge) > 0 {
            return Err("Unordered stable boundary directions")
          }
        }
        previous_x = Some(edge.current_x)
        wind += state.chains[edge.chain].direction
      }
      previous = Some(id)
      cursor = if auxiliary { edge.next_sel } else { edge.next_ael }
    }
    if !auxiliary && (seen.length() != state.ael_count || (stable && wind != 0)) {
      return Err("Active count or boundary winding pair mismatch")
    }
  }
  for id in state.horizontal_queue {
    match validation_charge(state, metered, 0, 1) {
      Ok(_) => ()
      Err(e) => return Err(e)
    }
    guard edge_peek(state, id) is Some(edge) else {
      return Err("Stale queued horizontal")
    }
    if !edge.in_ael ||
      queued.contains(id.slot) ||
      !edge.queued_horizontal ||
      edge.horizontal == NonHorizontal {
      return Err("Invalid horizontal queue membership")
    }
    match validation_charge(state, metered, 1, 0) {
      Ok(_) => ()
      Err(e) => return Err(e)
    }
    queued.add(id.slot)
  }
  if state.held_horizontal is Some(id) {
    guard edge_peek(state, id) is Some(edge) else {
      return Err("Stale held horizontal")
    }
    if !edge.in_ael ||
      edge.queued_horizontal ||
      edge.horizontal == NonHorizontal {
      return Err("Invalid held horizontal")
    }
  }
  for i = 0; i < state.slots.length(); i = i + 1 {
    match validation_charge(state, metered, 0, 1) {
      Ok(_) => ()
      Err(e) => return Err(e)
    }
    match state.slots[i].edge {
      None =>
        if !state.cleaned && !free.contains(i) {
          return Err("Released slot missing from free list")
        }
      Some(edge) => {
        if edge.in_ael != ael.contains(i) ||
          edge.in_sel != sel.contains(i) ||
          edge.queued_horizontal != queued.contains(i) {
          return Err("Unreachable linked or pending edge")
        }
        if !edge.in_ael &&
          (edge.prev_ael is Some(_) || edge.next_ael is Some(_)) {
          return Err("Detached edge retains active links")
        }
        if !edge.in_sel &&
          (edge.prev_sel is Some(_) || edge.next_sel is Some(_)) {
          return Err("Detached edge retains auxiliary links")
        }
        if edge.chain < 0 || edge.chain >= state.chains.length() {
          return Err("Invalid active boundary chain")
        }
        let chain = state.chains[edge.chain]
        if edge.cursor < 0 ||
          edge.cursor + 1 >= chain.vertices.length() ||
          edge.bot != chain.vertices[edge.cursor] ||
          edge.top != chain.vertices[edge.cursor + 1] {
          return Err("Invalid boundary cursor")
        }
      }
    }
  }
  for event in state.exits {
    match validation_charge(state, metered, 0, 1) {
      Ok(_) => ()
      Err(e) => return Err(e)
    }
    if event.vertex < 0 ||
      event.vertex >= state.vertices.length() ||
      event.first_chain < 0 ||
      event.second_chain < 0 ||
      event.first_chain >= state.chains.length() ||
      event.second_chain >= state.chains.length() {
      return Err("Invalid retained exit provenance")
    }
  }
  if state.entry_contact is Some((vertex, a, b)) {
    if vertex < 0 || vertex >= state.vertices.length() {
      return Err("Invalid entry contact vertex")
    }
    guard edge_peek(state, a) is Some(first) &&
      edge_peek(state, b) is Some(second) else {
      return Err("Stale entry contact handles")
    }
    if !first.in_ael ||
      !second.in_ael ||
      first.current_x != state.vertices[vertex].point.x ||
      second.current_x != first.current_x {
      return Err("Invalid entry contact references")
    }
  }
  match state.phase {
    AwaitEntryContact(vertex, a, b) =>
      if state.entry_contact != Some((vertex, a, b)) {
        return Err("Entry contact phase mismatch")
      }
    AwaitHorizontal(id) =>
      if state.held_horizontal != Some(id) {
        return Err("Pending horizontal phase mismatch")
      }
    AwaitMaxima(vertex, a, b) => {
      guard edge_peek(state, a) is Some(first) &&
        edge_peek(state, b) is Some(second) else {
        return Err("Stale pending maxima pair")
      }
      if !first.at_exit ||
        !second.at_exit ||
        first.top != vertex ||
        second.top != vertex {
        return Err("Pending maxima phase mismatch")
      }
    }
    _ => ()
  }
  Ok(())
}

///|
// Pure preflight: both checks precede the paired update.
fn budget_error(
  budget : CallBudget,
  nodes : Int,
  work : Int,
  loc : ErrorLocation,
) -> ClipError? {
  if nodes < 0 || work < 0 {
    return Some(InvalidInput(loc, "Budget charges must be non-negative"))
  }
  if nodes > budget.limits.max_nodes - budget.stats.nodes {
    return Some(
      ResourceLimitExceeded(loc, InternalNodes, budget.limits.max_nodes),
    )
  }
  if work > budget.limits.max_work - budget.stats.work {
    return Some(ResourceLimitExceeded(loc, ScanWork, budget.limits.max_work))
  }
  None
}

///|
fn budget_spend(
  budget : CallBudget,
  nodes : Int,
  work : Int,
  loc : ErrorLocation,
) -> Unit raise ScanAbort {
  if budget_error(budget, nodes, work, loc) is Some(error) {
    raise ScanAbort(error)
  }
  budget.stats.nodes += nodes
  budget.stats.work += work
}

///|
// Diagnostic entry points remain usable after failure; production opts into metering.
fn validation_charge(
  state : ScanState,
  metered : Bool,
  nodes : Int,
  work : Int,
) -> Result[Unit, String] {
  if !metered {
    return Ok(())
  }
  if state.failure is Some(_) {
    return Err("Validation already failed")
  }
  if state.cleaned {
    state.failure = Some(
      InvalidInput(
        location(OutputTopology, None, None),
        "Validation state has been cleaned",
      ),
    )
    state.phase = Failed
    return Err("Validation state has been cleaned")
  }
  if budget_error(
      state.budget,
      nodes,
      work,
      location(OutputTopology, None, None),
    )
    is Some(error) {
    state.failure = Some(error)
    state.phase = Failed
    return Err("Validation budget exhausted")
  }
  state.stats.nodes += nodes
  state.stats.work += work
  Ok(())
}