///|
fn SkiaRasterRenderer::record_unsupported_command(
  self : SkiaRasterRenderer,
  command : String,
  reason : String,
) -> Unit {
  self.unsupported_command_count = self.unsupported_command_count + 1
  self.unsupported_command_diagnostics.push({ command, reason })
}

///|
fn SkiaRasterRenderer::render_command(
  self : SkiaRasterRenderer,
  canvas : @skia_native.Canvas,
  command : @render_common.PlanOp,
  canvas_scopes : Array[@render_common.PlanScopeKind],
) -> Unit {
  match command {
    @render_common.PlanOp::Clear(color) => canvas.clear(skia_color(color))
    @render_common.PlanOp::FillRect(rect, color) =>
      canvas.draw_rect(skia_rect(rect), skia_paint(color))
    @render_common.PlanOp::StrokeRect(rect, color, width) =>
      canvas.draw_rect(skia_rect(rect), skia_stroke_paint(color, width))
    @render_common.PlanOp::FillRoundedRect(rounded, color) =>
      canvas.draw_round_rect(
        skia_rect(rounded.rect),
        Float::from_double(rounded.radius),
        Float::from_double(rounded.radius),
        skia_paint(color),
      )
    @render_common.PlanOp::StrokeRoundedRect(rounded, color, width) =>
      canvas.draw_round_rect(
        skia_rect(rounded.rect),
        Float::from_double(rounded.radius),
        Float::from_double(rounded.radius),
        skia_stroke_paint(color, width),
      )
    @render_common.PlanOp::FillRoundedRectBrush(rounded, brush) =>
      self.draw_brush_rounded_rect(canvas, rounded, brush, stroke_width=None)
    @render_common.PlanOp::StrokeRoundedRectBrush(rounded, brush, width) =>
      self.draw_brush_rounded_rect(
        canvas,
        rounded,
        brush,
        stroke_width=Some(width),
      )
    @render_common.PlanOp::DrawShadow(shadow) =>
      self.draw_shadow(canvas, shadow)
    @render_common.PlanOp::DrawText(run) => self.draw_text(canvas, run)
    @render_common.PlanOp::DrawImage(run) => self.draw_image(canvas, run)
    @render_common.PlanOp::PushClip(rect) => {
      ignore(canvas.save())
      canvas_scopes.push(@render_common.PlanScopeKind::Clip)
      canvas.clip_rect(skia_rect(rect), anti_alias=true)
    }
    @render_common.PlanOp::PopClip =>
      self.pop_canvas_scope(
        canvas,
        canvas_scopes,
        @render_common.PlanScopeKind::Clip,
      )
    @render_common.PlanOp::PushRoundedClip(rounded) => {
      ignore(canvas.save())
      canvas_scopes.push(@render_common.PlanScopeKind::RoundedClip)
      canvas.clip_rrect(skia_rrect(rounded), anti_alias=true)
    }
    @render_common.PlanOp::PopRoundedClip =>
      self.pop_canvas_scope(
        canvas,
        canvas_scopes,
        @render_common.PlanScopeKind::RoundedClip,
      )
    @render_common.PlanOp::PushTransform(transform) => {
      ignore(canvas.save())
      canvas_scopes.push(@render_common.PlanScopeKind::Transform)
      canvas.concat(skia_matrix(transform))
    }
    @render_common.PlanOp::PopTransform =>
      self.pop_canvas_scope(
        canvas,
        canvas_scopes,
        @render_common.PlanScopeKind::Transform,
      )
    @render_common.PlanOp::PushOpacity(opacity) => {
      ignore(canvas.save_layer(paint=skia_opacity_paint(opacity)))
      canvas_scopes.push(@render_common.PlanScopeKind::Opacity)
    }
    @render_common.PlanOp::PopOpacity =>
      self.pop_canvas_scope(
        canvas,
        canvas_scopes,
        @render_common.PlanScopeKind::Opacity,
      )
    @render_common.PlanOp::PushLayer(layer) => {
      self.push_layer(canvas, layer)
      canvas_scopes.push(@render_common.PlanScopeKind::Layer)
    }
    @render_common.PlanOp::PopLayer =>
      self.pop_canvas_scope(
        canvas,
        canvas_scopes,
        @render_common.PlanScopeKind::Layer,
      )
    @render_common.PlanOp::BeginRetainedLayer(spec) => {
      self.push_layer(
        canvas,
        @core.LayerSpec::new(
          mask=@core.LayerMask::RectMask(spec.frame),
          offscreen=true,
        ),
      )
      canvas_scopes.push(@render_common.PlanScopeKind::Layer)
    }
    @render_common.PlanOp::EndRetainedLayer(_) =>
      self.pop_canvas_scope(
        canvas,
        canvas_scopes,
        @render_common.PlanScopeKind::Layer,
      )
    @render_common.PlanOp::PushFilter(filter) => {
      self.push_filter(canvas, filter)
      canvas_scopes.push(@render_common.PlanScopeKind::Filter)
    }
    @render_common.PlanOp::PopFilter =>
      self.pop_canvas_scope(
        canvas,
        canvas_scopes,
        @render_common.PlanScopeKind::Filter,
      )
    @render_common.PlanOp::DrawPath(path) => self.draw_path(canvas, path)
    @render_common.PlanOp::DrawShaderEffect(spec) =>
      self.draw_shader_effect(canvas, spec)
  }
}

