use super::PointId;
use super::algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE;
use crate::subpath::{BezierHandles, ManipulatorGroup};
use crate::vector::{SegmentId, Vector};
use core_types::list::{Item, List};
use dyn_any::DynAny;
use glam::DVec2;
use kurbo::{BezPath, CubicBez, Line, ParamCurve, ParamCurveDeriv, PathSeg, Point, QuadBez};
use std::ops::Sub;

#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[widget(Radio)]
pub enum BooleanOperation {
	#[default]
	#[icon("BooleanUnion")]
	Union,
	#[icon("BooleanSubtractFront")]
	SubtractFront,
	#[icon("BooleanSubtractBack")]
	SubtractBack,
	#[icon("BooleanIntersect")]
	Intersect,
	#[icon("BooleanDifference")]
	Difference,
}

/// Represents different geometric interpretations of calculating the centroid (center of mass).
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[widget(Radio)]
pub enum CentroidType {
	/// The center of mass for the area of a solid shape's interior, as if made out of an infinitely flat material.
	#[default]
	Area,
	/// The center of mass for the arc length of a curved shape's perimeter, as if made out of an infinitely thin wire.
	Length,
}

#[repr(C)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[widget(Radio)]
pub enum RowsOrColumns {
	#[default]
	Rows = 0,
	Columns,
}

/// A box's four corner values, such as a rectangle's corner radii, expanded on read from any number of stored
/// values by the CSS `border-radius` shorthand rules.
///
/// Wraps a `List<f64>` so the Data panel can introspect its values, mirroring how `DashPattern` wraps its lengths,
/// while remaining a single rank-0 value on the wire.
#[derive(Default, Debug, Clone, PartialEq, graphene_hash::CacheHash, DynAny)]
pub struct BoxCorners(pub List<f64>);

impl BoxCorners {
	/// Expands the stored values to the four corners, clockwise from the top-left, by the CSS `border-radius` shorthand rules.
	/// - `[]` → `[0, 0, 0, 0]`
	/// - `[a]` → `[a, a, a, a]`
	/// - `[a, b]` → `[a, b, a, b]`
	/// - `[a, b, c]` → `[a, b, c, b]`
	/// - `[a, b, c, d, …]` → `[a, b, c, d]`
	pub fn to_corner_values(&self) -> [f64; 4] {
		let values: Vec<f64> = self.0.iter_element_values().copied().collect();
		match values.as_slice() {
			[] => [0., 0., 0., 0.],
			&[a] => [a, a, a, a],
			&[a, b] => [a, b, a, b],
			&[a, b, c] => [a, b, c, b],
			&[a, b, c, d, ..] => [a, b, c, d],
		}
	}
}

impl From<f64> for BoxCorners {
	fn from(value: f64) -> Self {
		Self(List::new_from_element(value))
	}
}

impl From<Vec<f64>> for BoxCorners {
	fn from(values: Vec<f64>) -> Self {
		Self(values.into_iter().map(Item::new_from_element).collect())
	}
}

impl From<&str> for BoxCorners {
	fn from(text: &str) -> Self {
		Self::from(core_types::misc::parse_f64_list(text))
	}
}

impl From<String> for BoxCorners {
	fn from(text: String) -> Self {
		Self::from(text.as_str())
	}
}

pub trait AsU64 {
	fn as_u64(&self) -> u64;
}
impl AsU64 for u32 {
	fn as_u64(&self) -> u64 {
		*self as u64
	}
}
impl AsU64 for u64 {
	fn as_u64(&self) -> u64 {
		*self
	}
}
impl AsU64 for f64 {
	fn as_u64(&self) -> u64 {
		*self as u64
	}
}

pub trait AsI64 {
	fn as_i64(&self) -> i64;
}
impl AsI64 for u32 {
	fn as_i64(&self) -> i64 {
		*self as i64
	}
}
impl AsI64 for u64 {
	fn as_i64(&self) -> i64 {
		*self as i64
	}
}
impl AsI64 for f64 {
	fn as_i64(&self) -> i64 {
		*self as i64
	}
}

