///|
/// Native Skia image.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct Image {
  priv handle : ImageHandle
}

///|
/// Immutable encoded or raw byte data owned by Skia.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct Data {
  priv handle : DataHandle
}

///|
/// Native Skia codec for encoded image data.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct Codec {
  priv handle : CodecHandle
}

///|
/// Native Skia path.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct Path {
  priv handle : PathHandle
}

///|
/// Native Skia font.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct Font {
  priv handle : FontHandle
}

///|
/// Native Skia shaped text run.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct ShapedTextRun {
  priv handle : ShapedTextRunHandle
}

///|
/// Native SkParagraph layout.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct Paragraph {
  priv handle : ParagraphHandle
}

///|
/// SkParagraph line metrics exposed with UTF-8 byte ranges.
pub(all) struct ParagraphLineMetric {
  start : Int
  end : Int
  end_excluding_whitespace : Int
  end_including_newline : Int
  line_number : Int
  hard_break : Bool
  left : Float
  width : Float
  baseline : Float
  ascent : Float
  descent : Float
  height : Float
} derive(Debug, Eq)

///|
pub fn ParagraphLineMetric::right(self : ParagraphLineMetric) -> Float {
  self.left + self.width
}

///|
pub fn ParagraphLineMetric::is_valid(self : ParagraphLineMetric) -> Bool {
  self.start >= 0 &&
  self.end >= self.start &&
  self.line_number >= 0 &&
  self.width >= 0.0 &&
  self.height > 0.0
}

///|
/// SkParagraph hit-test result exposed as a UTF-8 byte offset.
pub(all) struct ParagraphHitTestResult {
  offset : Int
  downstream : Bool
} derive(Debug, Eq)

///|
/// Native Skia glyph identifier.
pub(all) struct GlyphId(UInt16) derive(Debug, Eq)

///|
pub fn GlyphId::new(value : UInt16) -> GlyphId {
  GlyphId(value)
}

///|
pub fn GlyphId::to_uint16(self : GlyphId) -> UInt16 {
  let GlyphId(value) = self
  value
}

///|
pub fn GlyphId::to_int(self : GlyphId) -> Int {
  self.to_uint16().to_int()
}

///|
/// Native Skia font metrics in y-down coordinates.
pub(all) struct FontMetrics {
  top : Float
  ascent : Float
  descent : Float
  bottom : Float
  leading : Float
  average_char_width : Float
  max_char_width : Float
  x_min : Float
  x_max : Float
  x_height : Float
  cap_height : Float
  underline_thickness : Float?
  underline_position : Float?
  strikeout_thickness : Float?
  strikeout_position : Float?
  has_bounds : Bool
} derive(Debug, Eq)

///|
/// Native Skia typeface.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct Typeface {
  priv handle : TypefaceHandle
}

///|
/// Native typeface plus the value-layer fallback metadata that resolved it.
pub(all) struct ResolvedTypeface {
  typeface : Typeface
  resolution : @skia.FontFallbackResolutionDescriptor
}

///|
pub fn ResolvedTypeface::is_available(self : ResolvedTypeface) -> Bool {
  self.typeface.is_available() && self.resolution.is_valid()
}

///|
pub fn ResolvedTypeface::font_descriptor(
  self : ResolvedTypeface,
  size? : Float = 12.0,
  scale_x? : Float = 1.0,
  skew_x? : Float = 0.0,
) -> @skia.FontDescriptor {
  self.resolution.font_descriptor(size~, scale_x~, skew_x~)
}

///|
pub fn ResolvedTypeface::resource_plan(
  self : ResolvedTypeface,
  size? : Float = 12.0,
  scale_x? : Float = 1.0,
  skew_x? : Float = 0.0,
  request_byte_size? : Int64 = -1L,
  resolution_byte_size? : Int64 = -1L,
  match_byte_size? : Int64 = -1L,
  font_byte_size? : Int64 = -1L,
) -> @skia.RendererResourcePlan {
  self.resolution.resource_plan(
    size~,
    scale_x~,
    skew_x~,
    request_byte_size~,
    resolution_byte_size~,
    match_byte_size~,
    font_byte_size~,
  )
}