///|
fn SkiaRasterRenderer::pop_canvas_scope(
  self : SkiaRasterRenderer,
  canvas : @skia_native.Canvas,
  canvas_scopes : Array[@render_common.PlanScopeKind],
  expected : @render_common.PlanScopeKind,
) -> Unit {
  match canvas_scopes.pop() {
    Some(actual) =>
      if actual == expected {
        canvas.restore()
      } else {
        canvas_scopes.push(actual)
        self.record_unsupported_command(
          @render_common.plan_scope_pop_command(expected),
          "pop did not match active \{@render_common.plan_scope_name(actual)} scope",
        )
      }
    None =>
      self.record_unsupported_command(
        @render_common.plan_scope_pop_command(expected),
        "pop has no matching push scope",
      )
  }
}

///|
fn SkiaRasterRenderer::push_filter(
  self : SkiaRasterRenderer,
  canvas : @skia_native.Canvas,
  filter : @core.FilterEffect,
) -> Unit {
  match filter {
    @core.FilterEffect::Blur(radius) =>
      match
        @skia_native.ImageFilter::blur(
          Float::from_double(radius.max(0.0)),
          Float::from_double(radius.max(0.0)),
        ) {
        Some(image_filter) =>
          ignore(canvas.save_layer(image_filter=Some(image_filter)))
        None => {
          ignore(canvas.save_layer())
          self.record_unsupported_command(
            "PushFilter", "Skia blur image filter unavailable; used unfiltered layer",
          )
        }
      }
    @core.FilterEffect::Saturate(amount) =>
      self.push_color_filter(
        canvas,
        @skia_native.ColorFilter::saturation(Float::from_double(amount)),
      )
    @core.FilterEffect::Brightness(amount) =>
      self.push_color_filter(
        canvas,
        @skia_native.ColorFilter::brightness(Float::from_double(amount)),
      )
    @core.FilterEffect::Contrast(amount) =>
      self.push_color_filter(
        canvas,
        @skia_native.ColorFilter::contrast(Float::from_double(amount)),
      )
    @core.FilterEffect::ColorMatrix(values) =>
      self.push_color_filter(canvas, skia_color_filter_matrix(values))
  }
}

///|
fn SkiaRasterRenderer::push_color_filter(
  self : SkiaRasterRenderer,
  canvas : @skia_native.Canvas,
  color_filter : @skia_native.ColorFilter?,
) -> Unit {
  match color_filter {
    Some(color_filter) =>
      ignore(canvas.save_layer(color_filter=Some(color_filter)))
    None => {
      ignore(canvas.save_layer())
      self.record_unsupported_command(
        "PushFilter", "Skia color filter unavailable; used unfiltered layer",
      )
    }
  }
}

