///|
pub fn draw_commands_to_host_calls(
  commands : Array[@core.DrawCommand],
  size : @core.Size,
) -> Array[WebGpuHostCall] {
  let calls : Array[WebGpuHostCall] = [BeginFrame(size)]
  for command in commands {
    match command {
      Clear(color) => calls.push(Clear(color))
      FillRect(rect, color) => calls.push(FillRect(rect, color))
      StrokeRect(rect, color, width) =>
        calls.push(StrokeRect(rect, color, width))
      FillRoundedRect(rect, color) => calls.push(FillRoundedRect(rect, color))
      StrokeRoundedRect(rect, color, width) =>
        calls.push(StrokeRoundedRect(rect, color, width))
      FillRoundedRectBrush(rect, brush) =>
        calls.push(FillRoundedRectBrush(rect, brush))
      StrokeRoundedRectBrush(rect, brush, width) =>
        calls.push(StrokeRoundedRectBrush(rect, brush, width))
      DrawShadow(shadow) => calls.push(DrawShadow(shadow))
      DrawText(run) => calls.push(DrawText(run))
      DrawImage(run) => calls.push(DrawImage(run))
      DrawPath(path) => calls.push(DrawPath(path))
      PushClip(rect) => calls.push(PushClip(rect))
      PopClip => calls.push(PopClip)
      PushRoundedClip(rect) => calls.push(PushRoundedClip(rect))
      PopRoundedClip => calls.push(PopRoundedClip)
      PushTransform(transform) => calls.push(PushTransform(transform))
      PopTransform => calls.push(PopTransform)
      PushOpacity(opacity) => calls.push(PushOpacity(opacity))
      PopOpacity => calls.push(PopOpacity)
      PushLayer(layer) => calls.push(PushLayer(layer))
      PopLayer => calls.push(PopLayer)
      BeginRetainedLayer(spec) =>
        calls.push(
          PushLayer(
            @core.LayerSpec::new(mask=RectMask(spec.frame), offscreen=true),
          ),
        )
      EndRetainedLayer(_) => calls.push(PopLayer)
      PushFilter(filter) => calls.push(PushFilter(filter))
      PopFilter => calls.push(PopFilter)
      DrawShaderEffect(spec) => calls.push(DrawShaderEffect(spec))
    }
  }
  calls.push(Present)
  calls
}

///|
fn ensure_required_capabilities(
  capabilities : WebGpuHostCapabilities,
) -> Unit raise WebGpuHostError {
  if !capabilities.webgpu_available() {
    raise Unavailable
  }
  if !capabilities.adapter_ready() {
    raise AdapterUnavailable
  }
  if !capabilities.can_render() {
    raise PipelineUnavailable
  }
}

///|
fn submit_host_calls(
  renderer : HostWebGpuRenderer,
  calls : Array[WebGpuHostCall],
) -> Unit raise WebGpuHostError {
  for call in calls {
    match call {
      BeginFrame(size) =>
        raise_for_host_status(
          host_begin_frame(renderer, size.width, size.height),
        )
      Clear(color) =>
        raise_for_host_status(
          host_clear(renderer, color.r, color.g, color.b, color.a),
        )
      FillRect(rect, color) =>
        raise_for_host_status(
          host_fill_rect(
            renderer,
            rect.origin.x,
            rect.origin.y,
            rect.size.width,
            rect.size.height,
            color.r,
            color.g,
            color.b,
            color.a,
          ),
        )
      StrokeRect(rect, color, width) =>
        raise_for_host_status(
          host_stroke_rect(
            renderer,
            rect.origin.x,
            rect.origin.y,
            rect.size.width,
            rect.size.height,
            color.r,
            color.g,
            color.b,
            color.a,
            width,
          ),
        )
      FillRoundedRect(rounded, color) =>
        raise_for_host_status(
          host_fill_rounded_rect(
            renderer,
            rounded.rect.origin.x,
            rounded.rect.origin.y,
            rounded.rect.size.width,
            rounded.rect.size.height,
            rounded.radius,
            color.r,
            color.g,
            color.b,
            color.a,
          ),
        )
      StrokeRoundedRect(rounded, color, width) =>
        raise_for_host_status(
          host_stroke_rounded_rect(
            renderer,
            rounded.rect.origin.x,
            rounded.rect.origin.y,
            rounded.rect.size.width,
            rounded.rect.size.height,
            rounded.radius,
            color.r,
            color.g,
            color.b,
            color.a,
            width,
          ),
        )
      FillRoundedRectBrush(rounded, brush) =>
        submit_fill_rounded_rect_brush(renderer, rounded, brush)
      StrokeRoundedRectBrush(rounded, brush, width) =>
        submit_stroke_rounded_rect_brush(renderer, rounded, brush, width)
      DrawShadow(shadow) =>
        raise_for_host_status(
          host_draw_shadow(
            renderer,
            shadow.rect.rect.origin.x,
            shadow.rect.rect.origin.y,
            shadow.rect.rect.size.width,
            shadow.rect.rect.size.height,
            shadow.rect.radius,
            shadow.offset.x,
            shadow.offset.y,
            shadow.blur_radius,
            shadow.spread,
            shadow.color.r,
            shadow.color.g,
            shadow.color.b,
            shadow.color.a,
          ),
        )
      DrawText(run) =>
        raise_for_host_status(
          host_draw_text(
            renderer,
            host_string(run.text),
            run.frame.origin.x,
            run.frame.origin.y,
            run.frame.size.width,
            run.frame.size.height,
            host_string(run.font.css_family()),
            host_string(run.font.css_style()),
            run.font.size,
            run.font.weight,
            run.color.r,
            run.color.g,
            run.color.b,
            run.color.a,
            text_align_host_code(run.align),
          ),
        )
      DrawImage(run) =>
        raise_for_host_status(
          host_draw_image(
            renderer,
            host_string(run.source),
            run.frame.origin.x,
            run.frame.origin.y,
            run.frame.size.width,
            run.frame.size.height,
            run.opacity,
            image_fit_host_code(run.fit),
          ),
        )
      DrawPath(path) => submit_draw_path(renderer, path)
      PushClip(rect) =>
        raise_for_host_status(
          host_push_clip(
            renderer,
            rect.origin.x,
            rect.origin.y,
            rect.size.width,
            rect.size.height,
          ),
        )
      PopClip => raise_for_host_status(host_pop_clip(renderer))
      PushRoundedClip(rounded) => submit_push_rounded_clip(renderer, rounded)
      PopRoundedClip => raise_for_host_status(host_pop_layer(renderer))
      PushTransform(transform) =>
        raise_for_host_status(
          host_push_transform(
            renderer,
            transform.a,
            transform.b,
            transform.c,
            transform.d,
            transform.tx,
            transform.ty,
          ),
        )
      PopTransform => raise_for_host_status(host_pop_transform(renderer))
      PushOpacity(opacity) =>
        raise_for_host_status(host_push_opacity(renderer, opacity))
      PopOpacity => raise_for_host_status(host_pop_opacity(renderer))
      PushLayer(layer) => submit_push_layer(renderer, layer)
      PopLayer => raise_for_host_status(host_pop_layer(renderer))
      PushFilter(filter) => submit_push_filter(renderer, filter)
      PopFilter => raise_for_host_status(host_pop_filter(renderer))
      DrawShaderEffect(spec) => submit_draw_shader_effect(renderer, spec)
      Present => raise_for_host_status(host_present(renderer))
    }
  }
}

