// Copyright (c) 2026 BigSaltyMan. BSL-1.0; see LICENSE.

///|
pub(all) enum Orientation {
  CounterClockwise
  Clockwise
  Collinear
} derive(Eq, Debug)

///|
pub(all) enum PointLocation {
  Outside
  Inside
  OnBoundary
} derive(Eq, Debug)

///|
pub(all) struct Bounds64 {
  min_x : Int64
  min_y : Int64
  max_x : Int64
  max_y : Int64
} derive(Eq, Debug)

///|
/// Validated coordinates: |difference| <= 2e9, |product| <= 4e18;
/// |cross| <= 8e18 < 9223372036854775807. Re-audit before raising the limit.
fn cross_validated(a : Point64, b : Point64, c : Point64) -> Int64 {
  cross_components(b.x - a.x, b.y - a.y, c.x - a.x, c.y - a.y)
}

///|
/// All components are validated coordinate differences, bounded by +/-2e9.
fn cross_components(ax : Int64, ay : Int64, bx : Int64, by : Int64) -> Int64 {
  ax * by - ay * bx
}

///|
pub fn cross(
  a : Point64,
  b : Point64,
  c : Point64,
  limits? : Limits = Default::default(),
) -> Result[Int64, ClipError] {
  if check_three(a, b, c, limits, Cross) is Some(e) {
    return Err(e)
  }
  Ok(cross_validated(a, b, c))
}

///|
/// Mathematical axes: x right, y up; positive cross is counterclockwise.
pub fn orientation(
  a : Point64,
  b : Point64,
  c : Point64,
  limits? : Limits = Default::default(),
) -> Result[Orientation, ClipError] {
  if check_three(a, b, c, limits, Orientation) is Some(e) {
    return Err(e)
  }
  let value = cross_validated(a, b, c)
  Ok(
    if value > 0L {
      CounterClockwise
    } else if value < 0L {
      Clockwise
    } else {
      Collinear
    },
  )
}

///|
pub fn bounds(
  path : Path64,
  limits? : Limits = Default::default(),
) -> Result[Bounds64?, ClipError] {
  if check_path(path, limits, Bounds, None) is Some(e) {
    return Err(e)
  }
  if path.is_empty() {
    return Ok(None)
  }
  let mut min_x = path[0].x
  let mut min_y = path[0].y
  let mut max_x = min_x
  let mut max_y = min_y
  for p in path {
    min_x = if p.x < min_x { p.x } else { min_x }
    min_y = if p.y < min_y { p.y } else { min_y }
    max_x = if p.x > max_x { p.x } else { max_x }
    max_y = if p.y > max_y { p.y } else { max_y }
  }
  Ok(Some({ min_x, min_y, max_x, max_y, }))
}

///|
priv enum AreaAccumulator {
  Small(Int64)
  Large(BigInt)
}

///|
/// Each shoelace term is in [-2e18, 2e18]. Test against safe thresholds
/// before adding; once promoted, all remaining operations stay exact BigInt.
fn add_area_term(sum : AreaAccumulator, term : Int64) -> AreaAccumulator {
  match sum {
    Large(value) => Large(value + BigInt::from_int64(term))
    Small(value) =>
      if (term > 0L && value > 9223372036854775807L - term) ||
        (term < 0L && value < -9223372036854775808L - term) {
        Large(BigInt::from_int64(value) + BigInt::from_int64(term))
      } else {
        Small(value + term)
      }
  }
}

///|
fn accumulate_area(path : Path64, initial : AreaAccumulator) -> AreaAccumulator {
  let mut sum = initial
  if path.length() < 3 {
    return sum
  }
  let mut previous = path[path.length() - 1]
  for current in path {
    sum = add_area_term(sum, previous.x * current.y - previous.y * current.x)
    previous = current
  }
  sum
}

///|
fn exact_area(sum : AreaAccumulator) -> BigInt {
  match sum {
    Small(value) => BigInt::from_int64(value)
    Large(value) => value
  }
}

///|
/// Exact twice signed area; closes the last edge implicitly. Zero area is
/// not a validity test for self-intersecting or degenerate contours.
pub fn area2(
  path : Path64,
  limits? : Limits = Default::default(),
) -> Result[BigInt, ClipError] {
  if check_path(path, limits, Area2, None) is Some(e) {
    return Err(e)
  }
  Ok(exact_area(accumulate_area(path, Small(0L))))
}