///|
fn SkiaRasterRenderer::draw_shadow(
  self : SkiaRasterRenderer,
  canvas : @skia_native.Canvas,
  shadow : @core.ShadowSpec,
) -> Unit {
  let blur = shadow.blur_radius.max(0.0)
  let spread = shadow.spread.max(0.0)
  let expanded = skia_shadow_rect(shadow, grow=spread)
  let radius = (shadow.rect.radius + spread).max(0.0)
  if blur > 0.0 {
    match @skia_native.MaskFilter::blur(Float::from_double(blur)) {
      Some(mask_filter) => {
        let saved = canvas.save_layer(
          bounds=Some(
            skia_rect(skia_shadow_rect(shadow, grow=spread + blur * 3.0)),
          ),
          mask_filter=Some(mask_filter),
        )
        canvas.draw_round_rect(
          skia_rect(expanded),
          Float::from_double(radius),
          Float::from_double(radius),
          skia_paint(shadow.color),
        )
        canvas.restore_to_count(saved)
      }
      None => {
        canvas.draw_round_rect(
          skia_rect(expanded),
          Float::from_double(radius),
          Float::from_double(radius),
          skia_paint(shadow.color),
        )
        self.record_unsupported_command(
          "DrawShadow", "Skia mask filter unavailable; drew unblurred shadow",
        )
      }
    }
    return ()
  }
  canvas.draw_round_rect(
    skia_rect(expanded),
    Float::from_double(radius),
    Float::from_double(radius),
    skia_paint(shadow.color),
  )
}

///|
fn skia_shadow_rect(shadow : @core.ShadowSpec, grow~ : Double) -> @core.Rect {
  let rect = shadow.rect.rect
  @core.Rect::new(
    x=rect.origin.x + shadow.offset.x - grow,
    y=rect.origin.y + shadow.offset.y - grow,
    width=rect.size.width + grow * 2.0,
    height=rect.size.height + grow * 2.0,
  )
}

///|
fn SkiaRasterRenderer::draw_brush_rounded_rect(
  self : SkiaRasterRenderer,
  canvas : @skia_native.Canvas,
  rounded : @core.RoundedRect,
  brush : @core.Brush,
  stroke_width~ : Double?,
) -> Unit {
  match brush {
    @core.Brush::Solid(color) =>
      match stroke_width {
        Some(width) =>
          canvas.draw_round_rect(
            skia_rect(rounded.rect),
            Float::from_double(rounded.radius),
            Float::from_double(rounded.radius),
            skia_stroke_paint(color, width),
          )
        None =>
          canvas.draw_round_rect(
            skia_rect(rounded.rect),
            Float::from_double(rounded.radius),
            Float::from_double(rounded.radius),
            skia_paint(color),
          )
      }
    _ =>
      match skia_brush_shader(brush) {
        Some(shader) =>
          match stroke_width {
            Some(width) =>
              canvas.draw_round_rect_shader(
                skia_rect(rounded.rect),
                Float::from_double(rounded.radius),
                Float::from_double(rounded.radius),
                shader,
                skia_stroke_paint(brush.fallback_color(), width),
              )
            None => {
              ignore(canvas.save())
              canvas.clip_rrect(skia_rrect(rounded), anti_alias=true)
              canvas.draw_rect_shader(
                skia_rect(rounded.rect),
                shader,
                @moui_skia.Paint::new(anti_alias=true),
              )
              canvas.restore()
            }
          }
        None => {
          canvas.draw_round_rect(
            skia_rect(rounded.rect),
            Float::from_double(rounded.radius),
            Float::from_double(rounded.radius),
            skia_paint(brush.fallback_color()),
          )
          self.record_unsupported_command(
            "FillRoundedRectBrush", "Skia brush shader unavailable; used fallback color",
          )
        }
      }
  }
}

///|
fn SkiaRasterRenderer::push_layer(
  self : SkiaRasterRenderer,
  canvas : @skia_native.Canvas,
  layer : @core.LayerSpec,
) -> Unit {
  let paint = skia_opacity_paint(layer.opacity).set_blend_mode(
    skia_blend_mode(layer.blend_mode),
  )
  match layer.mask {
    @core.LayerMask::NoMask => ignore(canvas.save_layer(paint~))
    @core.LayerMask::RectMask(rect) => {
      ignore(canvas.save_layer(bounds=Some(skia_rect(rect)), paint~))
      canvas.clip_rect(skia_rect(rect), anti_alias=true)
    }
    @core.LayerMask::RoundedMask(rounded) => {
      ignore(canvas.save_layer(bounds=Some(skia_rect(rounded.rect)), paint~))
      canvas.clip_rrect(skia_rrect(rounded), anti_alias=true)
    }
  }
  if layer.offscreen {
    ignore(layer.offscreen)
  }
  let _ = self
}

