///|
pub(all) struct SnapshotSummary {
  width : Double
  height : Double
  dirty_count : Int
  draw_command_count : Int
  view_node_count : Int
  layout_node_count : Int
  semantics_node_count : Int
  active_effect_task_count : Int
  active_subscription_count : Int
} derive(Eq, Debug, ToJson)

///|
pub extend SnapshotSummary with Debug::{to_repr}

///|
pub extend SnapshotSummary with Eq::{not_equal, equal}

///|
pub extend SnapshotSummary with ToJson::{to_json}

///|
/// A structured Inspector view for DevTools clients. The runtime owns the
/// detailed tree snapshots; this report keeps the five sections together so a
/// tool can inspect one section without parsing a large summary string.
pub(all) struct InspectorReport {
  runtime : @runtime.RuntimeInspectorSnapshot
  view_tree : @runtime.ViewTreeInspectorSnapshot
  layout : @runtime.LayoutInspectorSnapshot
  render : @runtime.RenderInspectorSnapshot
  semantics : @runtime.SemanticsInspectorSnapshot
} derive(Eq, Debug, ToJson)

///|
pub extend InspectorReport with Debug::{to_repr}

///|
pub extend InspectorReport with Eq::{not_equal, equal}

///|
pub extend InspectorReport with ToJson::{to_json}

///|
pub fn InspectorReport::from_snapshot(
  snapshot : @runtime.InspectorSnapshot,
) -> InspectorReport {
  {
    runtime: snapshot.runtime(),
    view_tree: snapshot.view_tree(),
    layout: snapshot.layout(),
    render: snapshot.render(),
    semantics: snapshot.semantics(),
  }
}

///|
pub fn InspectorReport::runtime(
  self : InspectorReport,
) -> @runtime.RuntimeInspectorSnapshot {
  self.runtime
}

///|
pub fn InspectorReport::view_tree(
  self : InspectorReport,
) -> @runtime.ViewTreeInspectorSnapshot {
  self.view_tree
}

///|
pub fn InspectorReport::layout(
  self : InspectorReport,
) -> @runtime.LayoutInspectorSnapshot {
  self.layout
}

///|
pub fn InspectorReport::render(
  self : InspectorReport,
) -> @runtime.RenderInspectorSnapshot {
  self.render
}

///|
pub fn InspectorReport::semantics(
  self : InspectorReport,
) -> @runtime.SemanticsInspectorSnapshot {
  self.semantics
}

///|
pub fn inspector_report(
  snapshot : @runtime.InspectorSnapshot,
) -> InspectorReport {
  InspectorReport::from_snapshot(snapshot)
}

///|
pub struct SnapshotHistory {
  priv entries : Array[SnapshotSummary]
}

///|
pub fn SnapshotSummary::from_snapshot(
  snapshot : @runtime.InspectorSnapshot,
) -> SnapshotSummary {
  {
    width: snapshot.runtime().size().width,
    height: snapshot.runtime().size().height,
    dirty_count: snapshot.runtime().dirty_count(),
    draw_command_count: snapshot.runtime().draw_command_count(),
    view_node_count: snapshot.view_tree().node_count(),
    layout_node_count: snapshot.layout().node_count(),
    semantics_node_count: snapshot.semantics().node_count(),
    active_effect_task_count: snapshot.runtime().active_effect_task_count(),
    active_subscription_count: snapshot.runtime().active_subscription_count(),
  }
}

///|
pub fn snapshot_summary(
  snapshot : @runtime.InspectorSnapshot,
) -> SnapshotSummary {
  SnapshotSummary::from_snapshot(snapshot)
}

///|
pub fn SnapshotSummary::text(self : SnapshotSummary) -> String {
  "size=\{self.width}x\{self.height}; dirty=\{self.dirty_count}; commands=\{self.draw_command_count}; view_nodes=\{self.view_node_count}; layout_nodes=\{self.layout_node_count}; semantics_nodes=\{self.semantics_node_count}; effects=\{self.active_effect_task_count}; subscriptions=\{self.active_subscription_count}"
}

///|
pub fn snapshot_debug_text(snapshot : @runtime.InspectorSnapshot) -> String {
  inspector_report_text(snapshot)
}

