///|
fn native_paint_state_default() -> NativePaintState {
  {
    opacity: 1.0,
    transform: @core.Transform2D::identity(),
    clip: None,
    clip_radius: 0.0,
    clip_empty: false,
  }
}

///|
fn native_scoped_child_paint_state(
  state : NativePaintState,
) -> NativePaintState {
  { ..state, opacity: 1.0 }
}

///|
fn pop_native_paint_state(
  stack : Array[NativePaintState],
  fallback : NativePaintState,
) -> NativePaintState {
  match stack.pop() {
    Some(state) => state
    None => fallback
  }
}

///|
fn clamp_opacity(value : Double) -> Double {
  value.max(0.0).min(1.0)
}

///|
fn native_paint_state_visible(state : NativePaintState) -> Bool {
  !state.clip_empty
}

///|
fn push_clip_state(
  state : NativePaintState,
  rect : @core.Rect,
) -> NativePaintState {
  if state.clip_empty || rect.size.width <= 0.0 || rect.size.height <= 0.0 {
    { ..state, clip: None, clip_empty: true }
  } else {
    match intersect_clip(state.clip, transform_rect(rect, state.transform)) {
      Some(clip) => { ..state, clip: Some(clip), clip_empty: false }
      None => { ..state, clip: None, clip_empty: true }
    }
  }
}

///|
fn push_rounded_clip_state(
  state : NativePaintState,
  rounded : @core.RoundedRect,
) -> NativePaintState {
  if state.clip_empty ||
    rounded.rect.size.width <= 0.0 ||
    rounded.rect.size.height <= 0.0 {
    { ..state, clip: None, clip_radius: 0.0, clip_empty: true }
  } else {
    match
      intersect_clip(state.clip, transform_rect(rounded.rect, state.transform)) {
      Some(clip) => {
        let new_radius = if state.clip_radius > 0.0 {
          state.clip_radius.min(rounded.radius)
        } else {
          rounded.radius
        }
        {
          ..state,
          clip: Some(clip),
          clip_radius: new_radius,
          clip_empty: false,
        }
      }
      None => { ..state, clip: None, clip_radius: 0.0, clip_empty: true }
    }
  }
}

///|
fn multiply_transform(
  a : @core.Transform2D,
  b : @core.Transform2D,
) -> @core.Transform2D {
  {
    a: a.a * b.a + a.c * b.b,
    b: a.b * b.a + a.d * b.b,
    c: a.a * b.c + a.c * b.d,
    d: a.b * b.c + a.d * b.d,
    tx: a.a * b.tx + a.c * b.ty + a.tx,
    ty: a.b * b.tx + a.d * b.ty + a.ty,
  }
}

///|
fn transform_point(
  transform : @core.Transform2D,
  x : Double,
  y : Double,
) -> @core.Point {
  @core.Point::new(
    x=transform.a * x + transform.c * y + transform.tx,
    y=transform.b * x + transform.d * y + transform.ty,
  )
}

///|
fn transform_rect(
  rect : @core.Rect,
  transform : @core.Transform2D,
) -> @core.Rect {
  let p0 = transform_point(transform, rect.origin.x, rect.origin.y)
  let p1 = transform_point(
    transform,
    rect.origin.x + rect.size.width,
    rect.origin.y,
  )
  let p2 = transform_point(
    transform,
    rect.origin.x + rect.size.width,
    rect.origin.y + rect.size.height,
  )
  let p3 = transform_point(
    transform,
    rect.origin.x,
    rect.origin.y + rect.size.height,
  )
  let min_x = p0.x.min(p1.x).min(p2.x).min(p3.x)
  let max_x = p0.x.max(p1.x).max(p2.x).max(p3.x)
  let min_y = p0.y.min(p1.y).min(p2.y).min(p3.y)
  let max_y = p0.y.max(p1.y).max(p2.y).max(p3.y)
  @core.Rect::new(x=min_x, y=min_y, width=max_x - min_x, height=max_y - min_y)
}

///|
fn intersect_clip(current : @core.Rect?, next : @core.Rect) -> @core.Rect? {
  match current {
    Some(current) => rect_intersection(current, next)
    None => Some(next)
  }
}

///|
fn rect_intersection(a : @core.Rect, b : @core.Rect) -> @core.Rect? {
  let x0 = a.origin.x.max(b.origin.x)
  let y0 = a.origin.y.max(b.origin.y)
  let x1 = (a.origin.x + a.size.width).min(b.origin.x + b.size.width)
  let y1 = (a.origin.y + a.size.height).min(b.origin.y + b.size.height)
  if x1 <= x0 || y1 <= y0 {
    None
  } else {
    Some(@core.Rect::new(x=x0, y=y0, width=x1 - x0, height=y1 - y0))
  }
}

///|
fn native_scissor_for_state(
  state : NativePaintState,
  size : @core.Size,
  scale_factor : Double,
) -> NativeScissor? {
  match state.clip {
    Some(clip) => Some(native_scissor_for_rect(clip, size, scale_factor))
    None => None
  }
}

///|
fn native_layer_scissor(
  state : NativePaintState,
  size : @core.Size,
  scale_factor : Double,
  mask : NativeLayerMask?,
) -> NativeScissor? {
  let scissor = native_scissor_for_state(state, size, scale_factor)
  match mask {
    None => scissor
    Some(mask) =>
      native_scissor_for_optional_rect(
        intersect_clip(state.clip, mask.rect),
        size,
        scale_factor,
      )
  }
}

///|
fn native_layer_mask_for_state(
  state : NativePaintState,
  mask : @core.LayerMask,
) -> NativeLayerMask? {
  match mask {
    @core.LayerMask::NoMask => None
    @core.LayerMask::RectMask(rect) =>
      Some({
        rect: transform_rect(rect, state.transform),
        radius: 0.0,
        rounded: false,
      })
    @core.LayerMask::RoundedMask(rounded) =>
      Some({
        rect: transform_rect(rounded.rect, state.transform),
        radius: rounded.radius.max(0.0),
        rounded: true,
      })
  }
}

///|
fn native_scissor_for_optional_rect(
  rect : @core.Rect?,
  size : @core.Size,
  scale_factor : Double,
) -> NativeScissor? {
  match rect {
    Some(rect) => Some(native_scissor_for_rect(rect, size, scale_factor))
    None => Some({ x: 0, y: 0, width: 0, height: 0 })
  }
}

///|
fn native_scissor_for_rect(
  rect : @core.Rect,
  size : @core.Size,
  scale_factor : Double,
) -> NativeScissor {
  let scale = native_scissor_scale(scale_factor)
  let x0 = rect.origin.x.max(0.0).min(size.width.max(0.0))
  let y0 = rect.origin.y.max(0.0).min(size.height.max(0.0))
  let x1 = (rect.origin.x + rect.size.width).max(0.0).min(size.width.max(0.0))
  let y1 = (rect.origin.y + rect.size.height).max(0.0).min(size.height.max(0.0))
  {
    x: (x0 * scale).floor().to_int(),
    y: (y0 * scale).floor().to_int(),
    width: ((x1 - x0) * scale).ceil().max(0.0).to_int(),
    height: ((y1 - y0) * scale).ceil().max(0.0).to_int(),
  }
}

///|
fn native_scissor_visible(scissor : NativeScissor?) -> Bool {
  match scissor {
    Some(scissor) => scissor.width > 0 && scissor.height > 0
    None => true
  }
}

///|
fn native_scissor_scale(scale_factor : Double) -> Double {
  if scale_factor > 0.0 {
    scale_factor
  } else {
    1.0
  }
}