///|
/// MoUI renderer facade. Core value types (`RendererBackendKind`,
/// `@core.SurfaceMetrics`, `RenderFrameResult`, …) are defined in this
/// file. The package also owns opaque host-surface capabilities, image/frame
/// DTOs, capability reporting, and the provider/session plugin system (ADR
/// 0027) in `provider_contract.mbt` and `renderer_session.mbt`.
///
/// ## RendererProvider plugin system
///
/// See `provider_contract.mbt` for:
/// - `RendererProvider` — static registration and delayed surface binding
/// - `RendererSession` — complete per-window live renderer lifecycle
/// - `HostSurface` — opaque platform capability wrapper
///
/// Provider selection and registries live in `moui/render/common`. Concrete
/// providers live in sub-packages:
/// - `moui_skia_renderer`
/// - `moui_wgpu_renderer`
/// - `moui_sun_renderer`
/// - `moui_web_renderer` and `moui_web_renderer/canvas2d`
pub(all) enum RendererBackendKind {
  NativeWgpu
  WebGpuWasm
  SkiaRasterNative
  SkiaGpuNative
  SunRasterNative
  Canvas2DWasm
} derive(Eq, Debug, ToJson)

///|
pub(all) enum RendererCapabilityStatus {
  Supported
  Unavailable
} derive(Eq, Debug, ToJson)

///|
pub(all) enum RendererFamily {
  Wgpu
  WebGpu
  Skia
  Sun
  Canvas2D
} derive(Eq, Debug, ToJson)

///|
pub(all) enum RendererPresentationModel {
  HostGpuSurface
  WebCanvas
  CpuPixelFrame
  ExternalSurface
} derive(Eq, Debug, ToJson)

///|
pub(all) enum RendererTarget {
  Native
  WasmGc
  Web
} derive(Eq, Debug, ToJson)

///|
pub(all) enum RendererSelection {
  Default
  Backend(RendererBackendKind)
  Family(RendererFamily)
} derive(Eq, Debug, ToJson)

///|
/// Renderer-level window identifier. Mirror of `WindowId` in the host
/// package — the host converts between the two at the API boundary so that
/// `moui/render` never imports `moui/backend`.
pub(all) struct RenderWindowId {
  value : Int
} derive(Eq, Debug, ToJson)

///|
pub fn RenderWindowId::new(value : Int) -> RenderWindowId {
  { value, }
}

///|
pub(all) struct RendererBackendInfo {
  backend : RendererBackendKind
  status : RendererCapabilityStatus
  can_render : Bool
  can_resize : Bool
  owns_surface : Bool
  description : String
} derive(Eq, Debug, ToJson)

///|
pub(all) struct RendererDescriptor {
  backend : RendererBackendKind
  family : RendererFamily
  presentation : RendererPresentationModel
  supported_targets : Array[RendererTarget]
  capabilities : Array[RendererBackendFeatureCapability]
  description : String
} derive(Eq, Debug, ToJson)

///|
pub(all) struct RenderFrameInput {
  metrics : @core.SurfaceMetrics
  commands : Array[@core.DrawCommand]
  damage : @core.DamageRegion
  clear_color : @core.Color?
} derive(Eq, Debug, ToJson)

///|
pub(all) struct RenderFrameResult {
  presented : Bool
  queued : Bool
  cache_hit_count : Int
  cache_miss_count : Int
  cache_update_count : Int
  cache_evict_count : Int
} derive(Eq, Debug, ToJson)

///|
/// Completion emitted by an asynchronous renderer worker. A queued frame is
/// not considered presented until the host drains this record.
pub(all) enum RenderPresentStatus {
  Recorded
  Presented
  Dropped
  FallbackToRaster
} derive(Eq, Debug, ToJson)

///|
/// Renderer-worker completion metadata. The frame token is the only identity
/// used by backend frame owners to accept or reject an asynchronous result.
pub(all) struct RenderPresentCompletion {
  token : FrameToken
  submitted_at_ms : Double
  completed_at_ms : Double
  presented_at_ms : Double
  status : RenderPresentStatus
  gpu_context_generation : Int
} derive(Eq, Debug, ToJson)

///|
pub(all) enum WgpuSurfaceSourceKind {
  NativeHostSurface
  WebCanvasSurface
} derive(Eq, Debug, ToJson)

///|
pub(all) struct WgpuSurfaceContract {
  source : WgpuSurfaceSourceKind
  backend : RendererBackendKind
  ready : Bool
  description : String
} derive(Eq, Debug, ToJson)

