// Copyright (c) 2026 BigSaltyMan. BSL-1.0; see LICENSE.
// Explicit geometry/semantic boundary. No default fill or Boolean operation.

///|
fn flush_connections(
  state : ScanState,
  decide : (EngineEvent) -> ConnectionDecision,
) -> Bool raise ScanAbort {
  while state.connection_cursor < state.pending_connections.length() {
    let event = state.pending_connections[state.connection_cursor]
    if !apply_connection(state, event, decide(event)) {
      if event.kind == EntryContact {
        let vertex = edge_get(state, event.first).bot
        state.entry_contact = Some((vertex, event.first, event.second))
        state.phase = AwaitEntryContact(vertex, event.first, event.second)
      }
      return false
    }
    if event.kind == EntryContact && state.entry_contact is Some(_) {
      state.entry_contact = None
      state.phase = Horizontals
    }
    state.connection_cursor += 1
  }
  state.pending_connections.clear()
  state.connection_cursor = 0
  true
}

///|
/// One real geometry phase; Defer leaves a visible pending action and no fake result.
#warnings("-unused_value")
fn engine_step(
  state : ScanState,
  decide : (EngineEvent) -> ConnectionDecision,
) -> Result[ScanPhase, ClipError] {
  if state.failure is Some(error) {
    return Err(error)
  }
  if state.phase == Complete {
    return Ok(Complete)
  }
  try {
    scan_spend(state, 0, 1, location(ScanEvents, None, None))
    let had_pending = state.connection_cursor <
      state.pending_connections.length()
    if !flush_connections(state, decide) {
      return Ok(state.phase)
    }
    if had_pending {
      return Ok(state.phase)
    }
    match state.phase {
      EnterMinima if state.boolean_operation is Some(operation) =>
        boolean_enter_height(state, operation)
      EnterMinima => {
        let y = state.current_y.unwrap()
        // Preserve the immutable minima indices before enter_height removes the bucket.
        let minima = state.entries.get(y)
        enter_height(state)
        if minima is Some(indices) {
          for index in indices {
            scan_spend(state, 0, 1, location(ScanEvents, None, None))
            let m = state.minima[index]
            let mut first : EdgeId? = None
            let mut second : EdgeId? = None
            let mut cursor = state.ael_head
            while cursor is Some(id) {
              let e = edge_get(state, id)
              scan_spend(
                state,
                0,
                1,
                scan_location(state, Some(e.bot), ScanEvents),
              )
              if state.chains[e.chain].minima == index {
                if first is None {
                  first = Some(id)
                } else {
                  second = Some(id)
                  break
                }
              }
              cursor = e.next_ael
            }
            scan_spend(
              state,
              1,
              1,
              scan_location(state, Some(m.vertex), ScanEvents),
            )
            state.pending_connections.push({
              kind: Minimum,
              first: first.unwrap(),
              second: second.unwrap(),
              point: state.vertices[m.vertex].point,
            })
            for entering in [first.unwrap(), second.unwrap()] {
              let mut resident = state.ael_head
              while resident is Some(other) {
                let edge = edge_get(state, other)
                scan_spend(
                  state,
                  0,
                  1,
                  scan_location(state, Some(m.vertex), ScanEvents),
                )
                let other_min = state.chains[edge.chain].minima
                let older = state.vertices[state.minima[other_min].vertex].point.y >
                  y ||
                  other_min < index
                if other_min != index &&
                  older &&
                  edge.current_x == state.vertices[m.vertex].point.x {
                  scan_spend(
                    state,
                    1,
                    1,
                    scan_location(state, Some(m.vertex), ScanEvents),
                  )
                  state.pending_connections.push({
                    kind: EntryContact,
                    first: entering,
                    second: other,
                    point: state.vertices[m.vertex].point,
                  })
                }
                resident = edge.next_ael
              }
            }
          }
        }
        state.entry_contact = None
        state.phase = Horizontals
        ignore(flush_connections(state, decide))
      }
      AwaitEntryContact(v, a, b) => {
        let event : EngineEvent = {
          kind: EntryContact,
          first: a,
          second: b,
          point: state.vertices[v].point,
        }
        if apply_connection(state, event, decide(event)) {
          state.entry_contact = None
          state.phase = Horizontals
        }
      }
      Horizontals =>
        match horizontal_pop(state) {
          Some(id) => {
            state.phase = AwaitHorizontal(id)
            ignore(process_horizontal(state, id, decide))
          }
          None =>
            match find_maxima(state) {
              Some(phase) => state.phase = phase
              None =>
                if state.needs_entries {
                  state.needs_entries = false
                  state.phase = EnterMinima
                } else {
                  if state.preserve_collinear {
                    fixed_horizontal_convert(state)
                  }
                  state.phase = PrepareBand
                }
            }
        }
      AwaitHorizontal(id) => ignore(process_horizontal(state, id, decide))
      AwaitIntersections(_) => {
        build_intersections(state)
        if state.intersection_cursor < state.intersections.length() {
          ignore(process_intersection(state, decide))
        }
        if state.intersection_cursor == state.intersections.length() {
          finish_intersections(state)
        }
      }
      AwaitMaxima(v, a, b) => {
        if state.preserve_collinear {
          // Clipper2 DoMaxima consumes every real crossing between the pair before
          // their maximum connection. Equal-height overlapping quads need this too.
          let point = state.vertices[v].point
          boolean_split_join(state, a, point)
          boolean_split_join(state, b, point)
          while edge_get(state, a).next_ael is Some(next) && next != b {
            let crossing : EngineEvent = {
              kind: Crossing,
              first: a,
              second: next,
              point,
            }
            if !apply_connection(state, crossing, decide(crossing)) {
              return Ok(state.phase)
            }
            ael_swap(state, a, next)
          }
        }
        let event : EngineEvent = {
          kind: Maximum,
          first: a,
          second: b,
          point: state.vertices[v].point,
        }
        if apply_connection(state, event, decide(event)) {
          if edge_get(state, a).output_end is Some(_) ||
            edge_get(state, b).output_end is Some(_) {
            output_error(state, "Maxima decision left live output ends")
          }
          commit_maxima(state, a, b)
        }
      }
      TopVertices => {
        let mut cursor = state.ael_head
        while cursor is Some(id) {
          let edge = edge_get(state, id)
          scan_spend(state, 0, 1, scan_location(state, Some(edge.top), ScanTop))
          cursor = edge.next_ael
          if state.preserve_collinear &&
            !edge.at_exit &&
            state.vertices[edge.top].point.y == state.current_y.unwrap() &&
            edge.cursor + 2 == state.chains[edge.chain].vertices.length() {
            let mut partner : EdgeId? = None
            let mut candidate = edge.next_ael
            while candidate is Some(other) {
              scan_spend(
                state,
                0,
                1,
                scan_location(state, Some(edge.top), ScanTop),
              )
              let other_edge = edge_get(state, other)
              if other_edge.top == edge.top &&
                other_edge.horizontal == NonHorizontal &&
                other_edge.cursor + 2 ==
                state.chains[other_edge.chain].vertices.length() {
                partner = Some(other)
                break
              }
              candidate = other_edge.next_ael
            }
            if partner is Some(other) {
              let previous = edge.prev_ael
              edge_advance(state, id)
              if !edge_get(state, other).at_exit {
                edge_advance(state, other)
              }
              state.phase = AwaitMaxima(edge.top, id, other)
              match engine_step(state, decide) {
                Ok(_) => ()
                Err(error) => raise ScanAbort(error)
              }
              cursor = match previous {
                Some(p) => edge_get(state, p).next_ael
                None => state.ael_head
              }
              continue
            }
            // Fixed DoMaxima leaves output untouched while its partner is
            // horizontal. That horizontal may exchange contribution before close.
            edge_advance(state, id)
            continue
          }
          if (!state.preserve_collinear || !edge.at_exit) &&
            state.vertices[edge.top].point.y == state.current_y.unwrap() {
            ignore(output_add_edge(state, id, state.vertices[edge.top].point))
            boolean_split_join(state, id, state.vertices[edge.top].point)
            edge_advance(state, id)
            boolean_check_neighbors(state, id, state.vertices[edge.bot].point)
          }
        }
        state.phase = Horizontals
      }
      _ =>
        match scan_step(state) {
          Ok(_) => ()
          Err(error) => raise ScanAbort(error)
        }
    }
    Ok(state.phase)
  } catch {
    ScanAbort(error) => {
      state.failure = Some(error)
      state.phase = Failed
      Err(error)
    }
  }
}

