// Copyright (c) 2026 BigSaltyMan. BSL-1.0; see LICENSE.
// Horizontal layering and explicit ring joins adapted from Clipper2 concepts,
// Copyright (c) Angus Johnson 2010-2025.

///|
fn horizontal_segment(
  state : ScanState,
  first : OutPtId,
  second : OutPtId,
) -> Unit raise ScanAbort {
  let a = output_point(state, first)
  let b = output_point(state, second)
  if a.point.y != b.point.y || a.point.x == b.point.x {
    return
  }
  if output_resolve(state, a.record) != output_resolve(state, b.record) {
    return
  }
  let left = if a.point.x < b.point.x { a.point.x } else { b.point.x }
  let right = if a.point.x > b.point.x { a.point.x } else { b.point.x }
  horizontal_spend(state, 1, 1, location(HorizontalEvents, None, None))
  let segment : HorizontalSegment = {
    first,
    second,
    y: a.point.y,
    left,
    right,
    to_right: a.point.x < b.point.x,
  }
  // Candidates retain node identities. Point contact alone creates no join.
  for existing in state.output.horizontals {
    horizontal_spend(state, 0, 1, location(HorizontalEvents, None, None))
    if existing.y != segment.y {
      continue
    }
    let low = if existing.left > left { existing.left } else { left }
    let high = if existing.right < right { existing.right } else { right }
    if low >= high {
      continue
    }
    let one = horizontal_node_at(state, existing, low)
    let two = horizontal_node_at(state, segment, low)
    if one != two {
      horizontal_spend(state, 1, 1, location(HorizontalEvents, None, None))
      state.stats.horizontal_joins += 1
      state.output.joins.push({
        first: one,
        second: two,
        left: low,
        right: high,
        consumed: false,
        opposed: existing.to_right != segment.to_right,
      })
    }
  }
  state.output.horizontals.push(segment)
}

///|
/// Locate/split the current horizontal arc; original endpoint identities survive.
fn horizontal_node_at(
  state : ScanState,
  segment : HorizontalSegment,
  x : Int64,
) -> OutPtId raise ScanAbort {
  let start = output_point(state, segment.first)
  if start.point.x == x {
    return segment.first
  }
  if output_point(state, segment.second).point.x == x {
    return segment.second
  }
  let next = output_point(state, start.next)
  let direction = if next.point.y == segment.y &&
    (
      (segment.to_right && next.point.x > start.point.x) ||
      (!segment.to_right && next.point.x < start.point.x)
    ) {
    true
  } else {
    false
  }
  let mut cursor = segment.first
  for steps = 0; steps <= state.output.points.length(); steps = steps + 1 {
    horizontal_spend(state, 0, 1, location(HorizontalEvents, None, None))
    let a = output_point(state, cursor)
    let target = if direction { a.next } else { a.prev }
    let b = output_point(state, target)
    if a.point.y != segment.y || b.point.y != segment.y {
      output_error(
        state, "Horizontal candidate no longer follows a horizontal arc",
      )
    }
    if b.point.x == x {
      return target
    }
    if (a.point.x < x && x < b.point.x) || (b.point.x < x && x < a.point.x) {
      let record = output_resolve(state, a.record)
      let node = output_new_point(state, record, Point64::new(x, segment.y))
      let left = if direction { cursor } else { target }
      let right = if direction { target } else { cursor }
      state.output.points[node].prev = left
      state.output.points[node].next = right
      state.output.points[left].next = node
      state.output.points[right].prev = node
      state.output.records[record].revision += 1
      return node
    }
    if target == segment.second {
      break
    }
    cursor = target
  }
  output_error(state, "Horizontal overlap point is not on the candidate arc")
}