#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[widget(Radio)]
pub enum GridType {
	#[default]
	Rectangular = 0,
	Isometric,
}

#[repr(C)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[widget(Radio)]
pub enum ArcType {
	/// Leaves the two ends of the arc unconnected.
	#[default]
	Open = 0,
	/// Connects the two ends of the arc with a straight line.
	Closed,
	/// Connects the two ends of the arc to its center, forming a wedge.
	PieSlice,
}

#[repr(C)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[widget(Radio)]
pub enum MergeByDistanceAlgorithm {
	#[default]
	Spatial,
	Topological,
}

#[repr(C)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[widget(Radio)]
pub enum ExtrudeJoiningAlgorithm {
	All,
	#[default]
	Extrema,
	None,
}

#[repr(C)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[widget(Radio)]
pub enum PointSpacingType {
	#[default]
	/// The desired spacing distance between points.
	Separation,
	/// The exact number of points to span the path.
	Quantity,
}

pub fn point_to_dvec2(point: Point) -> DVec2 {
	DVec2 { x: point.x, y: point.y }
}

pub fn dvec2_to_point(value: DVec2) -> Point {
	Point { x: value.x, y: value.y }
}

pub fn get_line_endpoints(line: Line) -> (DVec2, DVec2) {
	(point_to_dvec2(line.p0), point_to_dvec2(line.p1))
}

pub fn segment_to_handles(segment: &PathSeg) -> BezierHandles {
	match *segment {
		PathSeg::Line(_) => BezierHandles::Linear,
		PathSeg::Quad(QuadBez { p0: _, p1, p2: _ }) => BezierHandles::Quadratic { handle: point_to_dvec2(p1) },
		PathSeg::Cubic(CubicBez { p0: _, p1, p2, p3: _ }) => BezierHandles::Cubic {
			handle_start: point_to_dvec2(p1),
			handle_end: point_to_dvec2(p2),
		},
	}
}

pub fn handles_to_segment(start: DVec2, handles: BezierHandles, end: DVec2) -> PathSeg {
	match handles {
		BezierHandles::Linear => {
			let p0 = dvec2_to_point(start);
			let p1 = dvec2_to_point(end);
			PathSeg::Line(Line::new(p0, p1))
		}
		BezierHandles::Quadratic { handle } => {
			let p0 = dvec2_to_point(start);
			let p1 = dvec2_to_point(handle);
			let p2 = dvec2_to_point(end);
			PathSeg::Quad(QuadBez::new(p0, p1, p2))
		}
		BezierHandles::Cubic { handle_start, handle_end } => {
			let p0 = dvec2_to_point(start);
			let p1 = dvec2_to_point(handle_start);
			let p2 = dvec2_to_point(handle_end);
			let p3 = dvec2_to_point(end);
			PathSeg::Cubic(CubicBez::new(p0, p1, p2, p3))
		}
	}
}

pub fn bezpath_from_manipulator_groups(manipulator_groups: &[ManipulatorGroup<PointId>], closed: bool) -> BezPath {
	let mut bezpath = kurbo::BezPath::new();
	let mut out_handle;

	let Some(first) = manipulator_groups.first() else { return bezpath };
	bezpath.move_to(dvec2_to_point(first.anchor));
	out_handle = first.out_handle;

	for manipulator in manipulator_groups.iter().skip(1) {
		match (out_handle, manipulator.in_handle) {
			(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(manipulator.anchor)),
			(None, None) => bezpath.line_to(dvec2_to_point(manipulator.anchor)),
			(None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)),
			(Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)),
		}
		out_handle = manipulator.out_handle;
	}

	if closed {
		match (out_handle, first.in_handle) {
			(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(first.anchor)),
			(None, None) => bezpath.line_to(dvec2_to_point(first.anchor)),
			(None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)),
			(Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)),
		}
		bezpath.close_path();
	}
	bezpath
}

