///|
priv enum IWidget {
  IWindow(win~ : @gtk.Window, child~ : Ref[IWidget])
  IBox(
    gtk_box~ : @gtk.Box,
    children~ : Ref[Array[IWidget]],
    css_classes~ : Ref[Array[String]]
  )
  IButton(
    button~ : @gtk.Button,
    on_clicked~ : Ref[() -> Unit],
    css_classes~ : Ref[Array[String]]
  )
  ILabel(label~ : @gtk.Label, css_classes~ : Ref[Array[String]])
  IEntry(
    entry~ : @gtk.Entry,
    last_text~ : Ref[String],
    on_changed~ : Ref[(String) -> Unit],
    on_activate~ : Ref[(() -> Unit)?],
    updating~ : Ref[Bool]
  )
  IAdwApplicationWindow(win~ : @adw.ApplicationWindow, content~ : Ref[IWidget])
  IAdwToolbarView(
    view~ : @adw.ToolbarView,
    top_bar~ : Ref[IWidget?],
    bottom_bar~ : Ref[IWidget?],
    content~ : Ref[IWidget]
  )
  IAdwHeaderBar(
    header_bar~ : @adw.HeaderBar,
    title_widget~ : Ref[IWidget?],
    start~ : Ref[Array[IWidget]],
    end~ : Ref[Array[IWidget]]
  )
  IScrolledWindow(sw~ : @gtk.ScrolledWindow, child~ : Ref[IWidget])
  IListBox(
    list_box~ : @gtk.ListBox,
    children~ : Ref[Map[String, IWidget]],
    css_classes~ : Ref[Array[String]]
  )
  ICheckButton(
    check_button~ : @gtk.CheckButton,
    on_toggled~ : Ref[(Bool) -> Unit],
    last_active~ : Ref[Bool]
  )
  IAdwClamp(clamp~ : @adw.Clamp, child~ : Ref[IWidget])
  IAdwActionRow(
    action_row~ : @adw.ActionRow,
    prefix~ : Ref[Array[IWidget]],
    suffix~ : Ref[Array[IWidget]]
  )
  ISeparator(sep~ : @gtk.Separator)
  IAdwNavigationSplitView(
    split_view~ : @adw.NavigationSplitView,
    sidebar_page~ : @adw.NavigationPage,
    content_page~ : @adw.NavigationPage,
    sidebar~ : Ref[IWidget],
    content~ : Ref[IWidget]
  )
}

///|
fn IWidget::gtk_widget(self : IWidget) -> @gtk.Widget {
  match self {
    IWindow(win~, ..) => win.as_widget()
    IBox(gtk_box~, ..) => gtk_box.as_widget()
    IButton(button~, ..) => button.as_widget()
    ILabel(label~, ..) => label.as_widget()
    IEntry(entry~, ..) => entry.as_widget()
    IAdwApplicationWindow(win~, ..) => win.as_gtk_widget()
    IAdwToolbarView(view~, ..) => view.as_gtk_widget()
    IAdwHeaderBar(header_bar~, ..) => header_bar.as_gtk_widget()
    IScrolledWindow(sw~, ..) => sw.as_widget()
    IListBox(list_box~, ..) => list_box.as_widget()
    ICheckButton(check_button~, ..) => check_button.as_widget()
    IAdwClamp(clamp~, ..) => clamp.as_gtk_widget()
    IAdwActionRow(action_row~, ..) => action_row.as_gtk_widget()
    ISeparator(sep~) => sep.as_widget()
    IAdwNavigationSplitView(split_view~, ..) => split_view.as_gtk_widget()
  }
}