///|
/// Explicit connection decision; no automatic union based on coordinate equality.
#warnings("-unused_value")
fn execute_horizontal_join(
  state : ScanState,
  index : Int,
  splice : Bool,
) -> Unit raise ScanAbort {
  if index < 0 || index >= state.output.joins.length() {
    output_error(state, "Invalid horizontal join")
  }
  let join = state.output.joins[index]
  if join.consumed {
    output_error(state, "Horizontal join was already consumed")
  }
  horizontal_spend(state, 0, 1, location(HorizontalEvents, None, None))
  if join.left >= join.right {
    output_error(state, "Horizontal join has no positive overlap")
  }
  let a = output_point(state, join.first)
  let b = output_point(state, join.second)
  // Resolve again now, after any preceding split/merge. No cached record ownership.
  ignore(output_resolve(state, a.record))
  ignore(output_resolve(state, b.record))
  if a.point != b.point {
    output_error(state, "Horizontal join endpoints no longer match")
  }
  if splice && !join.opposed {
    output_error(
      state, "Same-direction overlap requires contribution selection, not a ring splice",
    )
  }
  if splice {
    ignore(output_splice(state, join.first, join.second))
  }
  join.consumed = true
}

///|
fn horizontal_output_span(
  state : ScanState,
  before : OutPtId?,
  edge : EdgeId,
) -> Unit raise ScanAbort {
  if before is Some(a) && output_endpoint(state, edge) is Some(b) && a != b {
    horizontal_segment(state, a, b)
  }
}

///|
/// Consume at most one adjacent crossing; a following call continues the same HE.
fn process_horizontal(
  state : ScanState,
  id : EdgeId,
  decide : (EngineEvent) -> ConnectionDecision,
) -> Bool raise ScanAbort {
  let edge = edge_get(state, id)
  if state.held_horizontal != Some(id) || edge.horizontal == NonHorizontal {
    scan_abort(
      state,
      InvalidInput(
        location(HorizontalEvents, None, None),
        "No matching held horizontal",
      ),
    )
  }
  let start = state.vertices[edge.bot].point
  let end = state.vertices[edge.top].point
  if !state.preserve_collinear || !state.horizontal_started {
    let initial = output_add_edge(
      state,
      id,
      Point64::new(edge.current_x, start.y),
    )
    if state.preserve_collinear && initial is Some(op) {
      fixed_horizontal_trial(state, op)
    }
    state.horizontal_started = true
  }
  let right = edge.horizontal == ToRight
  let neighbor = if right { edge.next_ael } else { edge.prev_ael }
  if neighbor is Some(other) {
    let candidate = edge_get(state, other)
    horizontal_spend(
      state,
      0,
      1,
      scan_location(state, Some(edge.bot), HorizontalEvents),
    )
    let within = if right {
      candidate.current_x >= edge.current_x && candidate.current_x <= end.x
    } else {
      candidate.current_x <= edge.current_x && candidate.current_x >= end.x
    }
    let chain = state.chains[edge.chain]
    let oc = state.chains[candidate.chain]
    let own_start = chain.minima == oc.minima &&
      edge.cursor == 0 &&
      candidate.cursor == 0 &&
      candidate.current_x == start.x
    let own_exit = chain.vertices[chain.vertices.length() - 1] ==
      oc.vertices[oc.vertices.length() - 1] &&
      candidate.at_exit &&
      candidate.current_x == end.x
    if state.preserve_collinear &&
      own_exit &&
      edge.cursor + 2 == chain.vertices.length() {
      state.held_horizontal = None
      edge_advance(state, id)
      state.phase = AwaitMaxima(
        edge.top,
        if right {
          id
        } else {
          other
        },
        if right {
          other
        } else {
          id
        },
      )
      match engine_step(state, decide) {
        Ok(_) => ()
        Err(error) => raise ScanAbort(error)
      }
      state.phase = Horizontals
      return true
    }
    let outgoing_break = if within &&
      candidate.current_x == end.x &&
      candidate.horizontal == NonHorizontal &&
      edge.cursor + 2 < chain.vertices.length() {
      let next = state.vertices[chain.vertices[edge.cursor + 2]].point
      let relation = scan_outgoing_compare(
        state.vertices[candidate.bot].point,
        state.vertices[candidate.top].point,
        next,
      ) catch {
        ScanAbort(error) => scan_abort(state, error)
      }
      if right {
        relation >= 0
      } else {
        relation <= 0
      }
    } else {
      false
    }
    if within && !own_exit && !outgoing_break {
      if state.preserve_collinear || !own_start {
        let event : EngineEvent = {
          kind: HorizontalCrossing,
          first: if right {
            id
          } else {
            other
          },
          second: if right {
            other
          } else {
            id
          },
          point: Point64::new(candidate.current_x, start.y),
        }
        let before = output_endpoint(state, id)
        if !apply_connection(state, event, decide(event)) {
          return false
        }
        if state.preserve_collinear {
          if output_endpoint(state, id) is Some(op) {
            fixed_horizontal_trial(state, op)
          }
        } else {
          horizontal_output_span(state, before, id)
        }
      }
      if right {
        ael_swap(state, id, other)
      } else {
        ael_swap(state, other, id)
      }
      if state.preserve_collinear {
        let point = Point64::new(candidate.current_x, start.y)
        if right {
          if candidate.prev_ael is Some(prev) {
            boolean_check_join(state, prev, other, point)
          }
        } else if candidate.next_ael is Some(next) {
          boolean_check_join(state, other, next, point)
        }
      }
      edge.current_x = candidate.current_x
      state.phase = AwaitHorizontal(id)
      return true
    }
  }
  let before = output_endpoint(state, id)
  let ending = output_add_edge(state, id, end)
  if state.preserve_collinear {
    if ending is Some(op) {
      fixed_horizontal_trial(state, op)
    }
  } else {
    horizontal_output_span(state, before, id)
  }
  commit_horizontal(state, id)
  boolean_check_neighbors(state, id, end)
  true
}