pub fn bezpath_to_manipulator_groups(bezpath: &BezPath) -> (Vec<ManipulatorGroup<PointId>>, bool) {
	let mut manipulator_groups = Vec::<ManipulatorGroup<PointId>>::new();
	let mut is_closed = false;

	for element in bezpath.elements() {
		let manipulator_group = match *element {
			kurbo::PathEl::MoveTo(point) => ManipulatorGroup::new(point_to_dvec2(point), None, None),
			kurbo::PathEl::LineTo(point) => ManipulatorGroup::new(point_to_dvec2(point), None, None),
			kurbo::PathEl::QuadTo(point, point1) => ManipulatorGroup::new(point_to_dvec2(point1), Some(point_to_dvec2(point)), None),
			kurbo::PathEl::CurveTo(point, point1, point2) => {
				if let Some(last_manipulator_group) = manipulator_groups.last_mut() {
					last_manipulator_group.out_handle = Some(point_to_dvec2(point));
				}
				ManipulatorGroup::new(point_to_dvec2(point2), Some(point_to_dvec2(point1)), None)
			}
			kurbo::PathEl::ClosePath => {
				if let Some(last_manipulators) = manipulator_groups.pop()
					&& let Some(first_manipulators) = manipulator_groups.first_mut()
				{
					first_manipulators.out_handle = last_manipulators.in_handle;
				}
				is_closed = true;
				break;
			}
		};

		manipulator_groups.push(manipulator_group);
	}

	(manipulator_groups, is_closed)
}

/// Returns true if the [`PathSeg`] is equivalent to a line.
///
/// This is different from simply checking if the segment is [`PathSeg::Line`] or [`PathSeg::Quad`] or [`PathSeg::Cubic`]. Bezier curve can also be a line if the control points are colinear to the start and end points. Therefore if the handles exceed the start and end point, it will still be considered as a line.
pub fn is_linear(segment: PathSeg) -> bool {
	let is_colinear = |a: Point, b: Point, c: Point| -> bool { ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)).abs() < MAX_ABSOLUTE_DIFFERENCE };

	match segment {
		PathSeg::Line(_) => true,
		PathSeg::Quad(QuadBez { p0, p1, p2 }) => is_colinear(p0, p1, p2),
		PathSeg::Cubic(CubicBez { p0, p1, p2, p3 }) => is_colinear(p0, p1, p3) && is_colinear(p0, p2, p3),
	}
}

/// Get an vec of all the points in a path segment.
pub fn pathseg_points_vec(segment: PathSeg) -> Vec<Point> {
	match segment {
		PathSeg::Line(line) => [line.p0, line.p1].to_vec(),
		PathSeg::Quad(quad_bez) => [quad_bez.p0, quad_bez.p1, quad_bez.p2].to_vec(),
		PathSeg::Cubic(cubic_bez) => [cubic_bez.p0, cubic_bez.p1, cubic_bez.p2, cubic_bez.p3].to_vec(),
	}
}

/// Returns true if the corresponding points of the two [`PathSeg`]s are within the provided absolute value difference from each other.
pub fn pathseg_abs_diff_eq(seg1: PathSeg, seg2: PathSeg, max_abs_diff: f64) -> bool {
	let seg1 = if is_linear(seg1) { PathSeg::Line(Line::new(seg1.start(), seg1.end())) } else { seg1 };
	let seg2 = if is_linear(seg2) { PathSeg::Line(Line::new(seg2.start(), seg2.end())) } else { seg2 };

	let seg1_points = pathseg_points_vec(seg1);
	let seg2_points = pathseg_points_vec(seg2);

	let cmp = |a: f64, b: f64| a.sub(b).abs() < max_abs_diff;

	seg1_points.len() == seg2_points.len() && seg1_points.into_iter().zip(seg2_points).all(|(a, b)| cmp(a.x, b.x) && cmp(a.y, b.y))
}
pub trait Tangent {
	fn tangent_at(&self, t: f64) -> DVec2;