///|
fn point_on_segment_validated(
  point : Point64,
  a : Point64,
  b : Point64,
) -> Bool {
  cross_validated(a, b, point) == 0L &&
  point.x >= (if a.x < b.x { a.x } else { b.x }) &&
  point.x <= (if a.x > b.x { a.x } else { b.x }) &&
  point.y >= (if a.y < b.y { a.y } else { b.y }) &&
  point.y <= (if a.y > b.y { a.y } else { b.y })
}

///|
pub fn point_on_segment(
  point : Point64,
  a : Point64,
  b : Point64,
  limits? : Limits = Default::default(),
) -> Result[Bool, ClipError] {
  if check_three(point, a, b, limits, PointOnSegment) is Some(e) {
    return Err(e)
  }
  Ok(point_on_segment_validated(point, a, b))
}

///|
/// Single closed contour, EvenOdd rule. Half-open y intervals count a shared
/// vertex once; horizontal edges participate only in the boundary check.
pub fn point_in_polygon(
  point : Point64,
  path : Path64,
  limits? : Limits = Default::default(),
) -> Result[PointLocation, ClipError] {
  if check_path(path, limits, PointInPolygon, Some(point)) is Some(e) {
    return Err(e)
  }
  if path.is_empty() {
    return Ok(Outside)
  }
  let mut inside = false
  let mut a = path[path.length() - 1]
  for b in path {
    if point_on_segment_validated(point, a, b) {
      return Ok(OnBoundary)
    }
    if (a.y > point.y) != (b.y > point.y) {
      let side = cross_validated(a, b, point)
      if (b.y > a.y && side > 0L) || (b.y < a.y && side < 0L) {
        inside = !inside
      }
    }
    a = b
  }
  Ok(if inside { Inside } else { Outside })
}

///|
/// Internal grid displacement rounding. Explicit ties-to-even on both targets.
fn scan_round_displacement(value : Double) -> Int64 raise ScanAbort {
  let loc = location(ScanBand, None, None)
  if value.is_nan() ||
    value.is_inf() ||
    value < -2000000001.0 ||
    value > 2000000001.0 {
    raise ScanAbort(
      NumericFailure(loc, "Non-finite or out-of-range scan displacement"),
    )
  }
  let floor_value = @double.floor(value)
  let lower = floor_value.to_int64()
  let fraction = value - floor_value
  let result = if fraction > 0.5 || (fraction == 0.5 && lower % 2L != 0L) {
    lower + 1L
  } else {
    lower
  }
  if result < -2000000000L || result > 2000000000L {
    raise ScanAbort(
      NumericFailure(loc, "Rounded scan displacement is out of range"),
    )
  }
  result
}

// Clipper2-derived projection: Copyright (c) Angus Johnson 2010-2025.
// Distributed under BSL-1.0; see LICENSE.

///|
/// Adapted from fixed Clipper2 TopX. Round the displacement, never bot.x+delta.
/// Coordinates have already passed the integer-core input check.
fn scan_project(
  bot : Point64,
  top : Point64,
  y : Int64,
) -> Int64 raise ScanAbort {
  let loc = location(ScanBand, None, None)
  if bot.y <= top.y || y > bot.y || y < top.y {
    raise ScanAbort(
      InvalidInput(
        loc, "Projection requires a descending nonhorizontal segment and an in-range height",
      ),
    )
  }
  if y == top.y || bot.x == top.x {
    return top.x
  }
  if y == bot.y {
    return bot.x
  }
  let dx = (top.x - bot.x).to_double() / (top.y - bot.y).to_double()
  if dx.is_nan() || dx.is_inf() {
    raise ScanAbort(NumericFailure(loc, "Non-finite scan slope"))
  }
  let displacement = scan_round_displacement(dx * (y - bot.y).to_double())
  if (displacement > 0L && bot.x > 9223372036854775807L - displacement) ||
    (displacement < 0L && bot.x < -9223372036854775808L - displacement) {
    raise ScanAbort(NumericFailure(loc, "Scan projection addition overflow"))
  }
  let result = bot.x + displacement
  if result < -COORDINATE_LIMIT || result > COORDINATE_LIMIT {
    raise ScanAbort(
      NumericFailure(loc, "Scan projection exceeds the coordinate limit"),
    )
  }
  result
}