///|
fn horizontal_spend(
  state : ScanState,
  nodes : Int,
  work : Int,
  loc : ErrorLocation,
) -> Unit raise ScanAbort {
  output_spend(state, nodes, work, loc~)
}

///|
priv struct FixedHorizontal {
  mut left : OutPtId
  right : OutPtId
  to_right : Bool
}

///|
fn fixed_horizontal_trial(
  state : ScanState,
  id : OutPtId,
) -> Unit raise ScanAbort {
  let p = output_point(state, id).point
  horizontal_spend(state, 1, 1, location(HorizontalEvents, None, None))
  state.output.horizontals.push({
    first: id,
    second: id,
    y: p.y,
    left: p.x,
    right: p.x,
    to_right: false,
  })
}

///|
fn fixed_horizontal_duplicate(
  state : ScanState,
  id : OutPtId,
  after : Bool,
) -> OutPtId raise ScanAbort {
  let op = output_point(state, id)
  let record = output_resolve(state, op.record)
  let new = output_new_point(state, record, op.point)
  let previous = if after { id } else { op.prev }
  let next = if after { op.next } else { id }
  state.output.points[new].prev = previous
  state.output.points[new].next = next
  state.output.points[previous].next = new
  state.output.points[next].prev = new
  state.output.records[record].revision += 1
  new
}