///|
#warnings("-unused_value")
fn engine_invariants(
  state : ScanState,
  metered? : Bool = false,
) -> Result[Unit, String] {
  match scan_invariants(state, metered~) {
    Err(m) => return Err(m)
    Ok(_) => ()
  }
  match output_invariants(state, metered~) {
    Err(m) => return Err(m)
    Ok(_) => ()
  }
  if state.intersection_cursor < 0 ||
    state.intersection_cursor > state.intersections.length() ||
    state.connection_cursor < 0 ||
    state.connection_cursor > state.pending_connections.length() {
    return Err("Invalid event cursor")
  }
  for i = state.intersection_cursor; i < state.intersections.length(); i = i + 1 {
    match validation_charge(state, metered, 0, 1) {
      Ok(_) => ()
      Err(e) => return Err(e)
    }
    let event = state.intersections[i]
    guard edge_peek(state, event.first) is Some(a) else {
      return Err("Stale first intersection edge")
    }
    guard edge_peek(state, event.second) is Some(b) else {
      return Err("Stale second intersection edge")
    }
    if a.bot != event.first_bot ||
      a.top != event.first_top ||
      b.bot != event.second_bot ||
      b.top != event.second_top {
      return Err("Stale intersection segment")
    }
    if state.failure is None &&
      (
        state.phase != AwaitIntersections(event.top_y) ||
        state.current_y != Some(event.bottom_y)
      ) {
      return Err("Stale intersection stage")
    }
  }
  for i = state.connection_cursor
      i < state.pending_connections.length()
      i = i + 1 {
    match validation_charge(state, metered, 0, 1) {
      Ok(_) => ()
      Err(e) => return Err(e)
    }
    let event = state.pending_connections[i]
    if edge_peek(state, event.first) is None ||
      edge_peek(state, event.second) is None {
      return Err("Stale pending connection edge")
    }
  }
  for index = 0; index < state.slots.length(); index = index + 1 {
    match validation_charge(state, metered, 0, 1) {
      Ok(_) => ()
      Err(e) => return Err(e)
    }
    let slot = state.slots[index]
    if slot.edge is Some(edge) {
      let bound = state.vertices.length()
      if (edge.wind_dx != 1 && edge.wind_dx != -1) ||
        edge.wind_count < -bound ||
        edge.wind_count > bound ||
        edge.wind_other < -bound ||
        edge.wind_other > bound {
        return Err("Invalid signed winding state")
      }
      if edge.joined is Some(other) {
        guard edge_peek(state, other) is Some(pair) else {
          return Err("Stale active seam")
        }
        if pair.joined != Some({ slot: index, generation: slot.generation, }) ||
          (edge.prev_ael != Some(other) && edge.next_ael != Some(other)) ||
          edge.output_end is Some(_) ||
          pair.output_end is Some(_) {
          return Err("Invalid active seam ownership")
        }
      }
    }
  }
  let stats = state.stats
  if stats.intersection_events < 0 ||
    stats.horizontal_joins < 0 ||
    stats.output_records < 0 ||
    stats.output_points < 0 ||
    stats.intersection_events > stats.nodes ||
    stats.horizontal_joins > stats.nodes ||
    stats.output_records > stats.nodes ||
    stats.output_points > stats.nodes {
    return Err("Invalid cumulative topology counters")
  }
  for join in state.output.joins {
    match validation_charge(state, metered, 0, 1) {
      Ok(_) => ()
      Err(e) => return Err(e)
    }
    if join.consumed {
      continue
    }
    if join.first < 0 ||
      join.first >= state.output.points.length() ||
      join.second < 0 ||
      join.second >= state.output.points.length() {
      return Err("Invalid horizontal join node")
    }
    let a = state.output.points[join.first]
    let b = state.output.points[join.second]
    if !a.alive || !b.alive || a.point != b.point || join.left >= join.right {
      return Err("Stale horizontal join geometry")
    }
  }
  Ok(())
}