///|
fn IWidget::destroy(self : IWidget) -> Unit {
  match self {
    IWindow(win~, child~) => {
      child.val.destroy()
      win.destroy()
    }
    IBox(children~, ..) =>
      for c in children.val {
        c.destroy()
      }
    IButton(_) | ILabel(..) | IEntry(..) | ICheckButton(_) | ISeparator(_) => ()
    IAdwApplicationWindow(win~, content~) => {
      content.val.destroy()
      win.destroy()
    }
    IAdwToolbarView(top_bar~, bottom_bar~, content~, ..) => {
      if top_bar.val is Some(w) {
        w.destroy()
      }
      if bottom_bar.val is Some(w) {
        w.destroy()
      }
      content.val.destroy()
    }
    IAdwHeaderBar(title_widget~, start~, end~, ..) => {
      if title_widget.val is Some(w) {
        w.destroy()
      }
      for w in start.val {
        w.destroy()
      }
      for w in end.val {
        w.destroy()
      }
    }
    IScrolledWindow(child~, ..) => child.val.destroy()
    IListBox(children~, ..) =>
      for _, c in children.val {
        c.destroy()
      }
    IAdwClamp(child~, ..) => child.val.destroy()
    IAdwActionRow(prefix~, suffix~, ..) => {
      for w in prefix.val {
        w.destroy()
      }
      for w in suffix.val {
        w.destroy()
      }
    }
    IAdwNavigationSplitView(sidebar~, content~, ..) => {
      sidebar.val.destroy()
      content.val.destroy()
    }
  }
}

///|
fn[Message] VWidget::create(
  self : VWidget[Message],
  app : @gtk.Application,
  dispatch : (Message) -> Unit,
) -> IWidget {
  match self {
    Window(title~, default_width~, default_height~, child~) =>
      create_window(app, dispatch, title, default_width, default_height, child)
    Box(
      orientation~,
      spacing~,
      margin_top~,
      margin_bottom~,
      margin_start~,
      margin_end~,
      halign~,
      css_classes~,
      children~
    ) =>
      create_box(
        app, dispatch, orientation, spacing, margin_top, margin_bottom, margin_start,
        margin_end, halign, css_classes, children,
      )
    Button(label~, icon_name~, css_classes~, on_clicked~) =>
      create_button(dispatch, label, icon_name, css_classes, on_clicked)
    Label(
      text~,
      markup~,
      wrap~,
      selectable~,
      xalign~,
      max_width_chars~,
      css_classes~
    ) =>
      create_label(
        text, markup, wrap, selectable, xalign, max_width_chars, css_classes,
      )
    Entry(text~, placeholder~, hexpand~, on_changed~, on_activate~) =>
      create_entry(
        dispatch, text, placeholder, hexpand, on_changed, on_activate,
      )
    AdwApplicationWindow(title~, default_width~, default_height~, content~) =>
      create_adw_application_window(
        app, dispatch, title, default_width, default_height, content,
      )
    AdwToolbarView(top_bar~, bottom_bar~, content~) =>
      create_adw_toolbar_view(app, dispatch, top_bar, bottom_bar, content)
    AdwHeaderBar(title_widget~, start~, end~) =>
      create_adw_header_bar(app, dispatch, title_widget, start, end)
    ScrolledWindow(hscrollbar_policy~, vscrollbar_policy~, vexpand~, child~) =>
      create_scrolled_window(
        app, dispatch, hscrollbar_policy, vscrollbar_policy, vexpand, child,
      )
    ListBox(css_classes~, children~) =>
      create_list_box(app, dispatch, css_classes, children)
    CheckButton(active~, on_toggled~) =>
      create_check_button(dispatch, active, on_toggled)
    AdwClamp(maximum_size~, child~) =>
      create_adw_clamp(app, dispatch, maximum_size, child)
    AdwActionRow(title~, subtitle~, prefix~, suffix~) =>
      create_adw_action_row(app, dispatch, title, subtitle, prefix, suffix)
    Separator(orientation~) => create_separator(orientation)
    AdwNavigationSplitView(
      sidebar~,
      sidebar_title~,
      content~,
      content_title~,
      min_sidebar_width~,
      max_sidebar_width~,
      sidebar_width_fraction~
    ) =>
      create_adw_navigation_split_view(
        app, dispatch, sidebar, sidebar_title, content, content_title, min_sidebar_width,
        max_sidebar_width, sidebar_width_fraction,
      )
  }
}