	fn tangent_at_start(&self) -> DVec2 {
		self.tangent_at(0.)
	}

	fn tangent_at_end(&self) -> DVec2 {
		self.tangent_at(1.)
	}
}

trait ControlPoints {
	type Points: AsRef<[Point]>;
	fn control_points(&self) -> Self::Points;
}

impl ControlPoints for kurbo::Line {
	type Points = [Point; 2];
	fn control_points(&self) -> Self::Points {
		[self.p0, self.p1]
	}
}

impl ControlPoints for kurbo::QuadBez {
	type Points = [Point; 3];
	fn control_points(&self) -> Self::Points {
		[self.p0, self.p1, self.p2]
	}
}

impl ControlPoints for kurbo::CubicBez {
	type Points = [Point; 4];
	fn control_points(&self) -> Self::Points {
		[self.p0, self.p1, self.p2, self.p3]
	}
}

impl<T: ControlPoints + ParamCurveDeriv> Tangent for T {
	fn tangent_at(&self, t: f64) -> DVec2 {
		point_to_dvec2(self.deriv().eval(t))
	}

	fn tangent_at_start(&self) -> DVec2 {
		let pts = self.control_points();
		let pts = pts.as_ref();
		let mut iter = pts.iter();
		iter.next()
			.and_then(|&start| iter.find(|&&p| p != start).map(|&p| DVec2 { x: p.x - start.x, y: p.y - start.y }))
			.unwrap_or_default()
	}

	fn tangent_at_end(&self) -> DVec2 {
		let pts = self.control_points();
		let pts = pts.as_ref();
		let mut iter = pts.iter().rev();
		iter.next()
			.and_then(|&end| iter.find(|&&p| p != end).map(|&p| DVec2 { x: end.x - p.x, y: end.y - p.y }))
			.unwrap_or_default()
	}
}

impl Tangent for kurbo::PathSeg {
	fn tangent_at(&self, t: f64) -> DVec2 {
		match self {
			PathSeg::Line(line) => line.tangent_at(t),
			PathSeg::Quad(quad) => quad.tangent_at(t),
			PathSeg::Cubic(cubic) => cubic.tangent_at(t),
		}
	}

	fn tangent_at_start(&self) -> DVec2 {
		match self {
			PathSeg::Line(line) => line.tangent_at_start(),
			PathSeg::Quad(quad) => quad.tangent_at_start(),
			PathSeg::Cubic(cubic) => cubic.tangent_at_start(),
		}
	}

	fn tangent_at_end(&self) -> DVec2 {
		match self {
			PathSeg::Line(line) => line.tangent_at_end(),
			PathSeg::Quad(quad) => quad.tangent_at_end(),
			PathSeg::Cubic(cubic) => cubic.tangent_at_end(),
		}
	}
}

/// A selectable part of a curve, either an anchor (start or end of a bézier) or a handle (doesn't necessarily go through the bézier but influences curvature).
#[derive(Clone, Copy, PartialEq, Eq, Hash, graphene_hash::CacheHash, Debug, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ManipulatorPointId {
	/// A control anchor - the start or end point of a bézier.
	Anchor(PointId),
	/// The handle for a bézier - the first handle on a cubic and the only handle on a quadratic.
	PrimaryHandle(SegmentId),
	/// The end handle on a cubic bézier.
	EndHandle(SegmentId),
}

impl ManipulatorPointId {
	/// Attempt to retrieve the manipulator position in layer space (no transformation applied).
	#[must_use]
	#[track_caller]
	pub fn get_position(&self, vector: &Vector) -> Option<DVec2> {
		match self {
			ManipulatorPointId::Anchor(id) => vector.point_domain.position_from_id(*id),
			ManipulatorPointId::PrimaryHandle(id) => vector.segment_from_id(*id).and_then(|bezier| bezier.handle_start()),
			ManipulatorPointId::EndHandle(id) => vector.segment_from_id(*id).and_then(|bezier| bezier.handle_end()),
		}
	}