///|
/// C++ ordinary GetLineIntersectPt branch: truncate final coordinates to zero.
fn intersection_integer(value : Double) -> Int64 raise ScanAbort {
  if value.is_nan() ||
    value.is_inf() ||
    value < -1000000000.0 ||
    value > 1000000000.0 {
    raise ScanAbort(
      NumericFailure(
        location(Intersections, None, None),
        "Intersection coordinate is not finite or in range",
      ),
    )
  }
  value.to_int64()
}

///|
/// Inputs are validated; all differences <=2e9, Double products <=4e18.
/// Deliberately follows the fixed ordinary C++ operation order, not TopX rounding.
fn line_intersection(
  a : Point64,
  b : Point64,
  c : Point64,
  d : Point64,
) -> Point64? raise ScanAbort {
  let dx1 = (b.x - a.x).to_double()
  let dy1 = (b.y - a.y).to_double()
  let dx2 = (d.x - c.x).to_double()
  let dy2 = (d.y - c.y).to_double()
  let det = dy1 * dx2 - dy2 * dx1
  if det == 0.0 {
    return None
  }
  let t = ((a.x - c.x).to_double() * dy2 - (a.y - c.y).to_double() * dx2) / det
  if t.is_nan() || t.is_inf() {
    raise ScanAbort(
      NumericFailure(
        location(Intersections, None, None),
        "Non-finite line parameter",
      ),
    )
  }
  if t <= 0.0 {
    Some(a)
  } else if t >= 1.0 {
    Some(b)
  } else {
    Some(
      Point64::new(
        intersection_integer(a.x.to_double() + t * dx1),
        intersection_integer(a.y.to_double() + t * dy1),
      ),
    )
  }
}

///|
/// Clipper2 GetClosestPointOnSegment: ties-even displacement, not truncation.
fn closest_on_segment(
  p : Point64,
  a : Point64,
  b : Point64,
) -> Point64 raise ScanAbort {
  if a == b {
    return a
  }
  let dx = (b.x - a.x).to_double()
  let dy = (b.y - a.y).to_double()
  let q0 = ((p.x - a.x).to_double() * dx + (p.y - a.y).to_double() * dy) /
    (dx * dx + dy * dy)
  if q0.is_nan() || q0.is_inf() {
    raise ScanAbort(
      NumericFailure(
        location(Intersections, None, None),
        "Non-finite closest-point parameter",
      ),
    )
  }
  let q = if q0 < 0.0 { 0.0 } else if q0 > 1.0 { 1.0 } else { q0 }
  let x = a.x + scan_round_displacement(q * dx)
  let y = a.y + scan_round_displacement(q * dy)
  if x < -COORDINATE_LIMIT ||
    x > COORDINATE_LIMIT ||
    y < -COORDINATE_LIMIT ||
    y > COORDINATE_LIMIT {
    raise ScanAbort(
      NumericFailure(
        location(Intersections, None, None),
        "Closest point is out of range",
      ),
    )
  }
  Point64::new(x, y)
}

///|
/// TopX endpoint look-ahead may extrapolate beyond the other edge's segment.
/// Compare without casting a possibly huge projection (|delta|<=4e18).
fn scan_outgoing_compare(
  bot : Point64,
  top : Point64,
  point : Point64,
) -> Int raise ScanAbort {
  if bot.y == top.y {
    raise ScanAbort(
      InvalidInput(
        location(HorizontalEvents, None, None),
        "Horizontal outgoing comparison",
      ),
    )
  }
  let delta = (top.x - bot.x).to_double() /
    (top.y - bot.y).to_double() *
    (point.y - bot.y).to_double()
  if delta.is_nan() || delta.is_inf() {
    raise ScanAbort(
      NumericFailure(
        location(HorizontalEvents, None, None),
        "Non-finite outgoing projection",
      ),
    )
  }
  if delta > 2000000000.5 {
    return 1
  }
  if delta < -2000000000.5 {
    return -1
  }
  let rounded = scan_round_displacement(delta)
  let x = bot.x + rounded
  if x < point.x {
    -1
  } else if x > point.x {
    1
  } else {
    0
  }
}