///|
fn[Message] create_window(
  app : @gtk.Application,
  dispatch : (Message) -> Unit,
  title : String,
  default_width : Int,
  default_height : Int,
  child : VWidget[Message],
) -> IWidget {
  let win = @gtk.Window::new()
  win.set_application(Some(app))
  win.set_title(Some(title))
  win.set_default_size(default_width, default_height)
  win.connect_destroy(_ => app.quit()) |> ignore()
  let child_iwidget = child.create(app, dispatch)
  win.set_child(Some(child_iwidget.gtk_widget()))
  win.present()
  IWindow(win~, child=Ref(child_iwidget))
}

///|
fn[Message] create_box(
  app : @gtk.Application,
  dispatch : (Message) -> Unit,
  orientation : Orientation,
  spacing : Int,
  margin_top : Int,
  margin_bottom : Int,
  margin_start : Int,
  margin_end : Int,
  halign : Align?,
  css_classes : Array[String],
  children : Array[VWidget[Message]],
) -> IWidget {
  let b = @gtk.Box::new(orientation.to_gtk(), spacing)
  b.set_margin_top(margin_top)
  b.set_margin_bottom(margin_bottom)
  b.set_margin_start(margin_start)
  b.set_margin_end(margin_end)
  if halign is Some(a) {
    b.as_widget().set_halign(a.to_gtk())
  }
  for cls in css_classes {
    b.as_widget().add_css_class(cls)
  }
  let child_widgets = children.map(fn(c) {
    let ci = c.create(app, dispatch)
    b.append(ci.gtk_widget())
    ci
  })
  IBox(gtk_box=b, children=Ref(child_widgets), css_classes=Ref(css_classes))
}

///|
fn[Message] create_button(
  dispatch : (Message) -> Unit,
  label : String,
  icon_name : String,
  css_classes : Array[String],
  on_clicked : Message,
) -> IWidget {
  let button = if icon_name != "" {
    @gtk.Button::new_from_icon_name(icon_name)
  } else {
    @gtk.Button::new_with_label(label)
  }
  for cls in css_classes {
    button.as_widget().add_css_class(cls)
  }
  let on_clicked_ref : Ref[() -> Unit] = Ref(fn() { dispatch(on_clicked) })
  button.connect_clicked(_ => (on_clicked_ref.val)()) |> ignore()
  IButton(button~, on_clicked=on_clicked_ref, css_classes=Ref(css_classes))
}

///|
fn create_label(
  text : String,
  markup : Bool,
  wrap : Bool,
  selectable : Bool,
  xalign : Double,
  max_width_chars : Int,
  css_classes : Array[String],
) -> IWidget {
  let label = @gtk.Label::new(None)
  if markup {
    label.set_markup(text)
  } else {
    label.set_text(text)
  }
  label.set_wrap(wrap)
  label.set_selectable(selectable)
  label.set_xalign(Float::from_double(xalign))
  if max_width_chars >= 0 {
    label.set_max_width_chars(max_width_chars)
  }
  for cls in css_classes {
    label.as_widget().add_css_class(cls)
  }
  ILabel(label~, css_classes=Ref(css_classes))
}