///|
fn SkiaRasterRenderer::draw_path(
  self : SkiaRasterRenderer,
  canvas : @skia_native.Canvas,
  spec : @core.PathSpec,
) -> Unit {
  match skia_path(spec) {
    Some(path) => {
      match spec.fill {
        Some(@core.Brush::Solid(color)) =>
          canvas.draw_path(path, skia_paint(color))
        Some(brush) =>
          self.draw_path_brush(canvas, path, brush, stroke_width=None)
        None => ()
      }
      match spec.stroke {
        Some(@core.Brush::Solid(color)) =>
          canvas.draw_path(path, skia_stroke_paint(color, spec.stroke_width))
        Some(brush) =>
          self.draw_path_brush(
            canvas,
            path,
            brush,
            stroke_width=Some(spec.stroke_width),
          )
        None => ()
      }
    }
    None =>
      self.record_unsupported_command(
        "DrawPath", "Skia path conversion unavailable; skipped path",
      )
  }
}

///|
fn SkiaRasterRenderer::draw_path_brush(
  self : SkiaRasterRenderer,
  canvas : @skia_native.Canvas,
  path : @skia_native.Path,
  brush : @core.Brush,
  stroke_width~ : Double?,
) -> Unit {
  match skia_brush_shader(brush) {
    Some(shader) =>
      match stroke_width {
        Some(width) =>
          canvas.draw_path_shader(
            path,
            shader,
            skia_stroke_paint(brush.fallback_color(), width),
          )
        None =>
          match path.bounds() {
            Some(bounds) => {
              ignore(canvas.save())
              canvas.clip_path(path, anti_alias=true)
              canvas.draw_rect_shader(
                bounds,
                shader,
                @moui_skia.Paint::new(anti_alias=true),
              )
              canvas.restore()
            }
            None => ()
          }
      }
    None => {
      let paint = match stroke_width {
        Some(width) => skia_stroke_paint(brush.fallback_color(), width)
        None => skia_paint(brush.fallback_color())
      }
      canvas.draw_path(path, paint)
      self.record_unsupported_command(
        "DrawPath", "Skia brush shader unavailable; used fallback color",
      )
    }
  }
}

///|
fn SkiaRasterRenderer::draw_shader_effect(
  self : SkiaRasterRenderer,
  canvas : @skia_native.Canvas,
  spec : @core.ShaderEffectSpec,
) -> Unit {
  match spec.name {
    "solid" => self.draw_brush_rect(canvas, spec.frame, spec.fallback)
    "checker" => self.draw_checker_effect(canvas, spec)
    "linear-gradient-debug" => self.draw_linear_gradient_debug(canvas, spec)
    "vignette" => self.draw_vignette_effect(canvas, spec)
    _ => {
      self.draw_brush_rect(canvas, spec.frame, spec.fallback)
      self.record_unsupported_command(
        "DrawShaderEffect",
        "unknown shader effect '\{spec.name}' used fallback brush",
      )
    }
  }
}

///|
fn SkiaRasterRenderer::draw_brush_rect(
  self : SkiaRasterRenderer,
  canvas : @skia_native.Canvas,
  frame : @core.Rect,
  brush : @core.Brush,
) -> Unit {
  match brush {
    @core.Brush::Solid(color) =>
      canvas.draw_rect(skia_rect(frame), skia_paint(color))
    _ =>
      match skia_brush_shader(brush) {
        Some(shader) =>
          canvas.draw_rect_shader(
            skia_rect(frame),
            shader,
            @moui_skia.Paint::new(anti_alias=true),
          )
        None => {
          canvas.draw_rect(skia_rect(frame), skia_paint(brush.fallback_color()))
          self.record_unsupported_command(
            "FillRectBrush", "Skia brush shader unavailable; used fallback color",
          )
        }
      }
  }
}

