///|
/// Fill rule for path filling
pub(all) enum FillRule {
  NonZero
  EvenOdd
} derive(Eq, Debug)

///|
pub extend FillRule with Debug::{to_repr}

///|
pub extend FillRule with Eq::{not_equal, equal}

///|
/// Paint style
pub(all) enum PaintStyle {
  Fill
  Stroke
} derive(Eq, Debug)

///|
pub extend PaintStyle with Debug::{to_repr}

///|
pub extend PaintStyle with Eq::{not_equal, equal}

///|
/// Stroke cap style
pub(all) enum StrokeCap {
  Butt
  Round
  Square
} derive(Eq, Debug)

///|
pub extend StrokeCap with Debug::{to_repr}

///|
pub extend StrokeCap with Eq::{not_equal, equal}

///|
/// Stroke join style
pub(all) enum StrokeJoin {
  Miter
  Round
  Bevel
} derive(Eq, Debug)

///|
pub extend StrokeJoin with Debug::{to_repr}

///|
pub extend StrokeJoin with Eq::{not_equal, equal}

///|
/// Pixel blend mode used when compositing paint over destination pixels.
pub(all) enum BlendMode {
  Clear
  Source
  Destination
  SourceOver
  DestinationOver
  SourceIn
  DestinationIn
  SourceOut
  DestinationOut
  SourceAtop
  DestinationAtop
  Xor
  Multiply
  Screen
  Add
  Darken
  Lighten
  Difference
  Exclusion
  Overlay
  HardLight
  SoftLight
  ColorDodge
  ColorBurn
  Hue
  Saturation
  Color
  Luminosity
  LinearBurn
  Subtract
} derive(Eq, Debug)

///|
pub extend BlendMode with Debug::{to_repr}

///|
pub extend BlendMode with Eq::{not_equal, equal}

///|
/// Paint configuration for drawing operations
pub struct Paint {
  mut color : Color
  mut shader : PaintShader?
  mut filter : PaintFilter?
  mut style : PaintStyle
  mut blend_mode : BlendMode
  mut stroke_width : Double
  mut stroke_cap : StrokeCap
  mut stroke_join : StrokeJoin
  mut miter_limit : Double
  mut stroke_dash_pattern : Array[Double]
  mut stroke_dash_offset : Double
  mut fill_rule : FillRule
  mut opacity : Double
} derive(Debug)

///|
pub extend Paint with Debug::{to_repr}

///|
/// Create a default paint
pub fn Paint::new() -> Paint {
  {
    color: Color::black(),
    shader: None,
    filter: None,
    style: Fill,
    blend_mode: SourceOver,
    stroke_width: 1.0,
    stroke_cap: Butt,
    stroke_join: Miter,
    miter_limit: 4.0,
    stroke_dash_pattern: [],
    stroke_dash_offset: 0.0,
    fill_rule: NonZero,
    opacity: 1.0,
  }
}

///|
/// Set the paint color and clear any shader color source.
pub fn Paint::set_color(self : Paint, color : Color) -> Paint {
  self.color = color
  self.shader = None
  self
}

///|
/// Return the solid paint color used when no shader color source is active.
pub fn Paint::get_color(self : Paint) -> Color {
  self.color
}

///|
/// Set a shader color source for vector and mask drawing.
pub fn Paint::set_shader(self : Paint, shader : PaintShader) -> Paint {
  self.shader = Some(shader)
  self
}

///|
/// Clear shader color source and return to solid-color drawing.
pub fn Paint::clear_shader(self : Paint) -> Paint {
  self.shader = None
  self
}

///|
/// Return the configured shader color source, if any.
pub fn Paint::get_shader(self : Paint) -> PaintShader? {
  self.shader
}

///|
/// Return whether this paint has an active shader color source.
pub fn Paint::has_shader(self : Paint) -> Bool {
  self.shader is Some(_)
}

///|
/// Set a color filter applied after color sampling and before opacity.
pub fn Paint::set_filter(self : Paint, filter : PaintFilter) -> Paint {
  self.filter = Some(filter)
  self
}

///|
/// Clear the active color filter.
pub fn Paint::clear_filter(self : Paint) -> Paint {
  self.filter = None
  self
}

///|
/// Return the configured color filter, if any.
pub fn Paint::get_filter(self : Paint) -> PaintFilter? {
  self.filter
}

///|
/// Return whether this paint has an active color filter.
pub fn Paint::has_filter(self : Paint) -> Bool {
  self.filter is Some(_)
}

///|
/// Set the paint style
pub fn Paint::set_style(self : Paint, style : PaintStyle) -> Paint {
  self.style = style
  self
}

///|
/// Return the paint style.
pub fn Paint::get_style(self : Paint) -> PaintStyle {
  self.style
}

///|
/// Set pixel blend mode used when compositing this paint.
pub fn Paint::set_blend_mode(self : Paint, blend_mode : BlendMode) -> Paint {
  self.blend_mode = blend_mode
  self
}

///|
/// Return the pixel blend mode used when compositing this paint.
pub fn Paint::get_blend_mode(self : Paint) -> BlendMode {
  self.blend_mode
}