///|
fn[Message] create_entry(
  dispatch : (Message) -> Unit,
  text : String,
  placeholder : String,
  hexpand : Bool,
  on_changed : (String) -> Message,
  on_activate : Message?,
) -> IWidget {
  let entry = @gtk.Entry::new()
  entry.as_editable().set_text(text)
  entry.set_placeholder_text(Some(placeholder))
  if hexpand {
    entry.as_widget().set_hexpand(true)
  }
  let handler : Ref[(String) -> Unit] = Ref(fn(t) { dispatch(on_changed(t)) })
  let activate_handler : Ref[(() -> Unit)?] = Ref(
    on_activate.map(fn(msg) { fn() { dispatch(msg) } }),
  )
  let last_text : Ref[String] = Ref(text)
  let updating : Ref[Bool] = Ref(false)
  let buf = entry.get_buffer()
  let _ = buf.connect_inserted_text(fn(buf, _, _, _) {
    if updating.val {
      return
    }
    let t = buf.get_text()
    last_text.val = t
    (handler.val)(t)
  })
  let _ = buf.connect_deleted_text(fn(buf, _, _) {
    if updating.val {
      return
    }
    let t = buf.get_text()
    last_text.val = t
    (handler.val)(t)
  })
  let _ = entry.connect_activate(fn(_) {
    match activate_handler.val {
      Some(f) => f()
      None => ()
    }
  })
  IEntry(
    entry~,
    last_text~,
    on_changed=handler,
    on_activate=activate_handler,
    updating~,
  )
}

///|
fn[Message] create_adw_application_window(
  app : @gtk.Application,
  dispatch : (Message) -> Unit,
  title : String,
  default_width : Int,
  default_height : Int,
  content : VWidget[Message],
) -> IWidget {
  let win = @adw.ApplicationWindow::new(app)
  win.set_title(Some(title))
  win.set_default_size(default_width, default_height)
  win.connect_destroy(_ => app.quit()) |> ignore()
  let content_iw = content.create(app, dispatch)
  win.set_content(Some(content_iw.gtk_widget()))
  win.present()
  IAdwApplicationWindow(win~, content=Ref(content_iw))
}

///|
fn[Message] create_adw_toolbar_view(
  app : @gtk.Application,
  dispatch : (Message) -> Unit,
  top_bar : VWidget[Message]?,
  bottom_bar : VWidget[Message]?,
  content : VWidget[Message],
) -> IWidget {
  let view = @adw.ToolbarView::new()
  let top_iw = match top_bar {
    Some(v) => {
      let i = v.create(app, dispatch)
      view.add_top_bar(i.gtk_widget())
      Some(i)
    }
    None => None
  }
  let bottom_iw = match bottom_bar {
    Some(v) => {
      let i = v.create(app, dispatch)
      view.add_bottom_bar(i.gtk_widget())
      Some(i)
    }
    None => None
  }
  let content_iw = content.create(app, dispatch)
  view.set_content(Some(content_iw.gtk_widget()))
  IAdwToolbarView(
    view~,
    top_bar=Ref(top_iw),
    bottom_bar=Ref(bottom_iw),
    content=Ref(content_iw),
  )
}

///|
fn[Message] create_adw_header_bar(
  app : @gtk.Application,
  dispatch : (Message) -> Unit,
  title_widget : VWidget[Message]?,
  start : Array[VWidget[Message]],
  end : Array[VWidget[Message]],
) -> IWidget {
  let header_bar = @adw.HeaderBar::new()
  let title_iw = match title_widget {
    Some(v) => {
      let i = v.create(app, dispatch)
      header_bar.set_title_widget(Some(i.gtk_widget()))
      Some(i)
    }
    None => None
  }
  let start_iw = start.map(fn(v) {
    let i = v.create(app, dispatch)
    header_bar.pack_start(i.gtk_widget())
    i
  })
  let end_iw = end.map(fn(v) {
    let i = v.create(app, dispatch)
    header_bar.pack_end(i.gtk_widget())
    i
  })
  IAdwHeaderBar(
    header_bar~,
    title_widget=Ref(title_iw),
    start=Ref(start_iw),
    end=Ref(end_iw),
  )
}

///|
fn[Message] create_scrolled_window(
  app : @gtk.Application,
  dispatch : (Message) -> Unit,
  hscrollbar_policy : @gtk.PolicyType,
  vscrollbar_policy : @gtk.PolicyType,
  vexpand : Bool,
  child : VWidget[Message],
) -> IWidget {
  let sw = @gtk.ScrolledWindow::new()
  sw.set_policy(hscrollbar_policy, vscrollbar_policy)
  if vexpand {
    sw.as_widget().set_vexpand(true)
  }
  let child_iw = child.create(app, dispatch)
  sw.set_child(Some(child_iw.gtk_widget()))
  IScrolledWindow(sw~, child=Ref(child_iw))
}