///|
/// Native Skia font manager.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct FontMgr {
  priv handle : FontMgrHandle
}

///|
/// Native Skia shader.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct Shader {
  priv handle : ShaderHandle
}

///|
/// Native Skia color filter.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct ColorFilter {
  priv handle : ColorFilterHandle
}

///|
/// Native Skia image filter.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct ImageFilter {
  priv handle : ImageFilterHandle
}

///|
/// Native Skia mask filter.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct MaskFilter {
  priv handle : MaskFilterHandle
}

///|
/// Native Skia bitmap with owned pixel storage.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct Bitmap {
  priv handle : BitmapHandle
}

///|
/// Immutable Skia display list. A picture is safe to retain and replay on the
/// renderer worker; it does not contain MoonBit objects.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct Picture {
  priv handle : PictureHandle
}

///|
/// Native recorder for immutable Skia pictures.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct PictureRecorder {
  priv handle : PictureRecorderHandle
}

///|
/// Native Skia GPU context. The handle owns a Ganesh direct context when a GPU
/// backend is linked.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct GpuContext {
  priv handle : GpuContextHandle
  descriptor : @skia.GpuContextDescriptor
}

///|
/// Native raster or GPU surface. The handle owns a Skia `SkSurface` when Skia is linked.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct Surface {
  priv handle : SurfaceHandle
  descriptor : @skia.SurfaceDescriptor
}

///|
/// Native support decision for creating a surface from a value-layer target.
pub(all) enum SurfaceTargetSupportStatus {
  SurfaceTargetSupported
  SurfaceTargetEmpty
  SurfaceTargetSkiaUnavailable
  SurfaceTargetWindowUnsupported
  SurfaceTargetGpuUnsupported
  SurfaceTargetMissingGpuContext
  SurfaceTargetGpuContextMismatch
  SurfaceTargetGpuContextUnsupported
} derive(Debug, Eq)

///|
pub fn SurfaceTargetSupportStatus::is_supported(
  self : SurfaceTargetSupportStatus,
) -> Bool {
  self is SurfaceTargetSupported
}

///|
/// Native diagnostic status for a value-layer GPU context descriptor.
pub(all) enum SkiaGpuContextSupportStatus {
  SkiaGpuContextAvailable
  SkiaGpuContextSkiaUnavailable
  SkiaGpuContextAnonymous
  SkiaGpuContextMockUnsupported
  SkiaGpuContextBackendUnsupported
  SkiaGpuContextMetalOptInDisabled
  SkiaGpuContextMetalHeadersUnavailable
  SkiaGpuContextMetalRuntimeUnavailable
  SkiaGpuContextDirect3DOptInDisabled
  SkiaGpuContextDirect3DHeadersUnavailable
  SkiaGpuContextDirect3DRuntimeUnavailable
  SkiaGpuContextVulkanOptInDisabled
  SkiaGpuContextVulkanHeadersUnavailable
  SkiaGpuContextVulkanRuntimeUnavailable
  SkiaGpuContextEglOptInDisabled
  SkiaGpuContextEglHeadersUnavailable
  SkiaGpuContextEglRuntimeUnavailable
} derive(Debug, Eq)

///|
pub fn SkiaGpuContextSupportStatus::is_available(
  self : SkiaGpuContextSupportStatus,
) -> Bool {
  self is SkiaGpuContextAvailable
}

///|
/// Canvas borrowed from a native surface.
#warnings("-struct_never_constructed-unused_field")
pub(all) struct Canvas {
  priv handle : CanvasHandle
  priv owner : Surface?
  priv recorder_owner : PictureRecorder?
}