///|
pub fn RendererBackendInfo::new(
  backend~ : RendererBackendKind,
  status~ : RendererCapabilityStatus,
  can_render~ : Bool,
  can_resize~ : Bool,
  owns_surface~ : Bool,
  description? : String = "",
) -> RendererBackendInfo {
  { backend, status, can_render, can_resize, owns_surface, description }
}

///|
pub fn RendererBackendKind::label(self : RendererBackendKind) -> String {
  match self {
    NativeWgpu => "native-wgpu"
    WebGpuWasm => "webgpu-wasm"
    SkiaRasterNative => "skia-raster-native"
    SkiaGpuNative => "skia-gpu-native"
    SunRasterNative => "sun-raster-native"
    Canvas2DWasm => "canvas2d-wasm"
  }
}

///|
pub fn RendererBackendKind::short_label(self : RendererBackendKind) -> String {
  match self {
    NativeWgpu => "wgpu"
    WebGpuWasm => "webgpu"
    SkiaRasterNative => "skia"
    SkiaGpuNative => "skia-gpu"
    SunRasterNative => "sun"
    Canvas2DWasm => "canvas2d"
  }
}

///|
pub fn RendererFamily::label(self : RendererFamily) -> String {
  match self {
    Wgpu => "wgpu"
    WebGpu => "webgpu"
    Skia => "skia"
    Sun => "sun"
    Canvas2D => "canvas2d"
  }
}

///|
pub fn RendererPresentationModel::label(
  self : RendererPresentationModel,
) -> String {
  match self {
    HostGpuSurface => "host-gpu-surface"
    WebCanvas => "web-canvas"
    CpuPixelFrame => "cpu-pixel-frame"
    ExternalSurface => "external-surface"
  }
}

///|
pub fn RendererSelection::native_default() -> RendererSelection {
  RendererSelection::Backend(RendererBackendKind::SkiaRasterNative)
}

///|
pub fn RendererSelection::web_default() -> RendererSelection {
  RendererSelection::Backend(RendererBackendKind::WebGpuWasm)
}

///|
pub fn RendererSelection::matches_backend(
  self : RendererSelection,
  backend : RendererBackendKind,
) -> Bool {
  match self {
    Default => false
    Backend(selected) => selected == backend
    Family(family) => backend.family() == family
  }
}

///|
pub fn RendererBackendKind::family(
  self : RendererBackendKind,
) -> RendererFamily {
  match self {
    NativeWgpu => RendererFamily::Wgpu
    WebGpuWasm => RendererFamily::WebGpu
    SkiaRasterNative | SkiaGpuNative => RendererFamily::Skia
    SunRasterNative => RendererFamily::Sun
    Canvas2DWasm => RendererFamily::Canvas2D
  }
}

///|
pub fn RendererDescriptor::new(
  backend~ : RendererBackendKind,
  family~ : RendererFamily,
  presentation~ : RendererPresentationModel,
  supported_targets~ : Array[RendererTarget],
  capabilities~ : Array[RendererBackendFeatureCapability],
  description? : String = "",
) -> RendererDescriptor {
  {
    backend,
    family,
    presentation,
    supported_targets,
    capabilities,
    description,
  }
}

///|
pub fn RenderFrameInput::new(
  metrics~ : @core.SurfaceMetrics,
  commands~ : Array[@core.DrawCommand],
  damage? : @core.DamageRegion = @core.DamageRegion::full(
    "legacy render frame input",
  ),
  clear_color? : @core.Color? = None,
) -> RenderFrameInput {
  { metrics, commands, damage, clear_color }
}

///|
pub fn RenderFrameResult::new(
  presented? : Bool = true,
  queued? : Bool = false,
  cache_hit_count? : Int = 0,
  cache_miss_count? : Int = 0,
  cache_update_count? : Int = 0,
  cache_evict_count? : Int = 0,
) -> RenderFrameResult {
  {
    presented,
    queued,
    cache_hit_count,
    cache_miss_count,
    cache_update_count,
    cache_evict_count,
  }
}

///|
pub fn RenderPresentCompletion::new(
  token~ : FrameToken,
  submitted_at_ms~ : Double,
  completed_at_ms~ : Double,
  presented_at_ms~ : Double,
  status~ : RenderPresentStatus,
  gpu_context_generation~ : Int,
) -> RenderPresentCompletion {
  {
    token,
    submitted_at_ms,
    completed_at_ms,
    presented_at_ms,
    status,
    gpu_context_generation,
  }
}

///|
pub fn RenderPresentCompletion::token(
  self : RenderPresentCompletion,
) -> FrameToken {
  self.token
}

///|
pub fn RenderPresentCompletion::submitted_at_ms(
  self : RenderPresentCompletion,
) -> Double {
  self.submitted_at_ms
}

///|
pub fn RenderPresentCompletion::completed_at_ms(
  self : RenderPresentCompletion,
) -> Double {
  self.completed_at_ms
}

