///|
pub(all) suberror SunRendererError {
  InvalidSurfaceSize(Int, Int)
  PresentFailed(String)
} derive(Eq, Debug, ToJson)

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

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

///|
pub extend SunRendererError with ToJson::{to_json}

///|
pub fn SunRendererError::message(self : SunRendererError) -> String {
  match self {
    InvalidSurfaceSize(width, height) =>
      "invalid Sun raster surface size \{width}x\{height}"
    PresentFailed(description) => "Sun presenter failed: \{description}"
  }
}

///|
pub struct SunRasterRenderer {
  mut metrics : @core.SurfaceMetrics
  target : SunPresentTarget
  mut canvas : @sun_graphics.Canvas
  mut present_count : Int
  mut unsupported_command_count : Int
  unsupported_command_diagnostics : Array[SunUnsupportedCommandDiagnostic]
  mut last_frame : SunPixelFrame?
  text_system : @sun_text.TextSystem
  resources : @sun_renderer.RendererResources
  image_lifecycle : @render_common.ImageResourceLifecycle
  async_image_loading : Bool
  mut canvas_stack : Array[@sun_graphics.Canvas]
  mut filter_stack : Array[@sun_graphics.PaintFilter]
  mut cached_layer_pixmaps : Map[String, @sun_graphics.Pixmap]
  mut cached_layer_revisions : Map[String, Int]
  mut cached_layer_scales : Map[String, Double]
  mut cached_layer_order : Array[String]
  mut cached_layer_hit_count : Int
  mut cached_layer_miss_count : Int
  mut cached_layer_update_count : Int
  mut cached_layer_evict_count : Int
  cached_layer_admissions : Array[SunCachedLayerAdmissionRecord]
  mut blur_radius_stack : Array[Double]
  mut rounded_clip_stack : Array[@core.RoundedRect]
  mut platform_view_draw_fn : (() -> Unit)?
}

///|
pub fn backend_info() -> @render.RendererBackendInfo {
  let preflight = sun_cpu_raster_preflight(
    @core.SurfaceMetrics::from_logical(
      size=@core.Size::new(width=1.0, height=1.0),
    ),
  )
  @render.RendererBackendInfo::new(
    backend=SunRasterNative,
    status=Supported,
    can_render=true,
    can_resize=true,
    owns_surface=true,
    description=sun_backend_info_description(preflight.summary()),
  )
}

///|
pub fn renderer_descriptor() -> @render.RendererDescriptor {
  sun_raster_native_descriptor()
}

///|
pub fn sun_raster_native_descriptor() -> @render.RendererDescriptor {
  @render.RendererDescriptor::new(
    backend=SunRasterNative,
    family=Sun,
    presentation=CpuPixelFrame,
    supported_targets=[Native],
    capabilities=sun_feature_capabilities(),
    description="Native Sun CPU raster renderer that presents copied RGBA pixel frames to host providers.",
  )
}

///|
pub fn create_with_present_target(
  metrics : @core.SurfaceMetrics,
  target : SunPresentTarget,
  async_image_loading? : Bool = false,
) -> SunRasterRenderer raise SunRendererError {
  let width = sun_dim_to_int(metrics.physical_size.width)
  let height = sun_dim_to_int(metrics.physical_size.height)
  guard width > 0 && height > 0 else { raise InvalidSurfaceSize(width, height) }
  let renderer = {
    metrics,
    target,
    canvas: @sun_graphics.Canvas::new(width, height),
    present_count: 0,
    unsupported_command_count: 0,
    unsupported_command_diagnostics: [],
    last_frame: None,
    text_system: @sun_text.TextSystem::with_font_cache_capacity(64),
    resources: @sun_renderer.RendererResources::with_font_capacity(
      1024, 1024, 64, 256, 256, 128,
    ),
    image_lifecycle: @render_common.ImageResourceLifecycle::new(),
    async_image_loading,
    canvas_stack: [],
    filter_stack: [],
    cached_layer_pixmaps: Map([]),
    cached_layer_revisions: Map([]),
    cached_layer_scales: Map([]),
    cached_layer_order: [],
    cached_layer_hit_count: 0,
    cached_layer_miss_count: 0,
    cached_layer_update_count: 0,
    cached_layer_evict_count: 0,
    cached_layer_admissions: [],
    blur_radius_stack: [],
    rounded_clip_stack: [],
    platform_view_draw_fn: None,
  }
  renderer.register_default_platform_fonts()
  renderer
}

///|
pub fn SunRasterRenderer::resize(
  self : SunRasterRenderer,
  metrics : @core.SurfaceMetrics,
) -> Unit raise SunRendererError {
  self.metrics = metrics
  self.canvas = new_canvas_for_metrics(metrics)
  self.clear_cached_layers()
}