///|
/// Summary of replaying a value-layer render command list into a native Canvas.
pub(all) struct CanvasReplayStats {
  command_count : Int
  replayed_count : Int
  skipped_count : Int
  draw_count : Int
  state_count : Int
  transform_count : Int
  clip_count : Int
  clear_count : Int
  text_count : Int
  present_count : Int
  resource_declaration_count : Int
  image_resource_count : Int
  shader_resource_count : Int
  filter_resource_count : Int
  path_resource_count : Int
  text_resource_count : Int
} derive(Debug, Eq)

///|
/// Coarse outcome for replaying commands into a native Canvas.
pub(all) enum CanvasReplayStatus {
  CanvasReplayComplete
  CanvasReplayDeferredPresent
  CanvasReplaySkippedCommands
} derive(Debug, Eq)

///|
pub fn CanvasReplayStatus::is_complete(self : CanvasReplayStatus) -> Bool {
  self is (CanvasReplayComplete | CanvasReplayDeferredPresent)
}

///|
pub fn CanvasReplayStats::empty() -> CanvasReplayStats {
  {
    command_count: 0,
    replayed_count: 0,
    skipped_count: 0,
    draw_count: 0,
    state_count: 0,
    transform_count: 0,
    clip_count: 0,
    clear_count: 0,
    text_count: 0,
    present_count: 0,
    resource_declaration_count: 0,
    image_resource_count: 0,
    shader_resource_count: 0,
    filter_resource_count: 0,
    path_resource_count: 0,
    text_resource_count: 0,
  }
}

///|
pub fn CanvasReplayStats::is_complete(self : CanvasReplayStats) -> Bool {
  self.status().is_complete()
}

///|
pub fn CanvasReplayStats::status(
  self : CanvasReplayStats,
) -> CanvasReplayStatus {
  if self.skipped_count > 0 {
    CanvasReplaySkippedCommands
  } else if self.present_count > 0 {
    CanvasReplayDeferredPresent
  } else {
    CanvasReplayComplete
  }
}

///|
priv struct CanvasReplayCommandResourceCounts {
  image_resource_count : Int
  shader_resource_count : Int
  filter_resource_count : Int
  path_resource_count : Int
  text_resource_count : Int
}

///|
fn canvas_replay_command_resource_counts(
  command : @skia.RenderCommand,
) -> CanvasReplayCommandResourceCounts {
  let mut image_resource_count = 0
  let mut shader_resource_count = 0
  let mut filter_resource_count = 0
  let mut path_resource_count = 0
  let mut text_resource_count = 0
  for descriptor in command.resource_descriptors() {
    match descriptor.key.kind {
      ImageResource => image_resource_count += 1
      ShaderResource => shader_resource_count += 1
      PathResource => path_resource_count += 1
      ColorFilterResource | ImageFilterResource | MaskFilterResource =>
        filter_resource_count += 1
      TypefaceResource | FontResource | TextRunResource =>
        text_resource_count += 1
      _ => ()
    }
  }
  {
    image_resource_count,
    shader_resource_count,
    filter_resource_count,
    path_resource_count,
    text_resource_count,
  }
}

///|
fn canvas_replay_skipped_stats(
  commands : @skia.RenderCommandList,
) -> CanvasReplayStats {
  let mut command_count = 0
  let mut skipped_count = 0
  let mut draw_count = 0
  let mut state_count = 0
  let mut transform_count = 0
  let mut clip_count = 0
  let mut clear_count = 0
  let mut text_count = 0
  let mut present_count = 0
  let mut resource_declaration_count = 0
  let mut image_resource_count = 0
  let mut shader_resource_count = 0
  let mut filter_resource_count = 0
  let mut path_resource_count = 0
  let mut text_resource_count = 0
  for command in commands.commands {
    command_count += 1
    if command.is_draw() {
      draw_count += 1
    }
    if command.is_state() {
      state_count += 1
    }
    if command.is_transform() {
      transform_count += 1
    }
    if command.is_clip() {
      clip_count += 1
    }
    if command.is_clear() {
      clear_count += 1
    }
    if command.is_text() {
      text_count += 1
    }
    if command.is_resource_declaration() {
      resource_declaration_count += 1
    }
    let resource_counts = canvas_replay_command_resource_counts(command)
    image_resource_count += resource_counts.image_resource_count
    shader_resource_count += resource_counts.shader_resource_count
    filter_resource_count += resource_counts.filter_resource_count
    path_resource_count += resource_counts.path_resource_count
    text_resource_count += resource_counts.text_resource_count
    if command.is_present() {
      present_count += 1
    } else if !command.is_resource_declaration() {
      skipped_count += 1
    }
  }
  {
    command_count,
    replayed_count: 0,
    skipped_count,
    draw_count,
    state_count,
    transform_count,
    clip_count,
    clear_count,
    text_count,
    present_count,
    resource_declaration_count,
    image_resource_count,
    shader_resource_count,
    filter_resource_count,
    path_resource_count,
    text_resource_count,
  }
}

