///|
pub fn plan_renderer_command_fallbacks(
  commands : Array[@core.DrawCommand],
) -> @render.RendererCommandPlan {
  let fallbacks : Array[@render.RendererCommandFallback] = []
  let mut clip_depth = 0
  let mut rounded_clip_depth = 0
  let mut transform_depth = 0
  let mut opacity_depth = 0
  let mut layer_depth = 0
  let mut filter_depth = 0
  for index, command in commands {
    match command {
      PushClip(_) => clip_depth = clip_depth + 1
      PopClip =>
        if clip_depth <= 0 {
          fallbacks.push(
            renderer_command_fallback(
              index~,
              feature=Clip,
              command="PopClip",
              kind=UnbalancedPop,
              reason="PopClip has no matching PushClip",
            ),
          )
        } else {
          clip_depth = clip_depth - 1
        }
      PushRoundedClip(_) => rounded_clip_depth = rounded_clip_depth + 1
      PopRoundedClip =>
        if rounded_clip_depth <= 0 {
          fallbacks.push(
            renderer_command_fallback(
              index~,
              feature=Clip,
              command="PopRoundedClip",
              kind=UnbalancedPop,
              reason="PopRoundedClip has no matching PushRoundedClip",
            ),
          )
        } else {
          rounded_clip_depth = rounded_clip_depth - 1
        }
      PushTransform(_) => transform_depth = transform_depth + 1
      PopTransform =>
        if transform_depth <= 0 {
          fallbacks.push(
            renderer_command_fallback(
              index~,
              feature=Transform,
              command="PopTransform",
              kind=UnbalancedPop,
              reason="PopTransform has no matching PushTransform",
            ),
          )
        } else {
          transform_depth = transform_depth - 1
        }
      PushOpacity(_) => opacity_depth = opacity_depth + 1
      PopOpacity =>
        if opacity_depth <= 0 {
          fallbacks.push(
            renderer_command_fallback(
              index~,
              feature=Opacity,
              command="PopOpacity",
              kind=UnbalancedPop,
              reason="PopOpacity has no matching PushOpacity",
            ),
          )
        } else {
          opacity_depth = opacity_depth - 1
        }
      PushLayer(_) | BeginRetainedLayer(_) => layer_depth = layer_depth + 1
      PopLayer | EndRetainedLayer(_) =>
        if layer_depth <= 0 {
          fallbacks.push(
            renderer_command_fallback(
              index~,
              feature=LayerCompositing,
              command="PopLayer",
              kind=UnbalancedPop,
              reason="PopLayer has no matching PushLayer",
            ),
          )
        } else {
          layer_depth = layer_depth - 1
        }
      PushFilter(_) => filter_depth = filter_depth + 1
      PopFilter =>
        if filter_depth <= 0 {
          fallbacks.push(
            renderer_command_fallback(
              index~,
              feature=FilterEffect,
              command="PopFilter",
              kind=UnbalancedPop,
              reason="PopFilter has no matching PushFilter",
            ),
          )
        } else {
          filter_depth = filter_depth - 1
        }
      DrawPath(_) => ()
      DrawShaderEffect(_) => ()
      _ => ()
    }
  }
  if clip_depth > 0 {
    fallbacks.push(
      renderer_command_fallback(
        index=commands.length(),
        feature=Clip,
        command="PushClip",
        kind=UnclosedScope,
        reason="command stream ended with an open clip scope",
      ),
    )
  }
  if rounded_clip_depth > 0 {
    fallbacks.push(
      renderer_command_fallback(
        index=commands.length(),
        feature=Clip,
        command="PushRoundedClip",
        kind=UnclosedScope,
        reason="command stream ended with an open rounded clip scope",
      ),
    )
  }
  if transform_depth > 0 {
    fallbacks.push(
      renderer_command_fallback(
        index=commands.length(),
        feature=Transform,
        command="PushTransform",
        kind=UnclosedScope,
        reason="command stream ended with an open transform scope",
      ),
    )
  }
  if opacity_depth > 0 {
    fallbacks.push(
      renderer_command_fallback(
        index=commands.length(),
        feature=Opacity,
        command="PushOpacity",
        kind=UnclosedScope,
        reason="command stream ended with an open opacity scope",
      ),
    )
  }
  if layer_depth > 0 {
    fallbacks.push(
      renderer_command_fallback(
        index=commands.length(),
        feature=LayerCompositing,
        command="PushLayer",
        kind=UnclosedScope,
        reason="command stream ended with an open layer scope",
      ),
    )
  }
  if filter_depth > 0 {
    fallbacks.push(
      renderer_command_fallback(
        index=commands.length(),
        feature=FilterEffect,
        command="PushFilter",
        kind=UnclosedScope,
        reason="command stream ended with an open filter scope",
      ),
    )
  }
  @render.RendererCommandPlan::new(
    total_commands=commands.length(),
    fallback_count=fallbacks.length(),
    final_layer_depth=layer_depth,
    final_filter_depth=filter_depth,
    fallbacks~,
  )
}

///|
fn renderer_command_fallback(
  index~ : Int,
  feature~ : @render.RendererFeature,
  command~ : String,
  reason~ : String,
  kind? : @render.RendererCommandFallbackKind = PlannedFallback,
) -> @render.RendererCommandFallback {
  @render.RendererCommandFallback::new(
    index~,
    feature~,
    command~,
    kind~,
    reason~,
  )
}