///|
fn[Message] create_list_box(
  app : @gtk.Application,
  dispatch : (Message) -> Unit,
  css_classes : Array[String],
  children : Map[String, VWidget[Message]],
) -> IWidget {
  let lb = @gtk.ListBox::new()
  lb.set_selection_mode(@gtk.SelectionMode::None)
  for cls in css_classes {
    lb.as_widget().add_css_class(cls)
  }
  let child_widgets : Map[String, IWidget] = Map([])
  for key, c in children {
    let ci = c.create(app, dispatch)
    lb.append(ci.gtk_widget())
    child_widgets[key] = ci
  }
  IListBox(
    list_box=lb,
    children=Ref(child_widgets),
    css_classes=Ref(css_classes),
  )
}

///|
fn[Message] create_check_button(
  dispatch : (Message) -> Unit,
  active : Bool,
  on_toggled : (Bool) -> Message,
) -> IWidget {
  let cb = @gtk.CheckButton::new()
  cb.set_active(active)
  let handler : Ref[(Bool) -> Unit] = Ref(fn(a) { dispatch(on_toggled(a)) })
  let last_active : Ref[Bool] = Ref(active)
  cb.connect_toggled(fn(cb) {
    let a = cb.get_active()
    last_active.val = a
    (handler.val)(a)
  })
  |> ignore()
  ICheckButton(check_button=cb, on_toggled=handler, last_active~)
}

///|
fn[Message] create_adw_clamp(
  app : @gtk.Application,
  dispatch : (Message) -> Unit,
  maximum_size : Int,
  child : VWidget[Message],
) -> IWidget {
  let clamp = @adw.Clamp::new()
  clamp.set_maximum_size(maximum_size)
  let child_iw = child.create(app, dispatch)
  clamp.set_child(Some(child_iw.gtk_widget()))
  IAdwClamp(clamp~, child=Ref(child_iw))
}

///|
fn[Message] create_adw_action_row(
  app : @gtk.Application,
  dispatch : (Message) -> Unit,
  title : String,
  subtitle : String,
  prefix : Array[VWidget[Message]],
  suffix : Array[VWidget[Message]],
) -> IWidget {
  let row = @adw.ActionRow::new()
  row.as_preferences_row().set_title(title)
  row.set_subtitle(subtitle)
  let prefix_iw = prefix.map(fn(v) {
    let i = v.create(app, dispatch)
    row.add_prefix(i.gtk_widget())
    i
  })
  let suffix_iw = suffix.map(fn(v) {
    let i = v.create(app, dispatch)
    row.add_suffix(i.gtk_widget())
    i
  })
  IAdwActionRow(action_row=row, prefix=Ref(prefix_iw), suffix=Ref(suffix_iw))
}

///|
fn create_separator(orientation : Orientation) -> IWidget {
  ISeparator(sep=@gtk.Separator::new(orientation.to_gtk()))
}

///|
fn[Message] create_adw_navigation_split_view(
  app : @gtk.Application,
  dispatch : (Message) -> Unit,
  sidebar : VWidget[Message],
  sidebar_title : String,
  content : VWidget[Message],
  content_title : String,
  min_sidebar_width : Double,
  max_sidebar_width : Double,
  sidebar_width_fraction : Double,
) -> IWidget {
  let sidebar_iw = sidebar.create(app, dispatch)
  let sidebar_page = @adw.NavigationPage::new(
    sidebar_iw.gtk_widget(),
    sidebar_title,
  )
  let content_iw = content.create(app, dispatch)
  let content_page = @adw.NavigationPage::new(
    content_iw.gtk_widget(),
    content_title,
  )
  let split_view = @adw.NavigationSplitView::new()
  split_view.set_min_sidebar_width(min_sidebar_width)
  split_view.set_max_sidebar_width(max_sidebar_width)
  split_view.set_sidebar_width_fraction(sidebar_width_fraction)
  split_view.set_sidebar(sidebar_page)
  split_view.set_content(content_page)
  IAdwNavigationSplitView(
    split_view~,
    sidebar_page~,
    content_page~,
    sidebar=Ref(sidebar_iw),
    content=Ref(content_iw),
  )
}