///|
/// Summary of rendering a value-layer frame through a native surface boundary.
pub(all) struct SurfaceFrameReplayStats {
  replay : CanvasReplayStats
  validation_status : @skia.RenderFrameValidationStatus
  surface_matches : Bool
  finalization : @skia.RenderFrameFinalizationDescriptor
  finalized : Bool
} derive(Debug, Eq)

///|
/// Coarse outcome for rendering a value-layer frame through a native surface.
pub(all) enum SurfaceFrameReplayStatus {
  SurfaceFrameReplayComplete
  SurfaceFrameReplayValidationFailed(@skia.RenderFrameValidationStatus)
  SurfaceFrameReplaySurfaceMismatch
  SurfaceFrameReplaySkippedCommands
  SurfaceFrameReplayFinalizationFailed
} derive(Debug, Eq)

///|
pub fn SurfaceFrameReplayStatus::is_complete(
  self : SurfaceFrameReplayStatus,
) -> Bool {
  self is SurfaceFrameReplayComplete
}

///|
pub fn SurfaceFrameReplayStats::status(
  self : SurfaceFrameReplayStats,
) -> SurfaceFrameReplayStatus {
  if !self.validation_status.is_ready() {
    SurfaceFrameReplayValidationFailed(self.validation_status)
  } else if !self.surface_matches {
    SurfaceFrameReplaySurfaceMismatch
  } else if !self.replay.is_complete() {
    SurfaceFrameReplaySkippedCommands
  } else if !self.finalized {
    SurfaceFrameReplayFinalizationFailed
  } else {
    SurfaceFrameReplayComplete
  }
}

///|
pub fn SurfaceFrameReplayStats::is_complete(
  self : SurfaceFrameReplayStats,
) -> Bool {
  self.status().is_complete()
}

///|
pub fn SurfaceFrameReplayStats::finalization_is_ready(
  self : SurfaceFrameReplayStats,
) -> Bool {
  self.finalization.is_ready()
}

///|
pub fn SurfaceFrameReplayStats::finalization_resource_plan(
  self : SurfaceFrameReplayStats,
) -> @skia.RendererResourcePlan {
  self.finalization.resource_plan
}

///|
/// Aggregate counters across the native replay resource caches.
pub(all) struct NativeReplayResourceStats {
  cache_count : Int
  resource_count : Int
  byte_size : Int64
  max_bytes : Int64
  hits : Int
  misses : Int
  evictions : Int
} derive(Debug, Eq)

///|
fn native_replay_resource_stats_empty() -> NativeReplayResourceStats {
  {
    cache_count: 0,
    resource_count: 0,
    byte_size: 0L,
    max_bytes: 0L,
    hits: 0,
    misses: 0,
    evictions: 0,
  }
}

///|
fn native_replay_resource_stats_add_cache(
  total : NativeReplayResourceStats,
  stats : @skia.ResourceCacheStats,
) -> NativeReplayResourceStats {
  {
    cache_count: total.cache_count + 1,
    resource_count: total.resource_count + stats.count,
    byte_size: total.byte_size + stats.byte_size,
    max_bytes: total.max_bytes + stats.max_bytes,
    hits: total.hits + stats.hits,
    misses: total.misses + stats.misses,
    evictions: total.evictions + stats.evictions,
  }
}