///|
pub fn SunRasterRenderer::render(
  self : SunRasterRenderer,
  commands : Array[@core.DrawCommand],
) -> Unit raise SunRendererError {
  self.canvas = new_canvas_for_metrics(self.metrics)
  self.canvas.clear(sun_color(@render.first_clear_color(commands)))
  self.canvas.scale(sx=self.metrics.scale_factor, sy=self.metrics.scale_factor)
  self.unsupported_command_count = 0
  self.unsupported_command_diagnostics.clear()
  let plan = @render_common.lower_draw_commands(commands)
  let canvas_scopes : Array[@render_common.PlanScopeKind] = []
  let opacity_stack : Array[Double] = []
  let blend_mode_stack : Array[@sun_graphics.BlendMode] = []
  let layer_spec_stack : Array[SunLayerScope] = []
  let rounded_clip_transform_stack : Array[@sun_graphics.Transform] = []
  ignore(
    self.render_cached_command_range(
      plan.ops,
      0,
      plan.ops.length(),
      canvas_scopes,
      opacity_stack,
      blend_mode_stack,
      layer_spec_stack,
      rounded_clip_transform_stack,
      SunCachedLayerFrameStats::new(),
    ),
  )
  if !canvas_scopes.is_empty() {
    for scope in canvas_scopes {
      self.record_unsupported_command(
        @render_common.plan_scope_push_command(scope),
        "command stream ended with an open \{@render_common.plan_scope_name(scope)} scope",
      )
    }
    while !self.canvas_stack.is_empty() {
      match self.canvas_stack.pop() {
        Some(parent_canvas) => self.canvas = parent_canvas
        None => ()
      }
    }
    self.filter_stack.clear()
    self.blur_radius_stack.clear()
    self.rounded_clip_stack.clear()
    rounded_clip_transform_stack.clear()
    while !blend_mode_stack.is_empty() {
      ignore(blend_mode_stack.pop())
    }
    while !layer_spec_stack.is_empty() {
      ignore(layer_spec_stack.pop())
    }
  }
  let frame = self.read_frame()
  self.last_frame = Some(frame)
  if (self.target.present)(frame) {
    self.present_count = self.present_count + 1
  } else {
    raise PresentFailed(self.target.description)
  }
}

///|
pub fn SunRasterRenderer::text_system(
  self : SunRasterRenderer,
) -> @core.TextSystem {
  @core.TextSystem::new(
    id="sun-raster-text-system",
    measure=input => self.measure_text_layout(input),
    paragraph_layout=Some(input => self.paragraph_layout_result(input)),
    register_font_data=(family, data) => {
      self.register_font_family_data(family, data)
    },
  )
}

///|
pub fn SunRasterRenderer::present_count(self : SunRasterRenderer) -> Int {
  self.present_count
}

///|
pub fn SunRasterRenderer::unsupported_command_count(
  self : SunRasterRenderer,
) -> Int {
  self.unsupported_command_count
}

///|
pub fn SunRasterRenderer::unsupported_command_diagnostics(
  self : SunRasterRenderer,
) -> Array[SunUnsupportedCommandDiagnostic] {
  self.unsupported_command_diagnostics.copy()
}

///|
pub fn SunRasterRenderer::last_frame(
  self : SunRasterRenderer,
) -> SunPixelFrame? {
  self.last_frame
}

///|
pub fn SunRasterRenderer::cached_layer_hit_count(
  self : SunRasterRenderer,
) -> Int {
  self.cached_layer_hit_count
}

///|
pub fn SunRasterRenderer::cached_layer_miss_count(
  self : SunRasterRenderer,
) -> Int {
  self.cached_layer_miss_count
}

///|
pub fn SunRasterRenderer::dispose(self : SunRasterRenderer) -> Unit {
  let records = self.image_lifecycle.snapshot()
  for record in records {
    ignore(self.image_lifecycle.dispose(record.source))
  }
  self.resources.get_image_cache().clear()
  self.clear_cached_layers()
  self.rounded_clip_stack = []
  self.blur_radius_stack = []
}

///|
pub fn SunRasterRenderer::register_font_bytes(
  self : SunRasterRenderer,
  key : String,
  data : Bytes,
) -> Unit {
  ignore(self.text_system.register_font_bytes(key, data))
}

///|
pub fn SunRasterRenderer::register_font_file(
  self : SunRasterRenderer,
  key : String,
  path : String,
  font_index? : Int = 0,
) -> Unit {
  ignore(self.text_system.register_font_file(key, path, font_index~))
}

///|
pub fn SunRasterRenderer::image_resources(
  self : SunRasterRenderer,
) -> Array[@render.ImageResourceRecord] {
  self.image_lifecycle.snapshot()
}

///|
pub fn SunRasterRenderer::apply_image_resource_load_completion(
  self : SunRasterRenderer,
  completion : @render.ImageResourceLoadCompletion,
) -> Bool {
  let completion_to_apply = if completion.has_decoded_rgba_payload() {
    match
      self.resources
      .get_image_cache()
      .get_or_decode_rgba_result(
        completion.source,
        completion.width,
        completion.height,
        completion.decoded_pixels,
      ) {
      Ok(_) => completion
      Err(_) =>
        @render.ImageResourceLoadCompletion::failed(
          completion.source,
          diagnostic="Sun decoded image payload invalid",
          background_io=completion.background_io,
        )
    }
  } else {
    completion
  }
  self.image_lifecycle.apply_load_completion(completion_to_apply)
}

///|
fn new_canvas_for_metrics(
  metrics : @core.SurfaceMetrics,
) -> @sun_graphics.Canvas raise SunRendererError {
  let width = sun_dim_to_int(metrics.physical_size.width)
  let height = sun_dim_to_int(metrics.physical_size.height)
  guard width > 0 && height > 0 else { raise InvalidSurfaceSize(width, height) }
  @sun_graphics.Canvas::new(width, height)
}

///|
fn SunRasterRenderer::read_frame(self : SunRasterRenderer) -> SunPixelFrame {
  let pixmap = self.canvas.get_pixmap()
  let width = pixmap.get_width()
  let height = pixmap.get_height()
  let pixels = copy_fixed_pixels(pixmap.get_pixels())
  {
    width,
    height,
    row_bytes: width * 4,
    scale_factor: self.metrics.scale_factor,
    pixels,
    bytes: pixmap.to_rgba_bytes(),
  }
}

///|
fn copy_fixed_pixels(src : FixedArray[Byte]) -> FixedArray[Byte] {
  let dst : FixedArray[Byte] = FixedArray::make(src.length(), 0)
  for index in 0..