///|
fn[Message] reconcile_optional_child(
  app : @gtk.Application,
  dispatch : (Message) -> Unit,
  old_child : IWidget?,
  new_child : VWidget[Message]?,
  remove~ : (IWidget) -> Unit,
  add~ : (IWidget) -> Unit,
) -> IWidget? {
  match (old_child, new_child) {
    (Some(old), Some(new)) =>
      match old.reconcile(new, app, dispatch) {
        Some(updated) => Some(updated)
        None => {
          remove(old)
          old.destroy()
          let w = new.create(app, dispatch)
          add(w)
          Some(w)
        }
      }
    (Some(old), None) => {
      remove(old)
      old.destroy()
      None
    }
    (None, Some(new)) => {
      let w = new.create(app, dispatch)
      add(w)
      Some(w)
    }
    (None, None) => None
  }
}

///|
fn[Message] IWidget::reconcile(
  self : IWidget,
  widget : VWidget[Message],
  app : @gtk.Application,
  dispatch : (Message) -> Unit,
) -> IWidget? {
  match (self, widget) {
    (
      IWindow(win~, child~) as iw,
      Window(title~, default_width~, default_height~, child=new_child),
    ) => {
      win.set_title(Some(title))
      win.set_default_size(default_width, default_height)
      let new_child_iw = reconcile_widget(app, dispatch, child.val, new_child)
      win.set_child(Some(new_child_iw.gtk_widget()))
      child.val = new_child_iw
      Some(iw)
    }
    (
      IBox(gtk_box~, children~, css_classes~) as ib,
      Box(
        orientation=_,
        spacing=_,
        margin_top~,
        margin_bottom~,
        margin_start~,
        margin_end~,
        halign~,
        css_classes=new_css_classes,
        children=new_children
      ),
    ) => {
      gtk_box.set_margin_top(margin_top)
      gtk_box.set_margin_bottom(margin_bottom)
      gtk_box.set_margin_start(margin_start)
      gtk_box.set_margin_end(margin_end)
      match halign {
        Some(a) => gtk_box.as_widget().set_halign(a.to_gtk())
        None => gtk_box.as_widget().set_halign(@gtk.Align::Fill)
      }
      for cls in css_classes.val {
        gtk_box.as_widget().remove_css_class(cls)
      }
      for cls in new_css_classes {
        gtk_box.as_widget().add_css_class(cls)
      }
      css_classes.val = new_css_classes
      children.val = reconcile_array(
        app,
        dispatch,
        children.val,
        new_children,
        remove=fn(w) { gtk_box.remove(w.gtk_widget()) },
        append=fn(w) { gtk_box.append(w.gtk_widget()) },
      )
      Some(ib)
    }
    (
      IButton(button~, on_clicked~, css_classes~) as ib,
      Button(
        label~,
        icon_name~,
        css_classes=new_css_classes,
        on_clicked=new_on_clicked
      ),
    ) => {
      if icon_name != "" {
        button.set_icon_name(icon_name)
      } else {
        button.set_label(label)
      }
      for cls in css_classes.val {
        button.as_widget().remove_css_class(cls)
      }
      for cls in new_css_classes {
        button.as_widget().add_css_class(cls)
      }
      css_classes.val = new_css_classes
      on_clicked.val = fn() { dispatch(new_on_clicked) }
      Some(ib)
    }
    (
      ILabel(label~, css_classes~) as il,
      Label(
        text~,
        markup~,
        wrap~,
        selectable~,
        xalign~,
        max_width_chars~,
        css_classes=new_css_classes
      ),
    ) => {
      if markup {
        label.set_markup(text)
      } else {
        label.set_text(text)
      }
      label.set_wrap(wrap)
      label.set_selectable(selectable)
      label.set_xalign(Float::from_double(xalign))
      if max_width_chars >= 0 {
        label.set_max_width_chars(max_width_chars)
      }
      for cls in css_classes.val {
        label.as_widget().remove_css_class(cls)
      }
      for cls in new_css_classes {
        label.as_widget().add_css_class(cls)
      }
      css_classes.val = new_css_classes
      Some(il)
    }
    (
      IEntry(entry~, last_text~, on_changed~, on_activate~, updating~) as ie,
      Entry(
        text~,
        placeholder~,
        hexpand=_,
        on_changed=new_on_changed,
        on_activate=new_on_activate
      ),
    ) => {
      if last_text.val != text {
        updating.val = true
        entry.get_buffer().set_text(text, -1)
        updating.val = false
        last_text.val = text
      }
      entry.set_placeholder_text(Some(placeholder))
      on_changed.val = fn(t) { dispatch(new_on_changed(t)) }
      on_activate.val = new_on_activate.map(fn(msg) { fn() { dispatch(msg) } })
      Some(ie)
    }
    (
      IAdwApplicationWindow(win~, content~) as iw,
      AdwApplicationWindow(
        title~,
        default_width~,
        default_height~,
        content=new_content
      ),
    ) => {
      win.set_title(Some(title))
      win.set_default_size(default_width, default_height)
      let new_content_iw = reconcile_widget(
        app,
        dispatch,
        content.val,
        new_content,
      )
      win.set_content(Some(new_content_iw.gtk_widget()))
      content.val = new_content_iw
      Some(iw)
    }
    (
      IAdwToolbarView(view~, top_bar~, bottom_bar~, content~) as iv,
      AdwToolbarView(
        top_bar=new_top,
        bottom_bar=new_bottom,
        content=new_content
      ),
    ) => {
      top_bar.val = reconcile_optional_child(
        app,
        dispatch,
        top_bar.val,
        new_top,
        remove=fn(w) { view.remove(w.gtk_widget()) },
        add=fn(w) { view.add_top_bar(w.gtk_widget()) },
      )
      bottom_bar.val = reconcile_optional_child(
        app,
        dispatch,
        bottom_bar.val,
        new_bottom,
        remove=fn(w) { view.remove(w.gtk_widget()) },
        add=fn(w) { view.add_bottom_bar(w.gtk_widget()) },
      )
      let new_content_iw = reconcile_widget(
        app,
        dispatch,
        content.val,
        new_content,
      )
      view.set_content(Some(new_content_iw.gtk_widget()))
      content.val = new_content_iw
      Some(iv)
    }
    (
      IAdwHeaderBar(header_bar~, title_widget~, start~, end~) as ih,
      AdwHeaderBar(title_widget=new_title, start=new_start, end=new_end),
    ) => {
      title_widget.val = reconcile_optional_child(
        app,
        dispatch,
        title_widget.val,
        new_title,
        remove=fn(_) { header_bar.set_title_widget(None) },
        add=fn(w) { header_bar.set_title_widget(Some(w.gtk_widget())) },
      )
      start.val = reconcile_array(
        app,
        dispatch,
        start.val,
        new_start,
        remove=fn(w) { header_bar.remove(w.gtk_widget()) },
        append=fn(w) { header_bar.pack_start(w.gtk_widget()) },
      )
      end.val = reconcile_array(
        app,
        dispatch,
        end.val,
        new_end,
        remove=fn(w) { header_bar.remove(w.gtk_widget()) },
        append=fn(w) { header_bar.pack_end(w.gtk_widget()) },
      )
      Some(ih)
    }
    (
      IScrolledWindow(sw~, child~) as isw,
      ScrolledWindow(
        hscrollbar_policy~,
        vscrollbar_policy~,
        vexpand=_,
        child=new_child
      ),
    ) => {
      sw.set_policy(hscrollbar_policy, vscrollbar_policy)
      let new_child_iw = reconcile_widget(app, dispatch, child.val, new_child)
      sw.set_child(Some(new_child_iw.gtk_widget()))
      child.val = new_child_iw
      Some(isw)
    }
    (
      IListBox(list_box~, children~, css_classes~) as ilb,
      ListBox(css_classes=new_css_classes, children=new_children),
    ) => {
      for cls in css_classes.val {
        list_box.as_widget().remove_css_class(cls)
      }
      for cls in new_css_classes {
        list_box.as_widget().add_css_class(cls)
      }
      css_classes.val = new_css_classes
      children.val = reconcile_map(
        app,
        dispatch,
        children.val,
        new_children,
        remove=fn(w) { list_box.remove(w.gtk_widget()) },
        append=fn(w) { list_box.append(w.gtk_widget()) },
      )
      Some(ilb)
    }
    (
      ICheckButton(check_button~, on_toggled~, last_active~) as icb,
      CheckButton(active~, on_toggled=new_on_toggled),
    ) => {
      if last_active.val != active {
        check_button.set_active(active)
        last_active.val = active
      }
      on_toggled.val = fn(a) { dispatch(new_on_toggled(a)) }
      Some(icb)
    }
    (IAdwClamp(clamp~, child~) as ic, AdwClamp(maximum_size~, child=new_child)) => {
      clamp.set_maximum_size(maximum_size)
      let new_child_iw = reconcile_widget(app, dispatch, child.val, new_child)
      clamp.set_child(Some(new_child_iw.gtk_widget()))
      child.val = new_child_iw
      Some(ic)
    }
    (
      IAdwActionRow(action_row~, prefix~, suffix~) as iar,
      AdwActionRow(title~, subtitle~, prefix=new_prefix, suffix=new_suffix),
    ) => {
      action_row.as_preferences_row().set_title(title)
      action_row.set_subtitle(subtitle)
      prefix.val = reconcile_array(
        app,
        dispatch,
        prefix.val,
        new_prefix,
        remove=fn(w) { action_row.remove(w.gtk_widget()) },
        append=fn(w) { action_row.add_prefix(w.gtk_widget()) },
      )
      suffix.val = reconcile_array(
        app,
        dispatch,
        suffix.val,
        new_suffix,
        remove=fn(w) { action_row.remove(w.gtk_widget()) },
        append=fn(w) { action_row.add_suffix(w.gtk_widget()) },
      )
      Some(iar)
    }
    (ISeparator(_) as is_, Separator(orientation=_)) => Some(is_)
    (
      IAdwNavigationSplitView(
        split_view~,
        sidebar_page~,
        content_page~,
        sidebar~,
        content~
      ) as inv,
      AdwNavigationSplitView(
        sidebar=new_sidebar,
        sidebar_title~,
        content=new_content,
        content_title~,
        min_sidebar_width~,
        max_sidebar_width~,
        sidebar_width_fraction~
      ),
    ) => {
      split_view.set_min_sidebar_width(min_sidebar_width)
      split_view.set_max_sidebar_width(max_sidebar_width)
      split_view.set_sidebar_width_fraction(sidebar_width_fraction)
      sidebar_page.set_title(sidebar_title)
      content_page.set_title(content_title)
      let new_sidebar_iw = reconcile_widget(
        app,
        dispatch,
        sidebar.val,
        new_sidebar,
      )
      sidebar_page.set_child(new_sidebar_iw.gtk_widget())
      sidebar.val = new_sidebar_iw
      let new_content_iw = reconcile_widget(
        app,
        dispatch,
        content.val,
        new_content,
      )
      content_page.set_child(new_content_iw.gtk_widget())
      content.val = new_content_iw
      Some(inv)
    }
    _ => None
  }
}