	pub fn get_anchor_position(&self, vector: &Vector) -> Option<DVec2> {
		match self {
			ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_) => self.get_anchor(vector).and_then(|id| vector.point_domain.position_from_id(id)),
			_ => self.get_position(vector),
		}
	}

	/// Attempt to get a pair of handles. For an anchor this is the first two handles connected. For a handle it is self and the first opposing handle.
	#[must_use]
	pub fn get_handle_pair(self, vector: &Vector) -> Option<[HandleId; 2]> {
		match self {
			ManipulatorPointId::Anchor(point) => vector.all_connected(point).take(2).collect::<Vec<_>>().try_into().ok(),
			ManipulatorPointId::PrimaryHandle(segment) => {
				let point = vector.segment_domain.segment_start_from_id(segment)?;
				let current = HandleId::primary(segment);
				let other = vector.segment_domain.all_connected(point).find(|&value| value != current);
				other.map(|other| [current, other])
			}
			ManipulatorPointId::EndHandle(segment) => {
				let point = vector.segment_domain.segment_end_from_id(segment)?;
				let current = HandleId::end(segment);
				let other = vector.segment_domain.all_connected(point).find(|&value| value != current);
				other.map(|other| [current, other])
			}
		}
	}

	/// Finds all the connected handles of a point.
	/// For an anchor it is all the connected handles.
	/// For a handle it is all the handles connected to its corresponding anchor other than the current handle.
	pub fn get_all_connected_handles(self, vector: &Vector) -> Option<Vec<HandleId>> {
		match self {
			ManipulatorPointId::Anchor(point) => {
				let connected = vector.all_connected(point).collect::<Vec<_>>();
				Some(connected)
			}
			ManipulatorPointId::PrimaryHandle(segment) => {
				let point = vector.segment_domain.segment_start_from_id(segment)?;
				let current = HandleId::primary(segment);
				let connected = vector.segment_domain.all_connected(point).filter(|&value| value != current).collect::<Vec<_>>();
				Some(connected)
			}
			ManipulatorPointId::EndHandle(segment) => {
				let point = vector.segment_domain.segment_end_from_id(segment)?;
				let current = HandleId::end(segment);
				let connected = vector.segment_domain.all_connected(point).filter(|&value| value != current).collect::<Vec<_>>();
				Some(connected)
			}
		}
	}

	/// Attempt to find the closest anchor. If self is already an anchor then it is just self. If it is a start or end handle, then the start or end point is chosen.
	#[must_use]
	pub fn get_anchor(self, vector: &Vector) -> Option<PointId> {
		match self {
			ManipulatorPointId::Anchor(point) => Some(point),
			ManipulatorPointId::PrimaryHandle(segment) => vector.segment_start_from_id(segment),
			ManipulatorPointId::EndHandle(segment) => vector.segment_end_from_id(segment),
		}
	}

	/// Attempt to convert self to a [`HandleId`], returning none for an anchor.
	#[must_use]
	pub fn as_handle(self) -> Option<HandleId> {
		match self {
			ManipulatorPointId::PrimaryHandle(segment) => Some(HandleId::primary(segment)),
			ManipulatorPointId::EndHandle(segment) => Some(HandleId::end(segment)),
			ManipulatorPointId::Anchor(_) => None,
		}
	}

	/// Attempt to convert self to an anchor, returning None for a handle.
	#[must_use]
	pub fn as_anchor(self) -> Option<PointId> {
		match self {
			ManipulatorPointId::Anchor(point) => Some(point),
			_ => None,
		}
	}

	pub fn get_segment(self) -> Option<SegmentId> {
		match self {
			ManipulatorPointId::PrimaryHandle(segment) | ManipulatorPointId::EndHandle(segment) => Some(segment),
			_ => None,
		}
	}
}