///|
/// Set stroke width
pub fn Paint::set_stroke_width(self : Paint, width : Double) -> Paint {
  self.stroke_width = normalize_stroke_width(width)
  self
}

///|
/// Return the stroke width.
pub fn Paint::get_stroke_width(self : Paint) -> Double {
  self.stroke_width
}

///|
/// Set stroke cap style
pub fn Paint::set_stroke_cap(self : Paint, cap : StrokeCap) -> Paint {
  self.stroke_cap = cap
  self
}

///|
/// Return the stroke cap style.
pub fn Paint::get_stroke_cap(self : Paint) -> StrokeCap {
  self.stroke_cap
}

///|
/// Set stroke join style
pub fn Paint::set_stroke_join(self : Paint, join : StrokeJoin) -> Paint {
  self.stroke_join = join
  self
}

///|
/// Return the stroke join style.
pub fn Paint::get_stroke_join(self : Paint) -> StrokeJoin {
  self.stroke_join
}

///|
/// Set stroke miter limit. Values below 1.0 are clamped to the minimum
/// meaningful ratio.
pub fn Paint::set_miter_limit(self : Paint, limit : Double) -> Paint {
  self.miter_limit = if limit > 1.0 { limit } else { 1.0 }
  self
}

///|
/// Return the stroke miter limit.
pub fn Paint::get_miter_limit(self : Paint) -> Double {
  self.miter_limit
}

///|
/// Set stroke dash pattern and phase offset. Empty or non-positive-only
/// patterns draw a solid stroke.
pub fn Paint::set_stroke_dash(
  self : Paint,
  pattern : Array[Double],
  offset : Double,
) -> Paint {
  self.stroke_dash_pattern = normalize_dash_pattern(pattern)
  self.stroke_dash_offset = offset
  self
}

///|
/// Return whether this paint has a normalized stroke dash pattern.
pub fn Paint::has_stroke_dash(self : Paint) -> Bool {
  self.stroke_dash_pattern.length() > 0
}

///|
/// Return a copied normalized stroke dash pattern.
pub fn Paint::get_stroke_dash_pattern(self : Paint) -> Array[Double] {
  copy_paint_dash_pattern(self.stroke_dash_pattern)
}

///|
/// Return the stroke dash phase offset.
pub fn Paint::get_stroke_dash_offset(self : Paint) -> Double {
  self.stroke_dash_offset
}

///|
/// Clear stroke dash pattern and return to solid strokes.
pub fn Paint::clear_stroke_dash(self : Paint) -> Paint {
  self.stroke_dash_pattern = []
  self.stroke_dash_offset = 0.0
  self
}

///|
/// Configure a round dotted stroke using the current stroke width.
pub fn Paint::set_dotted_stroke(self : Paint) -> Paint {
  let dot_length = 0.0001
  let gap = (self.stroke_width * 2.0).max(dot_length)
  self.stroke_cap = Round
  self.stroke_dash_pattern = [dot_length, gap]
  self.stroke_dash_offset = 0.0
  self
}

///|
/// Set fill rule
pub fn Paint::set_fill_rule(self : Paint, fill_rule : FillRule) -> Paint {
  self.fill_rule = fill_rule
  self
}

///|
/// Return the fill rule used by path filling.
pub fn Paint::get_fill_rule(self : Paint) -> FillRule {
  self.fill_rule
}

///|
/// Set opacity (0.0 to 1.0)
pub fn Paint::set_opacity(self : Paint, opacity : Double) -> Paint {
  self.opacity = normalize_opacity(opacity)
  self
}

///|
/// Return the normalized configured opacity.
pub fn Paint::get_opacity(self : Paint) -> Double {
  self.opacity
}

///|
/// Sample this paint's active color source at a device-space point and apply
/// paint opacity.
pub fn Paint::sample_color(self : Paint, x : Double, y : Double) -> Color {
  let color = match self.shader {
    Some(shader) => shader.sample(x, y)
    None => self.color
  }
  sample_source_color_with_filter(color, self.filter, self.opacity)
}

///|
/// Apply this paint's filter and opacity to a sampled source pixel.
pub fn Paint::sample_source_color(self : Paint, color : Color) -> Color {
  sample_source_color_with_filter(color, self.filter, self.opacity)
}

///|
fn Paint::sample_masked_source_color(
  self : Paint,
  color : Color,
  x : Double,
  y : Double,
) -> Color {
  match self.shader {
    Some(_) => self.sample_color(x, y).blend_alpha_mask(color.a.to_int())
    None => self.sample_source_color(color)
  }
}

///|
fn normalize_dash_pattern(pattern : Array[Double]) -> Array[Double] {
  let normalized = []
  let mut total = 0.0
  for value in pattern {
    if value > 0.0 {
      normalized.push(value)
      total = total + value
    }
  }
  guard normalized.length() > 0 && total > 0.0 else { return [] }
  if normalized.length() % 2 == 1 {
    let original_len = normalized.length()
    for i in 0.. Double {
  guard width == width && width > 0.0 else { return 0.0 }
  width
}

///|
fn copy_paint_dash_pattern(pattern : Array[Double]) -> Array[Double] {
  let copied = []
  for value in pattern {
    copied.push(value)
  }
  copied
}