///|
/// Render a compact, sectioned Inspector report for terminal output and
/// overlays. Full node details remain available through `inspector_report` and
/// `snapshot_report_json`.
pub fn inspector_report_text(snapshot : @runtime.InspectorSnapshot) -> String {
  let report = InspectorReport::from_snapshot(snapshot)
  let runtime = report.runtime
  let dirty = runtime.dirty_state()
  let size = runtime.size()
  let view = report.view_tree
  let layout = report.layout
  let render = report.render
  let semantics = report.semantics
  let dirty_reasons = dirty.reasons().join(",")
  let root_role = Repr(semantics.root_role()).to_string()
  let hit_path = runtime
    .last_event_hit_path()
    .map(id => id.to_string())
    .join(" > ")
  let timing = runtime.last_frame_timing()
  [
    "[runtime] size=\{size.width}x\{size.height}; rebuild=\{runtime.rebuild_pass_count()}; layout=\{runtime.layout_pass_count()}; paint=\{runtime.paint_pass_count()}; commands=\{runtime.draw_command_count()}",
    "[view] root=\{view.root_kind()}; nodes=\{view.node_count()}; controls=\{view.control_count()}; modified=\{view.modified_count()}; dirty(build/layout/paint)=\{view.dirty_build_count()}/\{view.dirty_layout_count()}/\{view.dirty_paint_count()}",
    "[layout] nodes=\{layout.node_count()}; leaves=\{layout.leaf_count()}; depth=\{layout.max_depth()}; entries=\{layout.bounds().length()}; constraints=available",
    "[render] commands=\{render.command_count()}; text=\{render.text_count()}; images=\{render.image_count()}; paths=\{render.path_count()}; clips/layers/filters=\{render.clip_depth()}/\{render.layer_depth()}/\{render.filter_depth()}",
    "[semantics] root=\{root_role}; nodes=\{semantics.node_count()}; focusable=\{semantics.focusable_count()}; actions=\{semantics.action_count()}; depth=\{semantics.max_depth()}",
    "[dirty] count=\{runtime.dirty_count()}; kind=\{dirty.damage_kind()}; rects=\{dirty.damage_rect_count()}; reason=\{dirty.reason()}; reasons=\{dirty_reasons}",
    "[effects] active=\{runtime.active_effect_task_count()}; started=\{runtime.started_effect_task_count()}; completed=\{runtime.completed_effect_task_count()}; cancelled=\{runtime.cancelled_effect_task_count()}; lifecycle=\{runtime.effect_task_lifecycle().length()}",
    "[subscriptions] active=\{runtime.active_subscription_count()}; started=\{runtime.started_subscription_count()}; reused=\{runtime.reused_subscription_count()}; cancelled=\{runtime.cancelled_subscription_count()}; lifecycle=\{runtime.subscription_lifecycle().length()}",
    "[cache] layout(hit/miss)=\{runtime.layout_cache_hit_count()}/\{runtime.layout_cache_miss_count()}; render(hit/miss)=\{runtime.render_cache_hit_count()}/\{runtime.render_cache_miss_count()}",
    "[event] hit_path=\{hit_path}",
    "[timing] build/layout/paint/draw/total=\{timing.build_ms()}/\{timing.layout_ms()}/\{timing.paint_ms()}/\{timing.draw_ms()}/\{timing.total_ms()}ms; presentation=renderer-session",
  ].join("\n")
}

///|
pub fn snapshot_report_json(
  snapshot : @runtime.InspectorSnapshot,
  indent? : Int = 2,
) -> String {
  ToJson::to_json(InspectorReport::from_snapshot(snapshot)).stringify(indent~)
}

///|
pub fn snapshot_json(
  snapshot : @runtime.InspectorSnapshot,
  indent? : Int = 2,
) -> String {
  snapshot_report_json(snapshot, indent~)
}

///|
pub fn SnapshotHistory::new() -> SnapshotHistory {
  { entries: [], }
}

///|
pub fn SnapshotHistory::push(
  self : SnapshotHistory,
  snapshot : @runtime.InspectorSnapshot,
) -> Unit {
  self.entries.push(SnapshotSummary::from_snapshot(snapshot))
}

///|
pub fn SnapshotHistory::length(self : SnapshotHistory) -> Int {
  self.entries.length()
}

///|
pub fn SnapshotHistory::latest(self : SnapshotHistory) -> SnapshotSummary? {
  if self.entries.is_empty() {
    None
  } else {
    Some(self.entries[self.entries.length() - 1])
  }
}

///|
pub fn SnapshotHistory::debug_text(self : SnapshotHistory) -> String {
  let lines : Array[String] = []
  for index in 0..