///|
pub(all) struct RuntimeInspectorSnapshot {
  size : Size
  dirty_count : Int
  needs_rebuild : Bool
  needs_layout : Bool
  needs_paint : Bool
  needs_redraw : Bool
  draw_command_count : Int
}

///|
pub(all) struct LayoutInspectorSnapshot {
  root_frame : Rect
  node_count : Int
  leaf_count : Int
  max_depth : Int
} derive(Eq, Debug, ToJson)

///|
pub(all) struct RenderInspectorSnapshot {
  command_count : Int
  text_count : Int
  image_count : Int
  clip_depth : Int
  open_clip_depth : Int
  layer_depth : Int
  open_layer_depth : Int
  filter_depth : Int
  open_filter_depth : Int
  path_count : Int
  shader_count : Int
  unbalanced_pop_count : Int
} derive(Eq, Debug, ToJson)

///|
pub(all) struct SemanticsInspectorSnapshot {
  root_role : SemanticsRole
  node_count : Int
  focusable_count : Int
  action_count : Int
  max_depth : Int
} derive(Eq, Debug, ToJson)

///|
pub(all) struct FrameProfile {
  frame : Frame
  dirty_count : Int
  draw_command_count : Int
  active_animations : Int
} derive(Eq, Debug, ToJson)

///|
pub fn RuntimeInspectorSnapshot::from_runtime(
  runtime : AppRuntime,
) -> RuntimeInspectorSnapshot {
  RuntimeInspectorSnapshot::{
    size: runtime.size(),
    dirty_count: runtime.dirty_count(),
    needs_rebuild: runtime.needs_rebuild(),
    needs_layout: runtime.needs_layout(),
    needs_paint: runtime.needs_paint(),
    needs_redraw: runtime.needs_redraw(),
    draw_command_count: runtime.draw_commands().length(),
  }
}

///|
pub fn LayoutInspectorSnapshot::from_runtime(
  runtime : AppRuntime,
) -> LayoutInspectorSnapshot {
  let layout = runtime.layout()
  {
    root_frame: layout.frame,
    node_count: layout_node_count(layout),
    leaf_count: layout_leaf_count(layout),
    max_depth: layout_max_depth(layout),
  }
}

///|
pub fn RenderInspectorSnapshot::from_runtime(
  runtime : AppRuntime,
) -> RenderInspectorSnapshot {
  RenderInspectorSnapshot::from_commands(runtime.draw_commands())
}

///|
pub fn RenderInspectorSnapshot::from_commands(
  commands : Array[DrawCommand],
) -> RenderInspectorSnapshot {
  render_inspector_from_commands(commands)
}

///|
pub fn SemanticsInspectorSnapshot::from_runtime(
  runtime : AppRuntime,
) -> SemanticsInspectorSnapshot {
  let semantics = runtime.semantics()
  {
    root_role: semantics.role,
    node_count: semantics_node_count(semantics),
    focusable_count: semantics_focusable_count(semantics),
    action_count: semantics_action_count(semantics),
    max_depth: semantics_max_depth(semantics),
  }
}

///|
pub fn FrameProfile::capture(
  runtime : AppRuntime,
  frame : Frame,
) -> FrameProfile {
  runtime.dispatch_event(AppEvent::FrameTick(frame))
  {
    frame,
    dirty_count: runtime.dirty_count(),
    draw_command_count: runtime.draw_commands().length(),
    active_animations: runtime.animation_count(),
  }
}

///|
fn layout_node_count(node : LayoutResult) -> Int {
  let mut count = 1
  for child in node.children {
    count = count + layout_node_count(child)
  }
  count
}

///|
fn layout_leaf_count(node : LayoutResult) -> Int {
  if node.children.length() == 0 {
    1
  } else {
    let mut count = 0
    for child in node.children {
      count = count + layout_leaf_count(child)
    }
    count
  }
}

///|
fn layout_max_depth(node : LayoutResult) -> Int {
  let mut depth = 1
  for child in node.children {
    depth = devtools_max_int(depth, 1 + layout_max_depth(child))
  }
  depth
}

///|
fn render_inspector_from_commands(
  commands : Array[DrawCommand],
) -> RenderInspectorSnapshot {
  let mut text_count = 0
  let mut image_count = 0
  let mut clip_depth = 0
  let mut max_clip_depth = 0
  let mut layer_depth = 0
  let mut max_layer_depth = 0
  let mut filter_depth = 0
  let mut max_filter_depth = 0
  let mut path_count = 0
  let mut shader_count = 0
  let mut unbalanced_pop_count = 0
  for command in commands {
    match command {
      DrawText(_) => text_count = text_count + 1
      DrawImage(_) => image_count = image_count + 1
      PushClip(_) | PushRoundedClip(_) => {
        clip_depth = clip_depth + 1
        max_clip_depth = devtools_max_int(max_clip_depth, clip_depth)
      }
      PopClip | PopRoundedClip =>
        if clip_depth == 0 {
          unbalanced_pop_count = unbalanced_pop_count + 1
        } else {
          clip_depth = clip_depth - 1
        }
      PushLayer(_) => {
        layer_depth = layer_depth + 1
        max_layer_depth = devtools_max_int(max_layer_depth, layer_depth)
      }
      PopLayer =>
        if layer_depth == 0 {
          unbalanced_pop_count = unbalanced_pop_count + 1
        } else {
          layer_depth = layer_depth - 1
        }
      PushFilter(_) => {
        filter_depth = filter_depth + 1
        max_filter_depth = devtools_max_int(max_filter_depth, filter_depth)
      }
      PopFilter =>
        if filter_depth == 0 {
          unbalanced_pop_count = unbalanced_pop_count + 1
        } else {
          filter_depth = filter_depth - 1
        }
      DrawPath(_) => path_count = path_count + 1
      DrawShaderEffect(_) => shader_count = shader_count + 1
      _ => ()
    }
  }
  {
    command_count: commands.length(),
    text_count,
    image_count,
    clip_depth: max_clip_depth,
    open_clip_depth: clip_depth,
    layer_depth: max_layer_depth,
    open_layer_depth: layer_depth,
    filter_depth: max_filter_depth,
    open_filter_depth: filter_depth,
    path_count,
    shader_count,
    unbalanced_pop_count,
  }
}

///|
fn semantics_node_count(node : SemanticsNode) -> Int {
  let mut count = 1
  for child in node.children {
    count = count + semantics_node_count(child)
  }
  count
}

///|
fn semantics_focusable_count(node : SemanticsNode) -> Int {
  let mut count = if node.focus_order is Some(_) { 1 } else { 0 }
  for child in node.children {
    count = count + semantics_focusable_count(child)
  }
  count
}

///|
fn semantics_action_count(node : SemanticsNode) -> Int {
  let mut count = node.actions.length()
  for child in node.children {
    count = count + semantics_action_count(child)
  }
  count
}

///|
fn semantics_max_depth(node : SemanticsNode) -> Int {
  let mut depth = 1
  for child in node.children {
    depth = devtools_max_int(depth, 1 + semantics_max_depth(child))
  }
  depth
}

///|
fn devtools_max_int(a : Int, b : Int) -> Int {
  if a > b {
    a
  } else {
    b
  }
}