/// The type of handle found on a bézier curve.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, graphene_hash::CacheHash, Debug, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum HandleType {
	/// The first handle on a cubic bézier or the only handle on a quadratic bézier.
	Primary,
	/// The second handle on a cubic bézier.
	End,
}

/// Represents a primary or end handle found in a particular segment.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, graphene_hash::CacheHash, Debug, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HandleId {
	pub ty: HandleType,
	pub segment: SegmentId,
}

impl std::fmt::Display for HandleId {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		match self.ty {
			// I haven't checked if "out" and "in" are reversed, or are accurate translations of the "primary" and "end" terms used in the `HandleType` enum, so this naming is an assumption.
			HandleType::Primary => write!(f, "{} out", self.segment.inner()),
			HandleType::End => write!(f, "{} in", self.segment.inner()),
		}
	}
}

impl HandleId {
	/// Construct a handle for the first handle on a cubic bézier or the only handle on a quadratic bézier.
	#[must_use]
	pub const fn primary(segment: SegmentId) -> Self {
		Self { ty: HandleType::Primary, segment }
	}

	/// Construct a handle for the end handle on a cubic bézier.
	#[must_use]
	pub const fn end(segment: SegmentId) -> Self {
		Self { ty: HandleType::End, segment }
	}

	/// Convert to [`ManipulatorPointId`].
	#[must_use]
	pub fn to_manipulator_point(self) -> ManipulatorPointId {
		match self.ty {
			HandleType::Primary => ManipulatorPointId::PrimaryHandle(self.segment),
			HandleType::End => ManipulatorPointId::EndHandle(self.segment),
		}
	}

	/// Calculate the magnitude of the handle from the anchor.
	pub fn length(self, vector: &Vector) -> f64 {
		let Some(anchor_position) = self.to_manipulator_point().get_anchor_position(vector) else {
			// TODO: This was previously an unwrap which was encountered, so this is a temporary way to avoid a crash
			return 0.;
		};
		let handle_position = self.to_manipulator_point().get_position(vector);
		handle_position.map(|pos| (pos - anchor_position).length()).unwrap_or(f64::MAX)
	}

	/// Convert an end handle to the primary handle and a primary handle to an end handle. Note that the new handle may not exist (e.g. for a quadratic bézier).
	#[must_use]
	pub fn opposite(self) -> Self {
		match self.ty {
			HandleType::Primary => Self::end(self.segment),
			HandleType::End => Self::primary(self.segment),
		}
	}
}

#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[widget(Dropdown)]
pub enum SpiralType {
	#[default]
	Archimedean,
	Logarithmic,
}

/// Controls how the morph/blend progression spends its time along the interpolation path, allowing for constant speed/spacing with respect to different parameters of change.
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[widget(Dropdown)]
pub enum InterpolationDistribution {
	/// All objects occupy an equal portion of the progression range, regardless of their changing distances, angles, sizes, or slants.
	#[default]
	Objects,
	/// All distances along the interpolation path are covered at a constant rate, meaning more time is spent traversing further distances.
	Distances,
	/// All angles of rotation between objects are covered at a constant rate, meaning more time is spent turning through larger angles.
	Angles,
	/// All sizes of expansion/contraction between objects are covered at a constant rate, meaning more time is spent scaling through larger (logarithmic) changes in size.
	Sizes,
	/// All slants (changes in skew angle) between objects are covered at a constant rate, meaning more time is spent skewing through larger changes in slant.
	Slants,
}

graphene_hash::impl_via_hash!(
	BooleanOperation,
	CentroidType,
	RowsOrColumns,
	GridType,
	ArcType,
	MergeByDistanceAlgorithm,
	ExtrudeJoiningAlgorithm,
	PointSpacingType,
	SpiralType,
	InterpolationDistribution
);