///|
fn submit_push_layer(
  renderer : HostWebGpuRenderer,
  layer : @core.LayerSpec,
) -> Unit raise WebGpuHostError {
  let mask = layer_mask_payload(layer.mask)
  raise_for_host_status(
    host_push_layer(
      renderer,
      layer.opacity,
      blend_mode_host_code(layer.blend_mode),
      mask.kind,
      mask.rect.origin.x,
      mask.rect.origin.y,
      mask.rect.size.width,
      mask.rect.size.height,
      mask.radius,
      layer.offscreen,
    ),
  )
}

///|
fn submit_push_rounded_clip(
  renderer : HostWebGpuRenderer,
  rounded : @core.RoundedRect,
) -> Unit raise WebGpuHostError {
  let mask = layer_mask_payload(RoundedMask(rounded))
  raise_for_host_status(
    host_push_layer(
      renderer,
      1.0,
      blend_mode_host_code(SourceOver),
      mask.kind,
      mask.rect.origin.x,
      mask.rect.origin.y,
      mask.rect.size.width,
      mask.rect.size.height,
      mask.radius,
      true,
    ),
  )
}

///|
fn submit_push_filter(
  renderer : HostWebGpuRenderer,
  filter : @core.FilterEffect,
) -> Unit raise WebGpuHostError {
  let payload = filter_effect_payload(filter)
  raise_for_host_status(
    host_push_filter(
      renderer,
      payload.kind,
      payload.amount,
      host_string(payload.matrix_values),
    ),
  )
}

///|
fn submit_draw_shader_effect(
  renderer : HostWebGpuRenderer,
  spec : @core.ShaderEffectSpec,
) -> Unit raise WebGpuHostError {
  let fallback = brush_payload(spec.fallback, spec.frame)
  raise_for_host_status(
    host_draw_shader_effect(
      renderer,
      host_string(spec.name),
      spec.frame.origin.x,
      spec.frame.origin.y,
      spec.frame.size.width,
      spec.frame.size.height,
      host_string(double_array_string(spec.uniforms)),
      fallback.start.x,
      fallback.start.y,
      fallback.end.x,
      fallback.end.y,
      fallback.start_color.r,
      fallback.start_color.g,
      fallback.start_color.b,
      fallback.start_color.a,
      fallback.end_color.r,
      fallback.end_color.g,
      fallback.end_color.b,
      fallback.end_color.a,
    ),
  )
}

///|
fn submit_draw_path(
  renderer : HostWebGpuRenderer,
  path : @core.PathSpec,
) -> Unit raise WebGpuHostError {
  let mesh = @render_common.tessellate_path(path)
  raise_for_host_status(
    host_draw_path_mesh(renderer, host_string(path_mesh_payload(mesh))),
  )
}