///|
/// Native resource caches shared across one or more Canvas replay calls.
pub(all) struct NativeReplayResources {
  image_cache : @skia.ResourceCache[@skia.RendererResourceKey, Image]
  shader_cache : @skia.ResourceCache[@skia.RendererResourceKey, Shader]
  path_cache : @skia.ResourceCache[@skia.RendererResourceKey, Path]
  text_run_cache : @skia.ResourceCache[
    @skia.RendererResourceKey,
    NativeReplayTextRun,
  ]
  font_cache : @skia.ResourceCache[@skia.RendererResourceKey, Font]
  typeface_cache : @skia.ResourceCache[@skia.RendererResourceKey, Typeface]
  color_filter_cache : @skia.ResourceCache[
    @skia.RendererResourceKey,
    ColorFilter,
  ]
  image_filter_cache : @skia.ResourceCache[
    @skia.RendererResourceKey,
    ImageFilter,
  ]
  mask_filter_cache : @skia.ResourceCache[@skia.RendererResourceKey, MaskFilter]
}

///|
pub(all) enum NativeReplayTextRun {
  DirectTextRun(@skia.TextRunDescriptor)
  ShapedTextRun(@skia.ShapedGlyphRunDescriptor)
}

///|
pub fn NativeReplayResources::new(
  image_max_bytes? : Int64 = 0L,
  shader_max_bytes? : Int64 = 0L,
  path_max_bytes? : Int64 = 0L,
  text_run_max_bytes? : Int64 = 0L,
  font_max_bytes? : Int64 = 0L,
  typeface_max_bytes? : Int64 = 0L,
  color_filter_max_bytes? : Int64 = 0L,
  image_filter_max_bytes? : Int64 = 0L,
  mask_filter_max_bytes? : Int64 = 0L,
) -> NativeReplayResources {
  {
    image_cache: @skia.ResourceCache::new(max_bytes=image_max_bytes),
    shader_cache: @skia.ResourceCache::new(max_bytes=shader_max_bytes),
    path_cache: @skia.ResourceCache::new(max_bytes=path_max_bytes),
    text_run_cache: @skia.ResourceCache::new(max_bytes=text_run_max_bytes),
    font_cache: @skia.ResourceCache::new(max_bytes=font_max_bytes),
    typeface_cache: @skia.ResourceCache::new(max_bytes=typeface_max_bytes),
    color_filter_cache: @skia.ResourceCache::new(
      max_bytes=color_filter_max_bytes,
    ),
    image_filter_cache: @skia.ResourceCache::new(
      max_bytes=image_filter_max_bytes,
    ),
    mask_filter_cache: @skia.ResourceCache::new(max_bytes=mask_filter_max_bytes),
  }
}

///|
pub fn NativeReplayResources::stats(
  self : NativeReplayResources,
) -> NativeReplayResourceStats {
  let stats = native_replay_resource_stats_empty()
  let stats = native_replay_resource_stats_add_cache(
    stats,
    self.image_cache.stats(),
  )
  let stats = native_replay_resource_stats_add_cache(
    stats,
    self.shader_cache.stats(),
  )
  let stats = native_replay_resource_stats_add_cache(
    stats,
    self.path_cache.stats(),
  )
  let stats = native_replay_resource_stats_add_cache(
    stats,
    self.text_run_cache.stats(),
  )
  let stats = native_replay_resource_stats_add_cache(
    stats,
    self.font_cache.stats(),
  )
  let stats = native_replay_resource_stats_add_cache(
    stats,
    self.typeface_cache.stats(),
  )
  let stats = native_replay_resource_stats_add_cache(
    stats,
    self.color_filter_cache.stats(),
  )
  let stats = native_replay_resource_stats_add_cache(
    stats,
    self.image_filter_cache.stats(),
  )
  native_replay_resource_stats_add_cache(stats, self.mask_filter_cache.stats())
}