///|
fn include_path_bounds_point(bounds : Rect?, point : Point) -> Rect? {
  match bounds {
    None => Some(Rect::new(point.x, point.y, point.x, point.y))
    Some(rect) =>
      Some(
        Rect::new(
          Float::min(rect.left, point.x),
          Float::min(rect.top, point.y),
          Float::max(rect.right, point.x),
          Float::max(rect.bottom, point.y),
        ),
      )
  }
}

///|
pub fn Path::bounds(self : Path) -> Rect? {
  let initial_bounds : Rect? = None
  for bounds = initial_bounds, i = 0; i < self.verbs.length(); i = i + 1 {
    let bounds = match self.verbs[i] {
      MoveTo(point) | LineTo(point) => include_path_bounds_point(bounds, point)
      QuadTo(control, end) =>
        include_path_bounds_point(
          include_path_bounds_point(bounds, control),
          end,
        )
      ConicTo(control, end, _) =>
        include_path_bounds_point(
          include_path_bounds_point(bounds, control),
          end,
        )
      CubicTo(control0, control1, end) =>
        include_path_bounds_point(
          include_path_bounds_point(
            include_path_bounds_point(bounds, control0),
            control1,
          ),
          end,
        )
      Close => bounds
    }
    continue bounds, i + 1
  } nobreak {
    bounds
  }
}

///|
fn path_eval_quad_scalar(
  p0 : Scalar,
  p1 : Scalar,
  p2 : Scalar,
  t : Scalar,
) -> Scalar {
  let u : Scalar = 1.0 - t
  u * u * p0 + u * 2.0 * t * p1 + t * t * p2
}

///|
fn path_eval_conic_scalar(
  p0 : Scalar,
  p1 : Scalar,
  p2 : Scalar,
  weight : Scalar,
  t : Scalar,
) -> Scalar {
  let u : Scalar = 1.0 - t
  let numerator = u * u * p0 + weight * 2.0 * u * t * p1 + t * t * p2
  let denominator = u * u + weight * 2.0 * u * t + t * t
  numerator / denominator
}

///|
fn path_eval_cubic_scalar(
  p0 : Scalar,
  p1 : Scalar,
  p2 : Scalar,
  p3 : Scalar,
  t : Scalar,
) -> Scalar {
  let u : Scalar = 1.0 - t
  u * u * u * p0 + u * 3.0 * u * t * p1 + u * 3.0 * t * t * p2 + t * t * t * p3
}

///|
fn include_path_parametric_point(
  bounds : Rect?,
  x0 : Scalar,
  x1 : Scalar,
  x2 : Scalar,
  y0 : Scalar,
  y1 : Scalar,
  y2 : Scalar,
  t : Scalar,
) -> Rect? {
  if t > 0.0 && t < 1.0 {
    include_path_bounds_point(
      bounds,
      Point::new(
        path_eval_quad_scalar(x0, x1, x2, t),
        path_eval_quad_scalar(y0, y1, y2, t),
      ),
    )
  } else {
    bounds
  }
}

///|
fn include_path_conic_parametric_point(
  bounds : Rect?,
  x0 : Scalar,
  x1 : Scalar,
  x2 : Scalar,
  y0 : Scalar,
  y1 : Scalar,
  y2 : Scalar,
  weight : Scalar,
  t : Scalar,
) -> Rect? {
  if t > 0.0 && t < 1.0 {
    include_path_bounds_point(
      bounds,
      Point::new(
        path_eval_conic_scalar(x0, x1, x2, weight, t),
        path_eval_conic_scalar(y0, y1, y2, weight, t),
      ),
    )
  } else {
    bounds
  }
}

///|
fn include_path_cubic_parametric_point(
  bounds : Rect?,
  x0 : Scalar,
  x1 : Scalar,
  x2 : Scalar,
  x3 : Scalar,
  y0 : Scalar,
  y1 : Scalar,
  y2 : Scalar,
  y3 : Scalar,
  t : Scalar,
) -> Rect? {
  if t > 0.0 && t < 1.0 {
    include_path_bounds_point(
      bounds,
      Point::new(
        path_eval_cubic_scalar(x0, x1, x2, x3, t),
        path_eval_cubic_scalar(y0, y1, y2, y3, t),
      ),
    )
  } else {
    bounds
  }
}