///|
fn fixed_horizontal_convert(state : ScanState) -> Unit raise ScanAbort {
  if state.output.horizontals.is_empty() {
    return
  }
  horizontal_spend(state, 2, 0, location(HorizontalEvents, None, None))
  let segments : Array[FixedHorizontal] = []
  let seen : Set[OutPtId] = Default::default()
  for trial in state.output.horizontals {
    horizontal_spend(state, 0, 1, location(HorizontalEvents, None, None))
    let original = output_point(state, trial.first)
    let rec = state.output.records[output_resolve(state, original.record)]
    let mut p = trial.first
    let mut n = p
    if rec.status == Building {
      // Our Back endpoint corresponds to the fixed engine's outrec.pts.
      let z = rec.anchor.unwrap()
      let a = output_point(state, z).prev
      while p != z &&
            output_point(state, output_point(state, p).prev).point.y ==
            original.point.y {
        horizontal_spend(state, 0, 1, location(HorizontalEvents, None, None))
        p = output_point(state, p).prev
      }
      while n != a &&
            output_point(state, output_point(state, n).next).point.y ==
            original.point.y {
        horizontal_spend(state, 0, 1, location(HorizontalEvents, None, None))
        n = output_point(state, n).next
      }
    } else {
      while output_point(state, p).prev != n &&
            output_point(state, output_point(state, p).prev).point.y ==
            original.point.y {
        horizontal_spend(state, 0, 1, location(HorizontalEvents, None, None))
        p = output_point(state, p).prev
      }
      while output_point(state, n).next != p &&
            output_point(state, output_point(state, n).next).point.y ==
            original.point.y {
        horizontal_spend(state, 0, 1, location(HorizontalEvents, None, None))
        n = output_point(state, n).next
      }
    }
    let px = output_point(state, p).point.x
    let nx = output_point(state, n).point.x
    if px == nx {
      continue
    }
    let left = if px < nx { p } else { n }
    if seen.contains(left) {
      continue
    }
    horizontal_spend(state, 3, 1, location(HorizontalEvents, None, None))
    seen.add(left)
    let segment : FixedHorizontal = {
      left,
      right: if px < nx {
        n
      } else {
        p
      },
      to_right: px < nx,
    }
    // Stable insertion order is observable when several horizontal runs start together.
    segments.push(segment)
    let mut j = segments.length() - 1
    while j > 0 {
      horizontal_spend(state, 0, 1, location(HorizontalEvents, None, None))
      if output_point(state, segments[j - 1].left).point.x <=
        output_point(state, segments[j].left).point.x {
        break
      }
      let temp = segments[j - 1]
      segments[j - 1] = segments[j]
      segments[j] = temp
      j -= 1
    }
  }
  for i = 0; i < segments.length(); i = i + 1 {
    let a = segments[i]
    for j = i + 1; j < segments.length(); j = j + 1 {
      horizontal_spend(state, 0, 1, location(HorizontalEvents, None, None))
      let b = segments[j]
      if output_point(state, b.left).point.x >=
        output_point(state, a.right).point.x ||
        a.to_right == b.to_right ||
        output_point(state, b.right).point.x <=
        output_point(state, a.left).point.x {
        continue
      }
      let y = output_point(state, a.left).point.y
      let mut first = a
      let mut second = b
      if !a.to_right {
        first = b
        second = a
      }
      // Follow the same directional cursor order, then duplicate actual endpoints;
      // do not insert an arithmetic overlap coordinate into either run.
      if a.to_right {
        while output_point(state, output_point(state, a.left).next).point.y == y &&
              output_point(state, output_point(state, a.left).next).point.x <=
              output_point(state, b.left).point.x {
          horizontal_spend(state, 0, 1, location(HorizontalEvents, None, None))
          a.left = output_point(state, a.left).next
        }
        while output_point(state, output_point(state, b.left).prev).point.y == y &&
              output_point(state, output_point(state, b.left).prev).point.x <=
              output_point(state, a.left).point.x {
          horizontal_spend(state, 0, 1, location(HorizontalEvents, None, None))
          b.left = output_point(state, b.left).prev
        }
      } else {
        while output_point(state, output_point(state, a.left).prev).point.y == y &&
              output_point(state, output_point(state, a.left).prev).point.x <=
              output_point(state, b.left).point.x {
          horizontal_spend(state, 0, 1, location(HorizontalEvents, None, None))
          a.left = output_point(state, a.left).prev
        }
        while output_point(state, output_point(state, b.left).next).point.y == y &&
              output_point(state, output_point(state, b.left).next).point.x <=
              output_point(state, a.left).point.x {
          horizontal_spend(state, 0, 1, location(HorizontalEvents, None, None))
          b.left = output_point(state, b.left).next
        }
      }
      let one = fixed_horizontal_duplicate(state, first.left, true)
      let two = fixed_horizontal_duplicate(state, second.left, false)
      horizontal_spend(state, 1, 1, location(HorizontalEvents, None, None))
      state.stats.horizontal_joins += 1
      state.output.joins.push({
        first: one,
        second: two,
        left: 0L,
        right: 1L,
        consumed: false,
        opposed: true,
      })
    }
  }
  state.output.horizontals.clear()
}

///|
fn fixed_horizontal_join(
  state : ScanState,
  index : Int,
) -> Unit raise ScanAbort {
  let join = state.output.joins[index]
  let ar = output_resolve(state, output_point(state, join.first).record)
  let br = output_resolve(state, output_point(state, join.second).record)
  let anchor = state.output.records[ar].anchor.unwrap()
  let next = output_point(state, join.first).next
  let previous = output_point(state, join.second).prev
  let created = output_splice(state, join.first, previous)
  if output_point(state, anchor).record == ar {
    state.output.records[ar].anchor = Some(anchor)
  }
  if ar == br {
    state.output.records[created].anchor = Some(next)
  }
  join.consumed = true
}