///|
pub fn RenderPresentCompletion::presented_at_ms(
  self : RenderPresentCompletion,
) -> Double {
  self.presented_at_ms
}

///|
pub fn RenderPresentCompletion::status(
  self : RenderPresentCompletion,
) -> RenderPresentStatus {
  self.status
}

///|
pub fn RenderPresentCompletion::gpu_context_generation(
  self : RenderPresentCompletion,
) -> Int {
  self.gpu_context_generation
}

///|
pub fn WgpuSurfaceContract::new(
  source~ : WgpuSurfaceSourceKind,
  backend~ : RendererBackendKind,
  ready~ : Bool,
  description? : String = "",
) -> WgpuSurfaceContract {
  { source, backend, ready, description }
}

///|
pub struct RectBatch {
  priv rect : @core.Rect
  priv color : @core.Color
} derive(Eq, Debug, ToJson)

///|
pub struct RectVertex {
  priv x : Double
  priv y : Double
  priv r : Double
  priv g : Double
  priv b : Double
  priv a : Double
} derive(Eq, Debug, ToJson)

///|
pub fn RectBatch::new(rect~ : @core.Rect, color~ : @core.Color) -> RectBatch {
  { rect, color }
}

///|
pub fn RectBatch::rect(self : RectBatch) -> @core.Rect {
  self.rect
}

///|
pub fn RectBatch::color(self : RectBatch) -> @core.Color {
  self.color
}

///|
pub fn RectVertex::x(self : RectVertex) -> Double {
  self.x
}

///|
pub fn RectVertex::y(self : RectVertex) -> Double {
  self.y
}

///|
pub fn RectVertex::r(self : RectVertex) -> Double {
  self.r
}

///|
pub fn RectVertex::g(self : RectVertex) -> Double {
  self.g
}

///|
pub fn RectVertex::b(self : RectVertex) -> Double {
  self.b
}

///|
pub fn RectVertex::a(self : RectVertex) -> Double {
  self.a
}

///|
pub fn collect_rect_batches(
  commands : Array[@core.DrawCommand],
) -> Array[RectBatch] {
  let batches : Array[RectBatch] = []
  for command in commands {
    match command {
      @core.DrawCommand::FillRect(rect, color) => batches.push({ rect, color })
      @core.DrawCommand::FillRoundedRect(rounded, color) =>
        batches.push({ rect: rounded.rect, color })
      @core.DrawCommand::StrokeRect(rect, color, width) =>
        append_stroke_rect_batches(batches, rect, color, width)
      @core.DrawCommand::StrokeRoundedRect(rounded, color, width) =>
        append_stroke_rect_batches(batches, rounded.rect, color, width)
      @core.DrawCommand::FillRoundedRectBrush(rounded, brush) =>
        batches.push({ rect: rounded.rect, color: brush_fallback_color(brush) })
      @core.DrawCommand::StrokeRoundedRectBrush(rounded, brush, width) =>
        append_stroke_rect_batches(
          batches,
          rounded.rect,
          brush_fallback_color(brush),
          width,
        )
      @core.DrawCommand::DrawShadow(shadow) =>
        append_shadow_batches(batches, shadow)
      @core.DrawCommand::Clear(_)
      | @core.DrawCommand::DrawText(_)
      | @core.DrawCommand::DrawImage(_)
      | @core.DrawCommand::PushClip(_)
      | @core.DrawCommand::PopClip
      | @core.DrawCommand::PushRoundedClip(_)
      | @core.DrawCommand::PopRoundedClip
      | @core.DrawCommand::PushTransform(_)
      | @core.DrawCommand::PopTransform
      | @core.DrawCommand::PushOpacity(_)
      | @core.DrawCommand::PopOpacity
      | @core.DrawCommand::PushLayer(_)
      | @core.DrawCommand::PopLayer
      | @core.DrawCommand::BeginRetainedLayer(_)
      | @core.DrawCommand::EndRetainedLayer(_)
      | @core.DrawCommand::PushFilter(_)
      | @core.DrawCommand::PopFilter
      | @core.DrawCommand::DrawPath(_)
      | @core.DrawCommand::DrawShaderEffect(_) => ()
    }
  }
  batches
}

///|
fn brush_fallback_color(brush : @core.Brush) -> @core.Color {
  match brush {
    @core.Brush::Solid(color) => color
    @core.Brush::LinearGradient(gradient) => gradient.start_color
    @core.Brush::RadialGradient(gradient) => gradient.center_color
  }
}