///|
fn skia_brush_shader(brush : @core.Brush) -> @skia_native.Shader? {
  match brush {
    @core.Brush::Solid(_) => None
    @core.Brush::LinearGradient(gradient) =>
      @skia_native.Shader::linear_gradient(
        skia_point(gradient.start),
        skia_point(gradient.end),
        skia_color(gradient.start_color),
        skia_color(gradient.end_color),
      )
    @core.Brush::RadialGradient(gradient) =>
      @skia_native.Shader::radial_gradient(
        skia_point(gradient.center),
        Float::from_double(gradient.radius),
        skia_color(gradient.center_color),
        skia_color(gradient.edge_color),
      )
  }
}

///|
fn SkiaRasterRenderer::draw_checker_effect(
  self : SkiaRasterRenderer,
  canvas : @skia_native.Canvas,
  spec : @core.ShaderEffectSpec,
) -> Unit {
  let amount = match spec.uniforms.get(0) {
    Some(value) => value.max(1.0)
    None => 8.0
  }
  let (color_a, color_b) = skia_shader_effect_colors(spec.fallback)
  let cell = (spec.frame.size.width.min(spec.frame.size.height) / amount).max(
    1.0,
  )
  let columns = (spec.frame.size.width / cell).ceil().to_int().max(1)
  let rows = (spec.frame.size.height / cell).ceil().to_int().max(1)
  for row in 0.. Unit {
  let (start_color, end_color) = skia_shader_effect_colors(spec.fallback)
  let gradient = @core.LinearGradientSpec::new(
    start=spec.frame.origin,
    end=@core.Point::new(
      x=spec.frame.origin.x + spec.frame.size.width,
      y=spec.frame.origin.y,
    ),
    start_color~,
    end_color~,
  )
  self.draw_brush_rect(
    canvas,
    spec.frame,
    @core.Brush::LinearGradient(gradient),
  )
}

///|
fn SkiaRasterRenderer::draw_vignette_effect(
  self : SkiaRasterRenderer,
  canvas : @skia_native.Canvas,
  spec : @core.ShaderEffectSpec,
) -> Unit {
  let (color, _) = skia_shader_effect_colors(spec.fallback)
  let center = @core.Point::new(
    x=spec.frame.origin.x + spec.frame.size.width / 2.0,
    y=spec.frame.origin.y + spec.frame.size.height / 2.0,
  )
  let radius = spec.frame.size.width.max(spec.frame.size.height) /
    2.0 *
    skia_vignette_radius_scale(spec)
  let outer_color = @core.Color::rgba(r=0.0, g=0.0, b=0.0, a=color.a)
  match
    @skia_native.Shader::radial_gradient(
      skia_point(center),
      Float::from_double(radius),
      skia_color(color),
      skia_color(outer_color),
    ) {
    Some(shader) =>
      canvas.draw_rect_shader(
        skia_rect(spec.frame),
        shader,
        @moui_skia.Paint::new(anti_alias=true),
      )
    None => {
      self.draw_brush_rect(canvas, spec.frame, spec.fallback)
      self.record_unsupported_command(
        "DrawShaderEffect", "Skia radial shader unavailable for vignette; used fallback brush",
      )
    }
  }
}

///|
fn skia_vignette_radius_scale(spec : @core.ShaderEffectSpec) -> Double {
  match spec.uniforms.get(0) {
    Some(amount) => amount.max(0.8)
    None => 0.8
  }
}

///|
fn skia_dim_to_int(value : Double) -> Int {
  value.ceil().max(1.0).to_int()
}

///|
fn skia_color(color : @core.Color) -> @moui_skia.Color {
  @moui_skia.Color::from_argb(
    color_component_byte(color.a),
    color_component_byte(color.r),
    color_component_byte(color.g),
    color_component_byte(color.b),
  )
}

///|
fn color_component_byte(value : Double) -> Byte {
  (value.max(0.0).min(1.0) * 255.0).round().to_int().to_byte()
}

///|
fn skia_paint(color : @core.Color) -> @moui_skia.Paint {
  @moui_skia.Paint::new(color=skia_color(color), anti_alias=true)
}

///|
fn skia_opacity_paint(opacity : Double) -> @moui_skia.Paint {
  @moui_skia.Paint::new(
    color=@moui_skia.Color::white().with_alpha(color_component_byte(opacity)),
    anti_alias=true,
  )
}

///|
fn skia_stroke_paint(color : @core.Color, width : Double) -> @moui_skia.Paint {
  @moui_skia.Paint::new(color=skia_color(color), anti_alias=true)
  .set_stroke(true)
  .set_stroke_width(Float::from_double(width.max(0.0)))
}

///|
fn skia_blend_mode(mode : @core.BlendMode) -> @moui_skia.BlendMode {
  match mode {
    @core.BlendMode::SourceOver => @moui_skia.BlendMode::SrcOver
    @core.BlendMode::Multiply => @moui_skia.BlendMode::Multiply
    @core.BlendMode::Screen => @moui_skia.BlendMode::Screen
    @core.BlendMode::Overlay => @moui_skia.BlendMode::Overlay
    @core.BlendMode::Darken => @moui_skia.BlendMode::Darken
    @core.BlendMode::Lighten => @moui_skia.BlendMode::Lighten
  }
}

///|
fn skia_shader_effect_colors(
  fallback : @core.Brush,
) -> (@core.Color, @core.Color) {
  match fallback {
    @core.Brush::Solid(color) => (color, color)
    @core.Brush::LinearGradient(gradient) =>
      (gradient.start_color, gradient.end_color)
    @core.Brush::RadialGradient(gradient) =>
      (gradient.center_color, gradient.edge_color)
  }
}

///|
fn skia_color_filter_matrix(
  values : Array[Double],
) -> @skia_native.ColorFilter? {
  @skia_native.ColorFilter::matrix(skia_color_filter_matrix_values(values))
}

///|
fn skia_color_filter_matrix_values(values : Array[Double]) -> Array[Float] {
  let matrix = skia_identity_color_matrix_values()
  let count = values.length().min(20)
  for index in 0.. Array[Float] {
  let matrix : Array[Float] = []
  for index in 0..<20 {
    matrix.push(
      if index == 0 || index == 6 || index == 12 || index == 18 {
        1.0
      } else {
        0.0
      },
    )
  }
  matrix
}

///|
fn skia_rect(rect : @core.Rect) -> @moui_skia.Rect {
  @moui_skia.Rect::from_xywh(
    Float::from_double(rect.origin.x),
    Float::from_double(rect.origin.y),
    Float::from_double(rect.size.width),
    Float::from_double(rect.size.height),
  )
}

///|
fn skia_point(point : @core.Point) -> @moui_skia.Point {
  @moui_skia.Point::new(
    Float::from_double(point.x),
    Float::from_double(point.y),
  )
}

///|
fn skia_rrect(rounded : @core.RoundedRect) -> @moui_skia.RRect {
  @moui_skia.RRect::from_rect_xy(
    skia_rect(rounded.rect),
    Float::from_double(rounded.radius),
    Float::from_double(rounded.radius),
  )
}

///|
fn skia_matrix(transform : @core.Transform2D) -> @moui_skia.Matrix {
  @moui_skia.Matrix::from_affine([
    Float::from_double(transform.a),
    Float::from_double(transform.c),
    Float::from_double(transform.tx),
    Float::from_double(transform.b),
    Float::from_double(transform.d),
    Float::from_double(transform.ty),
  ]).unwrap_or(@moui_skia.Matrix::identity())
}

///|
fn skia_path(spec : @core.PathSpec) -> @skia_native.Path? {
  match @skia_native.Path::new() {
    Some(path) => {
      for verb in spec.verbs {
        match verb {
          @core.PathVerb::MoveTo(point) => path.move_to(skia_point(point))
          @core.PathVerb::LineTo(point) => path.line_to(skia_point(point))
          @core.PathVerb::QuadTo(control, end) =>
            path.quad_to(skia_point(control), skia_point(end))
          @core.PathVerb::CubicTo(c1, c2, end) =>
            path.cubic_to(skia_point(c1), skia_point(c2), skia_point(end))
          @core.PathVerb::Close => path.close()
        }
      }
      Some(path)
    }
    None => None
  }
}