///|
fn include_path_quad_extrema(
  bounds : Rect?,
  start : Point,
  control : Point,
  end : Point,
) -> Rect? {
  let bounds = include_path_bounds_point(
    include_path_bounds_point(bounds, start),
    end,
  )
  let include_axis = fn(
    bounds : Rect?,
    p0 : Scalar,
    p1 : Scalar,
    p2 : Scalar,
  ) -> Rect? {
    let denominator = p0 - 2.0 * p1 + p2
    if denominator == 0.0 {
      bounds
    } else {
      let t = (p0 - p1) / denominator
      include_path_parametric_point(
        bounds,
        start.x,
        control.x,
        end.x,
        start.y,
        control.y,
        end.y,
        t,
      )
    }
  }
  include_axis(
    include_axis(bounds, start.x, control.x, end.x),
    start.y,
    control.y,
    end.y,
  )
}

///|
fn include_path_conic_extrema(
  bounds : Rect?,
  start : Point,
  control : Point,
  end : Point,
  weight : Scalar,
) -> Rect? {
  let bounds = include_path_bounds_point(
    include_path_bounds_point(bounds, start),
    end,
  )
  let include_axis = fn(
    bounds : Rect?,
    p0 : Scalar,
    p1 : Scalar,
    p2 : Scalar,
  ) -> Rect? {
    let denominator = p0 - weight * p1 - p1 + p2 * weight
    if denominator == 0.0 {
      bounds
    } else {
      let t = (p0 - weight * p1) / denominator
      include_path_conic_parametric_point(
        bounds,
        start.x,
        control.x,
        end.x,
        start.y,
        control.y,
        end.y,
        weight,
        t,
      )
    }
  }
  include_axis(
    include_axis(bounds, start.x, control.x, end.x),
    start.y,
    control.y,
    end.y,
  )
}

///|
fn include_path_cubic_axis_extrema(
  bounds : Rect?,
  start : Point,
  control0 : Point,
  control1 : Point,
  end : Point,
  a : Scalar,
  b : Scalar,
  c : Scalar,
) -> Rect? {
  if a == 0.0 {
    if b == 0.0 {
      bounds
    } else {
      include_path_cubic_parametric_point(
        bounds,
        start.x,
        control0.x,
        control1.x,
        end.x,
        start.y,
        control0.y,
        control1.y,
        end.y,
        -c / b,
      )
    }
  } else {
    let discriminant = b * b - 4.0 * a * c
    if discriminant < 0.0 {
      bounds
    } else {
      let root = discriminant.sqrt()
      let bounds = include_path_cubic_parametric_point(
        bounds,
        start.x,
        control0.x,
        control1.x,
        end.x,
        start.y,
        control0.y,
        control1.y,
        end.y,
        (-b + root) / (2.0 * a),
      )
      include_path_cubic_parametric_point(
        bounds,
        start.x,
        control0.x,
        control1.x,
        end.x,
        start.y,
        control0.y,
        control1.y,
        end.y,
        (-b - root) / (2.0 * a),
      )
    }
  }
}

///|
fn include_path_cubic_extrema(
  bounds : Rect?,
  start : Point,
  control0 : Point,
  control1 : Point,
  end : Point,
) -> Rect? {
  let bounds = include_path_bounds_point(
    include_path_bounds_point(bounds, start),
    end,
  )
  let include_axis = fn(
    bounds : Rect?,
    p0 : Scalar,
    p1 : Scalar,
    p2 : Scalar,
    p3 : Scalar,
  ) -> Rect? {
    let a = -p0 + 3.0 * p1 - 3.0 * p2 + p3
    let b = (p0 - p1 * 2.0 + p2) * 2.0
    let c = -p0 + p1
    include_path_cubic_axis_extrema(
      bounds, start, control0, control1, end, a, b, c,
    )
  }
  include_axis(
    include_axis(bounds, start.x, control0.x, control1.x, end.x),
    start.y,
    control0.y,
    control1.y,
    end.y,
  )
}