///|
fn append_shadow_batches(
  batches : Array[RectBatch],
  shadow : @core.ShadowSpec,
) -> Unit {
  let layers = 3
  for index in 0.. Unit {
  if width <= 0.0 || rect.size.width <= 0.0 || rect.size.height <= 0.0 {
    return
  }
  let stroke_width = clamp_stroke_width(width, rect)
  let x = rect.origin.x
  let y = rect.origin.y
  let w = rect.size.width
  let h = rect.size.height
  batches.push({
    rect: @core.Rect::new(x~, y~, width=w, height=stroke_width),
    color,
  })
  batches.push({
    rect: @core.Rect::new(
      x~,
      y=y + h - stroke_width,
      width=w,
      height=stroke_width,
    ),
    color,
  })
  if h > stroke_width * 2.0 {
    batches.push({
      rect: @core.Rect::new(
        x~,
        y=y + stroke_width,
        width=stroke_width,
        height=h - stroke_width * 2.0,
      ),
      color,
    })
    batches.push({
      rect: @core.Rect::new(
        x=x + w - stroke_width,
        y=y + stroke_width,
        width=stroke_width,
        height=h - stroke_width * 2.0,
      ),
      color,
    })
  }
}

///|
fn clamp_stroke_width(width : Double, rect : @core.Rect) -> Double {
  let max_width = rect.size.width.min(rect.size.height) / 2.0
  if width > max_width {
    max_width
  } else {
    width
  }
}

///|
pub fn rect_batches_to_vertices(
  batches : Array[RectBatch],
  size : @core.Size,
) -> Array[RectVertex] {
  let vertices : Array[RectVertex] = []
  for batch in batches {
    append_rect_vertices(vertices, batch, size)
  }
  vertices
}

///|
pub fn rect_vertices_to_bytes(vertices : Array[RectVertex]) -> Bytes {
  let out : Array[Byte] = []
  for vertex in vertices {
    push_f32le(out, vertex.x)
    push_f32le(out, vertex.y)
    push_f32le(out, vertex.r)
    push_f32le(out, vertex.g)
    push_f32le(out, vertex.b)
    push_f32le(out, vertex.a)
  }
  Bytes::from_array(out)
}

///|
pub fn first_clear_color(commands : Array[@core.DrawCommand]) -> @core.Color {
  let mut clear = @core.Color::white()
  let mut found = false
  for command in commands {
    if !found {
      match command {
        @core.DrawCommand::Clear(color) => {
          clear = color
          found = true
        }
        _ => ()
      }
    }
  }
  clear
}

///|
fn append_rect_vertices(
  vertices : Array[RectVertex],
  batch : RectBatch,
  size : @core.Size,
) -> Unit {
  let rect = batch.rect
  let color = batch.color
  let x0 = pixel_x_to_ndc(rect.origin.x, size.width)
  let x1 = pixel_x_to_ndc(rect.origin.x + rect.size.width, size.width)
  let y0 = pixel_y_to_ndc(rect.origin.y, size.height)
  let y1 = pixel_y_to_ndc(rect.origin.y + rect.size.height, size.height)
  push_vertex(vertices, x0, y0, color)
  push_vertex(vertices, x0, y1, color)
  push_vertex(vertices, x1, y1, color)
  push_vertex(vertices, x0, y0, color)
  push_vertex(vertices, x1, y1, color)
  push_vertex(vertices, x1, y0, color)
}

///|
fn push_vertex(
  vertices : Array[RectVertex],
  x : Double,
  y : Double,
  color : @core.Color,
) -> Unit {
  vertices.push({ x, y, r: color.r, g: color.g, b: color.b, a: color.a })
}

///|
fn pixel_x_to_ndc(x : Double, width : Double) -> Double {
  if width <= 0.0 {
    -1.0
  } else {
    x / width * 2.0 - 1.0
  }
}

///|
fn pixel_y_to_ndc(y : Double, height : Double) -> Double {
  if height <= 0.0 {
    1.0
  } else {
    1.0 - y / height * 2.0
  }
}

///|
fn push_f32le(out : Array[Byte], value : Double) -> Unit {
  let bits : UInt = Float::from_double(value)
    .reinterpret_as_int()
    .reinterpret_as_uint()
  out.push((bits & 0xFF).to_byte())
  out.push(((bits >> 8) & 0xFF).to_byte())
  out.push(((bits >> 16) & 0xFF).to_byte())
  out.push(((bits >> 24) & 0xFF).to_byte())
}

///|
pub fn grow_capacity(required : UInt64) -> UInt64 {
  let mut capacity = 256UL
  while capacity < required {
    capacity = capacity * 2UL
  }
  capacity
}

///|
pub fn size_dim_to_uint(value : Double) -> UInt {
  if value < 1.0 {
    1U
  } else {
    value.to_int().reinterpret_as_uint()
  }
}