///|
/// Computes a tighter geometric bound for quadratic, conic, and cubic curves.
///
/// `bounds` records the path's control-point bounds, matching Skia's cached
/// path bounds. This method mirrors Skia's `computeTightBounds` distinction by
/// adding only curve extrema that affect the actual drawn curve.
pub fn Path::compute_tight_bounds(self : Path) -> Rect? {
  let initial_bounds : Rect? = None
  let initial_current : Point? = None
  let initial_contour_start : Point? = None
  for bounds = initial_bounds, current = initial_current, contour_start = initial_contour_start, i = 0
      i < self.verbs.length()
      i = i + 1 {
    let (bounds, current, contour_start) = match self.verbs[i] {
      MoveTo(point) =>
        (include_path_bounds_point(bounds, point), Some(point), Some(point))
      LineTo(point) =>
        (
          match current {
            None => include_path_bounds_point(bounds, point)
            Some(start) =>
              include_path_bounds_point(
                include_path_bounds_point(bounds, start),
                point,
              )
          },
          Some(point),
          contour_start,
        )
      QuadTo(control, end) =>
        (
          match current {
            None => include_path_bounds_point(bounds, end)
            Some(start) =>
              include_path_quad_extrema(bounds, start, control, end)
          },
          Some(end),
          contour_start,
        )
      ConicTo(control, end, weight) =>
        (
          match current {
            None => include_path_bounds_point(bounds, end)
            Some(start) =>
              include_path_conic_extrema(bounds, start, control, end, weight)
          },
          Some(end),
          contour_start,
        )
      CubicTo(control0, control1, end) =>
        (
          match current {
            None => include_path_bounds_point(bounds, end)
            Some(start) =>
              include_path_cubic_extrema(bounds, start, control0, control1, end)
          },
          Some(end),
          contour_start,
        )
      Close =>
        (
          match (current, contour_start) {
            (Some(start), Some(end)) =>
              include_path_bounds_point(
                include_path_bounds_point(bounds, start),
                end,
              )
            _ => bounds
          },
          contour_start,
          contour_start,
        )
    }
    continue bounds, current, contour_start, i + 1
  } nobreak {
    bounds
  }
}

///|
pub fn Path::stroke_bounds(self : Path, paint : Paint) -> Rect? {
  match self.bounds() {
    None => None
    Some(bounds) =>
      if paint.style is Fill || paint.stroke_width <= 0.0 {
        Some(bounds)
      } else {
        let outset = paint.stroke_width / 2.0
        Some(bounds.outset(outset, outset))
      }
  }
}

///|
fn path_contains_crosses_edge(
  start : Point,
  end : Point,
  point : Point,
) -> Bool {
  (start.y > point.y) != (end.y > point.y) &&
  point.x <
  (end.x - start.x) * (point.y - start.y) / (end.y - start.y) + start.x
}

///|
fn Path::path_contains_closed_contours(self : Path, point : Point) -> Bool {
  let initial_inside = false
  let initial_current : Point? = None
  let initial_contour_start : Point? = None
  for inside = initial_inside, current = initial_current, contour_start = initial_contour_start, i = 0
      i < self.verbs.length()
      i = i + 1 {
    let (inside, current, contour_start) = match self.verbs[i] {
      MoveTo(next) => (inside, Some(next), Some(next))
      LineTo(next) =>
        match current {
          None => (inside, Some(next), contour_start)
          Some(start) =>
            (
              if path_contains_crosses_edge(start, next, point) {
                !inside
              } else {
                inside
              },
              Some(next),
              contour_start,
            )
        }
      QuadTo(_, end) | ConicTo(_, end, _) | CubicTo(_, _, end) =>
        match current {
          None => (inside, Some(end), contour_start)
          Some(start) =>
            (
              if path_contains_crosses_edge(start, end, point) {
                !inside
              } else {
                inside
              },
              Some(end),
              contour_start,
            )
        }
      Close =>
        match (current, contour_start) {
          (Some(start), Some(end)) =>
            (
              if path_contains_crosses_edge(start, end, point) {
                !inside
              } else {
                inside
              },
              contour_start,
              contour_start,
            )
          _ => (inside, current, contour_start)
        }
    }
    continue inside, current, contour_start, i + 1
  } nobreak {
    inside
  }
}

///|
/// Returns whether `point` is inside the portable path approximation.
///
/// This value-layer implementation treats line segments exactly and uses curve
/// endpoints for curved verbs. The native package delegates `Path::contains` to
/// Skia for full curve-aware containment when Skia is linked.
pub fn Path::contains(self : Path, point : Point) -> Bool {
  match self.bounds() {
    None => self.fill_type.is_inverse()
    Some(bounds) =>
      if self.fill_type.is_inverse() {
        !bounds.contains_point(point) ||
        !self.path_contains_closed_contours(point)
      } else {
        bounds.contains_point(point) &&
        self.path_contains_closed_contours(point)
      }
  }
}