///|
pub struct View[Msg] {
  priv node : ViewNode[Msg]
}

///|
enum ViewNode[Msg] {
  EmptyNode
  TextNode(TextViewSpec)
  ButtonNode(ButtonViewSpec, Msg?)
  TextFieldNode(TextFieldViewSpec, Msg?)
  ControlledTextFieldNode(ControlledTextFieldViewData[Msg])
  RichTextEditorNode(
    RichTextEditorViewSpec,
    ((Int, TextRange?) -> Msg)?,
    ((Int) -> Msg)?
  )
  ControlledRichTextEditorNode(ControlledRichTextEditorViewData[Msg])
  ImageNode(ImageViewSpec)
  CheckboxNode(CheckboxViewSpec)
  ControlledCheckboxNode(ControlledCheckboxViewData[Msg])
  PickerNode(PickerViewSpec)
  ControlledPickerNode(ControlledPickerViewData[Msg])
  DatePickerNode(DatePickerViewSpec)
  ControlledDatePickerNode(ControlledDatePickerViewData[Msg])
  SearchBarNode(SearchBarViewSpec, Msg?, Msg?)
  ControlledSearchBarNode(ControlledSearchBarViewData[Msg])
  SheetNode(SheetViewSpec, ViewNode[Msg], Msg?)
  FlexNode(FlexViewSpec, Array[ViewNode[Msg]])
  PaddingNode(PaddingViewSpec, ViewNode[Msg])
  ContainerNode(ContainerViewSpec, ViewNode[Msg]?)
  SpacerNode(SpacerViewSpec)
  FrameNode(FrameViewSpec, ViewNode[Msg])
  StackNode(StackViewSpec, Array[ViewNode[Msg]])
  ScrollNode(ScrollViewSpec, ViewNode[Msg], ScrollState?, ((Point) -> Msg)?)
  GridNode(GridViewSpec, Array[ViewNode[Msg]])
  ListNode(ListViewSpec, Array[ViewNode[Msg]])
  ModifiedNode(ViewModifierNode[Msg], ViewNode[Msg])
  CustomNode(CustomViewSpec)
  CustomLayoutNode(CustomLayoutSpec, Array[ViewNode[Msg]])
  ComponentNode((BuildContext) -> View[Msg])
}

///|
priv struct ControlledTextFieldViewData[Msg] {
  key : Key?
  value : String
  placeholder : String
  size : Size
  font : FontSpec
  foreground : Color
  placeholder_color : Color
  background : Brush
  border : BorderStyle?
  focused_border : BorderStyle?
  cursor_color : Color
  corner_radius : Double
  on_input : (String) -> Msg
  on_submit : Msg?
}

///|
priv struct ControlledRichTextEditorViewData[Msg] {
  key : Key?
  value : String
  placeholder : String
  size : Size
  format : RichTextFormatter
  font : FontSpec
  foreground : Color
  placeholder_color : Color
  background : Brush
  border : BorderStyle?
  focused_border : BorderStyle?
  cursor_color : Color
  corner_radius : Double
  on_input : (String) -> Msg
  on_selection_change : ((Int, TextRange?) -> Msg)?
  on_prefix_click : ((Int) -> Msg)?
  input_transform : ((String, Int, TextRange?, String) -> RichTextInputTransform?)?
  accepts_tab_input : Bool
}

///|
priv struct ControlledCheckboxViewData[Msg] {
  key : Key?
  label : String
  checked : Bool
  font : FontSpec
  foreground : Color
  check_color : Color
  size : Size
  on_change : (Bool) -> Msg
}

///|
priv struct ControlledPickerViewData[Msg] {
  key : Key?
  items : Array[PickerItem]
  selected : String
  placeholder : String
  font : FontSpec
  foreground : Color
  background : Brush
  cornerRadius : Double
  maxHeight : Double
  on_select : (String) -> Msg
}

///|
priv struct ControlledDatePickerViewData[Msg] {
  key : Key?
  selected : DateValue
  mode : DatePickerMode
  placeholder : String
  font : FontSpec
  foreground : Color
  background : Brush
  corner_radius : Double
  min_date : DateValue?
  max_date : DateValue?
  on_select : (DateValue) -> Msg
}

///|
priv struct ControlledSearchBarViewData[Msg] {
  key : Key?
  query : String
  placeholder : String
  font : FontSpec
  foreground : Color
  placeholder_color : Color
  background : Brush
  border : BorderStyle?
  focused_border : BorderStyle?
  cursor_color : Color
  corner_radius : Double
  on_input : (String) -> Msg
  on_submit : Msg?
  on_clear : Msg?
}

///|
priv enum ViewModifierNode[Msg] {
  KeyModifierNode(Key)
  PlainModifier(ViewModifier)
  RealizedModifier((ViewSpec) -> ViewSpec)
  TapModifier(Msg)
  FileDropModifier((Array[String], Point) -> Msg)
}

///|
fn[Msg] view_from_node(node : ViewNode[Msg]) -> View[Msg] {
  { node, }
}

///|
fn[Msg] modify_view(
  view : View[Msg],
  modifier : ViewModifierNode[Msg],
) -> View[Msg] {
  view_from_node(ModifiedNode(modifier, view.node))
}

///|
fn[Msg] modify_realized_view(
  view : View[Msg],
  modifier : (ViewSpec) -> ViewSpec,
) -> View[Msg] {
  modify_view(view, RealizedModifier(modifier))
}

///|
pub fn[Msg] View::empty() -> View[Msg] {
  view_from_node(EmptyNode)
}

///|
pub fn[Msg] View::text(
  text~ : String,
  size~ : Size,
  font~ : FontSpec,
  color~ : Color,
) -> View[Msg] {
  view_from_node(TextNode({ key: None, text, size, font, color }))
}

///|
pub fn[Msg] View::button(
  text~ : String,
  size~ : Size,
  font~ : FontSpec,
  foreground~ : Color,
  background~ : Brush,
  hovered_background~ : Brush,
  pressed_background~ : Brush,
  border? : BorderStyle? = None,
  shadow? : ShadowStyle? = None,
  corner_radius? : Double = 0.0,
  on_click? : Msg? = None,
) -> View[Msg] {
  view_from_node(
    ButtonNode(
      {
        key: None,
        text,
        size,
        font,
        foreground,
        background,
        hovered_background,
        pressed_background,
        border,
        shadow,
        corner_radius,
        on_click: None,
      },
      on_click,
    ),
  )
}

///|
pub fn[Msg] View::image(
  source~ : String,
  size? : Size? = None,
  opacity? : Double = 1.0,
  corner_radius? : Double = 0.0,
  fit? : ImageFit = ImageFit::Contain,
) -> View[Msg] {
  view_from_node(
    ImageNode({ key: None, source, size, opacity, corner_radius, fit }),
  )
}

///|
pub fn[Msg] View::checkbox(
  label~ : String,
  checked~ : Binding[Bool],
  font~ : FontSpec,
  foreground~ : Color,
  check_color~ : Color,
  size~ : Size,
) -> View[Msg] {
  view_from_node(
    CheckboxNode({
      key: None,
      label,
      checked,
      font,
      foreground,
      check_color,
      size,
    }),
  )
}

///|
pub fn[Msg] View::controlled_checkbox(
  label~ : String,
  checked~ : Bool,
  on_change~ : (Bool) -> Msg,
  font~ : FontSpec,
  foreground~ : Color,
  check_color~ : Color,
  size~ : Size,
) -> View[Msg] {
  view_from_node(
    ControlledCheckboxNode({
      key: None,
      label,
      checked,
      font,
      foreground,
      check_color,
      size,
      on_change,
    }),
  )
}

///|
pub fn[Msg] View::text_field(
  value~ : Binding[String],
  placeholder~ : String,
  size~ : Size,
  font~ : FontSpec,
  foreground~ : Color,
  placeholder_color~ : Color,
  background~ : Brush,
  border? : BorderStyle? = None,
  focused_border? : BorderStyle? = None,
  cursor_color~ : Color,
  corner_radius? : Double = 0.0,
  on_submit? : Msg? = None,
) -> View[Msg] {
  view_from_node(
    TextFieldNode(
      {
        key: None,
        value,
        placeholder,
        size,
        font,
        foreground,
        placeholder_color,
        background,
        border,
        focused_border,
        cursor_color,
        corner_radius,
        on_submit: None,
      },
      on_submit,
    ),
  )
}

///|
pub fn[Msg] View::controlled_text_field(
  value~ : String,
  on_input~ : (String) -> Msg,
  placeholder~ : String,
  size~ : Size,
  font~ : FontSpec,
  foreground~ : Color,
  placeholder_color~ : Color,
  background~ : Brush,
  border? : BorderStyle? = None,
  focused_border? : BorderStyle? = None,
  cursor_color~ : Color,
  corner_radius? : Double = 0.0,
  on_submit? : Msg? = None,
) -> View[Msg] {
  view_from_node(
    ControlledTextFieldNode({
      key: None,
      value,
      placeholder,
      size,
      font,
      foreground,
      placeholder_color,
      background,
      border,
      focused_border,
      cursor_color,
      corner_radius,
      on_input,
      on_submit,
    }),
  )
}

///|
pub fn[Msg] View::rich_text_editor(
  value~ : Binding[String],
  placeholder~ : String,
  size~ : Size,
  format~ : RichTextFormatter,
  font~ : FontSpec,
  foreground~ : Color,
  placeholder_color~ : Color,
  background~ : Brush,
  border? : BorderStyle? = None,
  focused_border? : BorderStyle? = None,
  cursor_color~ : Color,
  corner_radius? : Double = 0.0,
  on_selection_change? : ((Int, TextRange?) -> Msg)? = None,
  on_prefix_click? : ((Int) -> Msg)? = None,
  input_transform? : ((String, Int, TextRange?, String) -> RichTextInputTransform?)? = None,
  accepts_tab_input? : Bool = false,
) -> View[Msg] {
  view_from_node(
    RichTextEditorNode(
      {
        key: None,
        value,
        placeholder,
        size,
        format,
        font,
        foreground,
        placeholder_color,
        background,
        border,
        focused_border,
        cursor_color,
        corner_radius,
        on_selection_change: None,
        on_prefix_click: None,
        input_transform,
        accepts_tab_input,
      },
      on_selection_change,
      on_prefix_click,
    ),
  )
}

///|
pub fn[Msg] View::controlled_rich_text_editor(
  value~ : String,
  on_input~ : (String) -> Msg,
  placeholder~ : String,
  size~ : Size,
  format~ : RichTextFormatter,
  font~ : FontSpec,
  foreground~ : Color,
  placeholder_color~ : Color,
  background~ : Brush,
  border? : BorderStyle? = None,
  focused_border? : BorderStyle? = None,
  cursor_color~ : Color,
  corner_radius? : Double = 0.0,
  on_selection_change? : ((Int, TextRange?) -> Msg)? = None,
  on_prefix_click? : ((Int) -> Msg)? = None,
  input_transform? : ((String, Int, TextRange?, String) -> RichTextInputTransform?)? = None,
  accepts_tab_input? : Bool = false,
) -> View[Msg] {
  view_from_node(
    ControlledRichTextEditorNode({
      key: None,
      value,
      placeholder,
      size,
      format,
      font,
      foreground,
      placeholder_color,
      background,
      border,
      focused_border,
      cursor_color,
      corner_radius,
      on_input,
      on_selection_change,
      on_prefix_click,
      input_transform,
      accepts_tab_input,
    }),
  )
}

///|
pub fn[Msg] View::picker(
  items~ : Array[PickerItem],
  selected~ : Binding[String],
  placeholder~ : String,
  font~ : FontSpec,
  foreground~ : Color,
  background~ : Brush,
  corner_radius~ : Double,
  max_height~ : Double,
) -> View[Msg] {
  view_from_node(
    PickerNode({
      key: None,
      items,
      selected,
      placeholder,
      font,
      foreground,
      background,
      cornerRadius: corner_radius,
      maxHeight: max_height,
    }),
  )
}

///|
pub fn[Msg] View::controlled_picker(
  items~ : Array[PickerItem],
  selected~ : String,
  on_select~ : (String) -> Msg,
  placeholder~ : String,
  font~ : FontSpec,
  foreground~ : Color,
  background~ : Brush,
  corner_radius~ : Double,
  max_height~ : Double,
) -> View[Msg] {
  view_from_node(
    ControlledPickerNode({
      key: None,
      items,
      selected,
      placeholder,
      font,
      foreground,
      background,
      cornerRadius: corner_radius,
      maxHeight: max_height,
      on_select,
    }),
  )
}

///|
pub fn[Msg] View::datepicker(
  selected~ : Binding[DateValue],
  mode~ : DatePickerMode,
  placeholder~ : String,
  font~ : FontSpec,
  foreground~ : Color,
  background~ : Brush,
  corner_radius~ : Double,
  min_date? : DateValue? = None,
  max_date? : DateValue? = None,
) -> View[Msg] {
  view_from_node(
    DatePickerNode({
      key: None,
      selected,
      mode,
      placeholder,
      font,
      foreground,
      background,
      corner_radius,
      min_date,
      max_date,
    }),
  )
}

///|
pub fn[Msg] View::controlled_datepicker(
  selected~ : DateValue,
  on_select~ : (DateValue) -> Msg,
  mode~ : DatePickerMode,
  placeholder~ : String,
  font~ : FontSpec,
  foreground~ : Color,
  background~ : Brush,
  corner_radius~ : Double,
  min_date? : DateValue? = None,
  max_date? : DateValue? = None,
) -> View[Msg] {
  view_from_node(
    ControlledDatePickerNode({
      key: None,
      selected,
      mode,
      placeholder,
      font,
      foreground,
      background,
      corner_radius,
      min_date,
      max_date,
      on_select,
    }),
  )
}

///|
pub fn[Msg] View::search_bar(
  query~ : Binding[String],
  placeholder~ : String,
  font~ : FontSpec,
  foreground~ : Color,
  placeholder_color~ : Color,
  background~ : Brush,
  border? : BorderStyle? = None,
  focused_border? : BorderStyle? = None,
  cursor_color~ : Color,
  corner_radius~ : Double,
  on_submit? : Msg? = None,
  on_clear? : Msg? = None,
) -> View[Msg] {
  view_from_node(
    SearchBarNode(
      {
        key: None,
        query,
        placeholder,
        font,
        foreground,
        placeholder_color,
        background,
        border,
        focused_border,
        cursor_color,
        corner_radius,
        on_submit: None,
        on_clear: None,
      },
      on_submit,
      on_clear,
    ),
  )
}

///|
pub fn[Msg] View::controlled_search_bar(
  query~ : String,
  on_input~ : (String) -> Msg,
  placeholder~ : String,
  font~ : FontSpec,
  foreground~ : Color,
  placeholder_color~ : Color,
  background~ : Brush,
  border? : BorderStyle? = None,
  focused_border? : BorderStyle? = None,
  cursor_color~ : Color,
  corner_radius~ : Double,
  on_submit? : Msg? = None,
  on_clear? : Msg? = None,
) -> View[Msg] {
  view_from_node(
    ControlledSearchBarNode({
      key: None,
      query,
      placeholder,
      font,
      foreground,
      placeholder_color,
      background,
      border,
      focused_border,
      cursor_color,
      corner_radius,
      on_input,
      on_submit,
      on_clear,
    }),
  )
}

///|
pub fn[Msg] View::flex(
  children~ : Array[View[Msg]],
  axis~ : Axis,
  spacing? : Double = 0.0,
  main_axis_alignment? : MainAxisAlignment = MainAxisAlignment::MainStart,
  cross_axis_alignment? : CrossAxisAlignment = CrossAxisAlignment::CrossStretch,
) -> View[Msg] {
  view_from_node(
    FlexNode(
      {
        key: None,
        axis,
        spacing,
        main_axis_alignment,
        cross_axis_alignment,
        children: [],
      },
      children.map(child => child.node),
    ),
  )
}

///|
pub fn[Msg] View::padding_insets(
  child~ : View[Msg],
  insets~ : Insets,
) -> View[Msg] {
  view_from_node(
    PaddingNode({ key: None, insets, child: EmptySpec }, child.node),
  )
}

///|
pub fn[Msg] View::container(
  child? : View[Msg]? = None,
  size? : Size? = None,
  background? : Brush? = None,
  border? : BorderStyle? = None,
  corner_radius? : Double = 0.0,
  shadow? : ShadowStyle? = None,
) -> View[Msg] {
  view_from_node(
    ContainerNode(
      {
        key: None,
        size,
        background,
        border,
        corner_radius,
        shadow,
        child: None,
      },
      match child {
        Some(child) => Some(child.node)
        None => None
      },
    ),
  )
}

///|
pub fn[Msg] View::spacer(
  min_length? : Double = 0.0,
  weight? : Double = 1.0,
) -> View[Msg] {
  view_from_node(SpacerNode({ key: None, min_length, weight }))
}

///|
pub fn[Msg] View::frame_spec(
  child~ : View[Msg],
  width~ : Double?,
  height~ : Double?,
  min_width~ : Double?,
  min_height~ : Double?,
  max_width~ : Double?,
  max_height~ : Double?,
) -> View[Msg] {
  view_from_node(
    FrameNode(
      {
        key: None,
        width,
        height,
        min_width,
        min_height,
        max_width,
        max_height,
        child: EmptySpec,
      },
      child.node,
    ),
  )
}

///|
pub fn[Msg] View::stack(children~ : Array[View[Msg]]) -> View[Msg] {
  view_from_node(
    StackNode({ key: None, children: [] }, children.map(child => child.node)),
  )
}

///|
pub fn[Msg] View::scroll(
  child~ : View[Msg],
  offset? : Point = Point::new(x=0.0, y=0.0),
  viewport? : Size? = None,
  state? : ScrollState? = None,
  on_scroll? : ((Point) -> Msg)? = None,
) -> View[Msg] {
  view_from_node(
    ScrollNode(
      { key: None, offset, viewport, on_scroll: None, child: EmptySpec },
      child.node,
      state,
      on_scroll,
    ),
  )
}

///|
pub fn[Msg] View::grid(
  children~ : Array[View[Msg]],
  columns? : Int = 1,
  row_spacing? : Double = 0.0,
  column_spacing? : Double = 0.0,
) -> View[Msg] {
  view_from_node(
    GridNode(
      { key: None, columns, row_spacing, column_spacing, children: [] },
      children.map(child => child.node),
    ),
  )
}

///|
pub fn[Msg] View::list(
  children~ : Array[View[Msg]],
  spacing? : Double = 0.0,
) -> View[Msg] {
  view_from_node(
    ListNode(
      { key: None, spacing, children: [] },
      children.map(child => child.node),
    ),
  )
}

///|
pub fn[Msg] View::sheet(
  content~ : View[Msg],
  presentation_mode~ : SheetPresentationMode,
  background~ : Brush,
  corner_radius~ : Double,
  shadow? : ShadowStyle? = None,
  on_dismiss? : Msg? = None,
) -> View[Msg] {
  view_from_node(
    SheetNode(
      {
        key: None,
        content: EmptySpec,
        presentation_mode,
        background,
        corner_radius,
        shadow,
        on_dismiss: None,
      },
      content.node,
      on_dismiss,
    ),
  )
}

///|
pub fn[Msg] View::custom_view(
  measure~ : (Constraints) -> Size,
  paint~ : (Rect) -> Array[DrawCommand],
  semantics_role? : SemanticsRole = SemanticsRole::Group,
  semantics_label? : String = "",
) -> View[Msg] {
  view_from_node(
    CustomNode({ key: None, measure, paint, semantics_role, semantics_label }),
  )
}

///|
pub fn[Msg] View::custom_children_layout(
  children~ : Array[View[Msg]],
  measure~ : (CustomLayoutContext) -> Size,
  place~ : (CustomPlacementContext) -> Array[Rect],
  paint? : (Rect) -> Array[DrawCommand] = _frame => [],
  semantics_role? : SemanticsRole = SemanticsRole::Group,
  semantics_label? : String = "",
) -> View[Msg] {
  view_from_node(
    CustomLayoutNode(
      {
        key: None,
        children: [],
        cache: CustomLayoutCache::new(),
        measure,
        place,
        paint,
        semantics_role,
        semantics_label,
      },
      children.map(child => child.node),
    ),
  )
}

///|
pub fn[Msg] View::component(build : (BuildContext) -> View[Msg]) -> View[Msg] {
  view_from_node(ComponentNode(build))
}

///|
fn[Msg] realize_view(view : View[Msg], emit : (Msg) -> Unit) -> ViewSpec {
  realize_view_node(view.node, emit)
}

///|
pub fn[Msg, NextMsg] View::map(
  self : View[Msg],
  map : (Msg) -> NextMsg,
) -> View[NextMsg] {
  view_from_node(map_view_node(self.node, map))
}

///|
pub fn[Msg] View::key(self : View[Msg], value : String) -> View[Msg] {
  modify_view(self, KeyModifierNode(Key::new(value)))
}

///|
pub fn[Msg] View::font(self : View[Msg], font : FontSpec) -> View[Msg] {
  modify_view(self, PlainModifier(FontModifier(font)))
}

///|
pub fn[Msg] View::foreground(self : View[Msg], color : Color) -> View[Msg] {
  modify_view(self, PlainModifier(ForegroundModifier(color)))
}

///|
pub fn[Msg] View::title(self : View[Msg]) -> View[Msg] {
  let theme = Theme::default()
  self.font(theme.typography.title).foreground(theme.foreground)
}

///|
pub fn[Msg] View::caption(self : View[Msg]) -> View[Msg] {
  let theme = Theme::default()
  self.font(theme.typography.caption).foreground(theme.palette.muted)
}

///|
pub fn[Msg] View::mono(self : View[Msg]) -> View[Msg] {
  let theme = Theme::default()
  self
  .font({ ..theme.typography.body, families: FontFamilyStack::monospace() })
  .foreground(theme.foreground)
}

///|
pub fn[Msg] View::background(self : View[Msg], color : Color) -> View[Msg] {
  self.background_brush(Brush::solid(color))
}

///|
pub fn[Msg] View::background_brush(
  self : View[Msg],
  brush : Brush,
) -> View[Msg] {
  modify_view(self, PlainModifier(BackgroundModifier(brush)))
}

///|
pub fn[Msg] View::corner_radius(self : View[Msg], radius : Double) -> View[Msg] {
  modify_view(self, PlainModifier(CornerRadiusModifier(radius)))
}

///|
pub fn[Msg] View::opacity(self : View[Msg], opacity : Double) -> View[Msg] {
  modify_view(self, PlainModifier(OpacityModifier(opacity)))
}

///|
pub fn[Msg] View::shadow(self : View[Msg], style : ShadowStyle) -> View[Msg] {
  modify_view(self, PlainModifier(ShadowModifier(style)))
}

///|
pub fn[Msg] View::border(self : View[Msg], style : BorderStyle) -> View[Msg] {
  modify_view(self, PlainModifier(BorderModifier(style)))
}

///|
pub fn[Msg] View::offset(
  self : View[Msg],
  dx? : Double = 0.0,
  dy? : Double = 0.0,
) -> View[Msg] {
  modify_view(self, PlainModifier(OffsetModifier(dx, dy)))
}

///|
pub fn[Msg] View::clip(self : View[Msg]) -> View[Msg] {
  modify_view(self, PlainModifier(ClipModifier))
}

///|
pub fn[Msg] View::scale(
  self : View[Msg],
  sx? : Double = 1.0,
  sy? : Double = 1.0,
) -> View[Msg] {
  modify_view(self, PlainModifier(ScaleModifier(sx, sy)))
}

///|
pub fn[Msg] View::disabled(
  self : View[Msg],
  disabled? : Bool = true,
) -> View[Msg] {
  modify_view(self, PlainModifier(DisabledModifier(disabled)))
}

///|
pub fn[Msg] View::accessibility_label(
  self : View[Msg],
  label : String,
) -> View[Msg] {
  modify_view(self, PlainModifier(AccessibilityLabelModifier(label)))
}

///|
pub fn[Msg] View::semantics_role(
  self : View[Msg],
  role : SemanticsRole,
) -> View[Msg] {
  modify_view(self, PlainModifier(SemanticsRoleModifier(role)))
}

///|
pub fn[Msg] View::focusable(
  self : View[Msg],
  focusable? : Bool = true,
) -> View[Msg] {
  modify_view(self, PlainModifier(FocusableModifier(focusable)))
}

///|
pub fn[Msg] View::on_tap(self : View[Msg], message : Msg) -> View[Msg] {
  modify_view(self, TapModifier(message))
}

///|
pub fn[Msg] View::on_file_drop(
  self : View[Msg],
  handler : (Array[String], Point) -> Msg,
) -> View[Msg] {
  modify_view(self, FileDropModifier(handler))
}

///|
pub fn[Msg] View::keyboard_shortcut(
  self : View[Msg],
  shortcut : KeyboardShortcut,
) -> View[Msg] {
  modify_view(self, PlainModifier(KeyboardShortcutModifier(shortcut)))
}

///|
pub fn[Msg] View::flexible(
  self : View[Msg],
  weight? : Double = 1.0,
) -> View[Msg] {
  modify_view(self, PlainModifier(FlexibleModifier(max_double(0.0, weight))))
}

///|
pub fn[Msg] View::layout_priority(
  self : View[Msg],
  priority : Double,
) -> View[Msg] {
  modify_view(self, PlainModifier(LayoutPriorityModifier(priority)))
}

///|
pub fn[Msg] View::expanded(
  self : View[Msg],
  weight? : Double = 1.0,
) -> View[Msg] {
  self.flexible(weight~)
}

///|
pub fn[Msg] View::align(
  self : View[Msg],
  alignment? : Alignment = Alignment::center(),
) -> View[Msg] {
  modify_view(self, PlainModifier(AlignModifier(alignment)))
}

///|
pub fn[Msg] View::aspect_ratio(self : View[Msg], ratio : Double) -> View[Msg] {
  modify_view(self, PlainModifier(AspectRatioModifier(max_double(0.01, ratio))))
}

///|
pub fn[Msg] View::intrinsic_width(self : View[Msg]) -> View[Msg] {
  modify_view(self, PlainModifier(IntrinsicWidthModifier))
}

///|
pub fn[Msg] View::intrinsic_height(self : View[Msg]) -> View[Msg] {
  modify_view(self, PlainModifier(IntrinsicHeightModifier))
}

///|
pub fn[Msg] View::padding(self : View[Msg], value : Double) -> View[Msg] {
  modify_view(self, PlainModifier(PaddingModifier(Insets::all(value))))
}

///|
pub fn[Msg] View::spacing(self : View[Msg], spacing : Double) -> View[Msg] {
  modify_realized_view(self, spec => spec.spacing(spacing))
}

///|
pub fn[Msg] View::main_axis_alignment(
  self : View[Msg],
  alignment : MainAxisAlignment,
) -> View[Msg] {
  modify_realized_view(self, spec => spec.main_axis_alignment(alignment))
}

///|
pub fn[Msg] View::cross_axis_alignment(
  self : View[Msg],
  alignment : CrossAxisAlignment,
) -> View[Msg] {
  modify_realized_view(self, spec => spec.cross_axis_alignment(alignment))
}

///|
pub fn[Msg] View::theme(self : View[Msg], theme : Theme) -> View[Msg] {
  modify_realized_view(self, spec => spec.theme(theme))
}

///|
pub fn[Msg] View::environment(
  self : View[Msg],
  environment : Environment,
) -> View[Msg] {
  modify_realized_view(self, spec => spec.environment(environment))
}

///|
pub fn[Msg] View::frame(
  self : View[Msg],
  width? : Double,
  height? : Double,
  min_width? : Double,
  min_height? : Double,
  max_width? : Double,
  max_height? : Double,
) -> View[Msg] {
  modify_view(
    self,
    PlainModifier(
      FrameModifier({
        width,
        height,
        min_width,
        min_height,
        max_width,
        max_height,
      }),
    ),
  )
}

///|
fn[Msg] realize_view_node(
  node : ViewNode[Msg],
  emit : (Msg) -> Unit,
) -> ViewSpec {
  match node {
    EmptyNode => EmptySpec
    TextNode(data) => TextSpec(data)
    ButtonNode(data, message) =>
      ButtonSpec({
        ..data,
        on_click: match message {
          Some(message) => Some(() => emit(message))
          None => None
        },
      })
    TextFieldNode(data, message) =>
      TextFieldSpec({
        ..data,
        on_submit: match message {
          Some(message) => Some(() => emit(message))
          None => None
        },
      })
    ControlledTextFieldNode(data) =>
      TextFieldSpec({
        key: data.key,
        value: Binding::new(get=() => data.value, set=value => {
          emit((data.on_input)(value))
        }),
        placeholder: data.placeholder,
        size: data.size,
        font: data.font,
        foreground: data.foreground,
        placeholder_color: data.placeholder_color,
        background: data.background,
        border: data.border,
        focused_border: data.focused_border,
        cursor_color: data.cursor_color,
        corner_radius: data.corner_radius,
        on_submit: match data.on_submit {
          Some(message) => Some(() => emit(message))
          None => None
        },
      })
    RichTextEditorNode(data, selection, prefix) =>
      RichTextEditorSpec({
        ..data,
        on_selection_change: match selection {
          Some(handler) => Some((caret, range) => emit(handler(caret, range)))
          None => None
        },
        on_prefix_click: match prefix {
          Some(handler) => Some(offset => emit(handler(offset)))
          None => None
        },
      })
    ControlledRichTextEditorNode(data) =>
      RichTextEditorSpec({
        key: data.key,
        value: Binding::new(get=() => data.value, set=value => {
          emit((data.on_input)(value))
        }),
        placeholder: data.placeholder,
        size: data.size,
        format: data.format,
        font: data.font,
        foreground: data.foreground,
        placeholder_color: data.placeholder_color,
        background: data.background,
        border: data.border,
        focused_border: data.focused_border,
        cursor_color: data.cursor_color,
        corner_radius: data.corner_radius,
        on_selection_change: match data.on_selection_change {
          Some(handler) => Some((caret, range) => emit(handler(caret, range)))
          None => None
        },
        on_prefix_click: match data.on_prefix_click {
          Some(handler) => Some(offset => emit(handler(offset)))
          None => None
        },
        input_transform: data.input_transform,
        accepts_tab_input: data.accepts_tab_input,
      })
    ImageNode(data) => ImageSpec(data)
    CheckboxNode(data) => CheckboxSpec(data)
    ControlledCheckboxNode(data) =>
      CheckboxSpec({
        key: data.key,
        label: data.label,
        checked: Binding::new(get=() => data.checked, set=checked => {
          emit((data.on_change)(checked))
        }),
        font: data.font,
        foreground: data.foreground,
        check_color: data.check_color,
        size: data.size,
      })
    PickerNode(data) => PickerSpec(data)
    ControlledPickerNode(data) =>
      PickerSpec({
        key: data.key,
        items: data.items,
        selected: Binding::new(get=() => data.selected, set=selected => {
          emit((data.on_select)(selected))
        }),
        placeholder: data.placeholder,
        font: data.font,
        foreground: data.foreground,
        background: data.background,
        cornerRadius: data.cornerRadius,
        maxHeight: data.maxHeight,
      })
    DatePickerNode(data) => DatePickerSpec(data)
    ControlledDatePickerNode(data) =>
      DatePickerSpec({
        key: data.key,
        selected: Binding::new(get=() => data.selected, set=selected => {
          emit((data.on_select)(selected))
        }),
        mode: data.mode,
        placeholder: data.placeholder,
        font: data.font,
        foreground: data.foreground,
        background: data.background,
        corner_radius: data.corner_radius,
        min_date: data.min_date,
        max_date: data.max_date,
      })
    SearchBarNode(data, submit, clear) =>
      SearchBarSpec({
        ..data,
        on_submit: match submit {
          Some(message) => Some(() => emit(message))
          None => None
        },
        on_clear: match clear {
          Some(message) => Some(() => emit(message))
          None => None
        },
      })
    ControlledSearchBarNode(data) =>
      SearchBarSpec({
        key: data.key,
        query: Binding::new(get=() => data.query, set=query => {
          emit((data.on_input)(query))
        }),
        placeholder: data.placeholder,
        font: data.font,
        foreground: data.foreground,
        placeholder_color: data.placeholder_color,
        background: data.background,
        border: data.border,
        focused_border: data.focused_border,
        cursor_color: data.cursor_color,
        corner_radius: data.corner_radius,
        on_submit: match data.on_submit {
          Some(message) => Some(() => emit(message))
          None => None
        },
        on_clear: match data.on_clear {
          Some(message) => Some(() => emit(message))
          None => None
        },
      })
    SheetNode(data, content, dismiss) =>
      SheetSpec({
        ..data,
        content: realize_view_node(content, emit),
        on_dismiss: match dismiss {
          Some(message) => Some(() => emit(message))
          None => None
        },
      })
    FlexNode(data, children) =>
      FlexSpec({
        ..data,
        children: children.map(child => realize_view_node(child, emit)),
      })
    PaddingNode(data, child) =>
      PaddingSpec({ ..data, child: realize_view_node(child, emit) })
    ContainerNode(data, child) =>
      ContainerSpec({
        ..data,
        child: match child {
          Some(child) => Some(realize_view_node(child, emit))
          None => None
        },
      })
    SpacerNode(data) => SpacerSpec(data)
    FrameNode(data, child) =>
      FrameSpec({ ..data, child: realize_view_node(child, emit) })
    StackNode(data, children) =>
      StackSpec({
        ..data,
        children: children.map(child => realize_view_node(child, emit)),
      })
    ScrollNode(data, child, state, scroll) =>
      ScrollSpec({
        ..data,
        child: realize_view_node(child, emit),
        on_scroll: match (state, scroll) {
          (Some(state), Some(handler)) =>
            Some(delta => {
              state.scroll_to(
                x=0.0,
                y=max_scroll_offset(0.0, state.offset().y - delta.y),
              )
              emit(handler(delta))
            })
          (Some(state), None) =>
            Some(delta => {
              state.scroll_to(
                x=0.0,
                y=max_scroll_offset(0.0, state.offset().y - delta.y),
              )
            })
          (None, Some(handler)) => Some(delta => emit(handler(delta)))
          (None, None) => None
        },
      })
    GridNode(data, children) =>
      GridSpec({
        ..data,
        children: children.map(child => realize_view_node(child, emit)),
      })
    ListNode(data, children) =>
      ListSpec({
        ..data,
        children: children.map(child => realize_view_node(child, emit)),
      })
    ModifiedNode(modifier, child) => {
      let spec = realize_view_node(child, emit)
      match modifier {
        KeyModifierNode(key) => spec.with_key(key)
        PlainModifier(modifier) => spec.modified(modifier)
        RealizedModifier(modifier) => modifier(spec)
        TapModifier(message) => spec.on_tap(() => emit(message))
        FileDropModifier(handler) =>
          spec.on_file_drop((paths, point) => emit(handler(paths, point)))
      }
    }
    CustomNode(data) => CustomSpec(data)
    CustomLayoutNode(data, children) =>
      CustomLayoutSpec({
        ..data,
        children: children.map(child => realize_view_node(child, emit)),
      })
    ComponentNode(build) =>
      ViewSpec::component(Component::new(ctx => realize_view(build(ctx), emit)))
  }
}

///|
fn[Msg, NextMsg] map_view_node(
  node : ViewNode[Msg],
  map : (Msg) -> NextMsg,
) -> ViewNode[NextMsg] {
  match node {
    EmptyNode => EmptyNode
    TextNode(data) => TextNode(data)
    ButtonNode(data, message) =>
      ButtonNode(data, option_map_message(message, map))
    TextFieldNode(data, message) =>
      TextFieldNode(data, option_map_message(message, map))
    ControlledTextFieldNode(data) =>
      ControlledTextFieldNode({
        key: data.key,
        value: data.value,
        placeholder: data.placeholder,
        size: data.size,
        font: data.font,
        foreground: data.foreground,
        placeholder_color: data.placeholder_color,
        background: data.background,
        border: data.border,
        focused_border: data.focused_border,
        cursor_color: data.cursor_color,
        corner_radius: data.corner_radius,
        on_input: value => map((data.on_input)(value)),
        on_submit: option_map_message(data.on_submit, map),
      })
    RichTextEditorNode(data, selection, prefix) =>
      RichTextEditorNode(
        data,
        option_map_selection_handler(selection, map),
        option_map_prefix_handler(prefix, map),
      )
    ControlledRichTextEditorNode(data) =>
      ControlledRichTextEditorNode({
        key: data.key,
        value: data.value,
        placeholder: data.placeholder,
        size: data.size,
        format: data.format,
        font: data.font,
        foreground: data.foreground,
        placeholder_color: data.placeholder_color,
        background: data.background,
        border: data.border,
        focused_border: data.focused_border,
        cursor_color: data.cursor_color,
        corner_radius: data.corner_radius,
        on_input: value => map((data.on_input)(value)),
        on_selection_change: option_map_selection_handler(
          data.on_selection_change,
          map,
        ),
        on_prefix_click: option_map_prefix_handler(data.on_prefix_click, map),
        input_transform: data.input_transform,
        accepts_tab_input: data.accepts_tab_input,
      })
    ImageNode(data) => ImageNode(data)
    CheckboxNode(data) => CheckboxNode(data)
    ControlledCheckboxNode(data) =>
      ControlledCheckboxNode({
        key: data.key,
        label: data.label,
        checked: data.checked,
        font: data.font,
        foreground: data.foreground,
        check_color: data.check_color,
        size: data.size,
        on_change: value => map((data.on_change)(value)),
      })
    PickerNode(data) => PickerNode(data)
    ControlledPickerNode(data) =>
      ControlledPickerNode({
        key: data.key,
        items: data.items,
        selected: data.selected,
        placeholder: data.placeholder,
        font: data.font,
        foreground: data.foreground,
        background: data.background,
        cornerRadius: data.cornerRadius,
        maxHeight: data.maxHeight,
        on_select: value => map((data.on_select)(value)),
      })
    DatePickerNode(data) => DatePickerNode(data)
    ControlledDatePickerNode(data) =>
      ControlledDatePickerNode({
        key: data.key,
        selected: data.selected,
        mode: data.mode,
        placeholder: data.placeholder,
        font: data.font,
        foreground: data.foreground,
        background: data.background,
        corner_radius: data.corner_radius,
        min_date: data.min_date,
        max_date: data.max_date,
        on_select: value => map((data.on_select)(value)),
      })
    SearchBarNode(data, submit, clear) =>
      SearchBarNode(
        data,
        option_map_message(submit, map),
        option_map_message(clear, map),
      )
    ControlledSearchBarNode(data) =>
      ControlledSearchBarNode({
        key: data.key,
        query: data.query,
        placeholder: data.placeholder,
        font: data.font,
        foreground: data.foreground,
        placeholder_color: data.placeholder_color,
        background: data.background,
        border: data.border,
        focused_border: data.focused_border,
        cursor_color: data.cursor_color,
        corner_radius: data.corner_radius,
        on_input: value => map((data.on_input)(value)),
        on_submit: option_map_message(data.on_submit, map),
        on_clear: option_map_message(data.on_clear, map),
      })
    SheetNode(data, content, dismiss) =>
      SheetNode(
        data,
        map_view_node(content, map),
        option_map_message(dismiss, map),
      )
    FlexNode(data, children) =>
      FlexNode(data, children.map(child => map_view_node(child, map)))
    PaddingNode(data, child) => PaddingNode(data, map_view_node(child, map))
    ContainerNode(data, child) =>
      ContainerNode(data, option_map_view_node(child, map))
    SpacerNode(data) => SpacerNode(data)
    FrameNode(data, child) => FrameNode(data, map_view_node(child, map))
    StackNode(data, children) =>
      StackNode(data, children.map(child => map_view_node(child, map)))
    ScrollNode(data, child, state, scroll) =>
      ScrollNode(
        data,
        map_view_node(child, map),
        state,
        option_map_scroll_handler(scroll, map),
      )
    GridNode(data, children) =>
      GridNode(data, children.map(child => map_view_node(child, map)))
    ListNode(data, children) =>
      ListNode(data, children.map(child => map_view_node(child, map)))
    ModifiedNode(modifier, child) =>
      ModifiedNode(map_modifier_node(modifier, map), map_view_node(child, map))
    CustomNode(data) => CustomNode(data)
    CustomLayoutNode(data, children) =>
      CustomLayoutNode(data, children.map(child => map_view_node(child, map)))
    ComponentNode(build) => ComponentNode(ctx => build(ctx).map(map))
  }
}

///|
fn[Msg, NextMsg] map_modifier_node(
  modifier : ViewModifierNode[Msg],
  map : (Msg) -> NextMsg,
) -> ViewModifierNode[NextMsg] {
  match modifier {
    KeyModifierNode(key) => KeyModifierNode(key)
    PlainModifier(modifier) => PlainModifier(modifier)
    RealizedModifier(modifier) => RealizedModifier(modifier)
    TapModifier(message) => TapModifier(map(message))
    FileDropModifier(handler) =>
      FileDropModifier((paths, point) => map(handler(paths, point)))
  }
}

///|
fn[Msg, NextMsg] option_map_message(
  message : Msg?,
  map : (Msg) -> NextMsg,
) -> NextMsg? {
  match message {
    Some(message) => Some(map(message))
    None => None
  }
}

///|
fn[Msg, NextMsg] option_map_scroll_handler(
  handler : ((Point) -> Msg)?,
  map : (Msg) -> NextMsg,
) -> ((Point) -> NextMsg)? {
  match handler {
    Some(handler) => Some(delta => map(handler(delta)))
    None => None
  }
}

///|
fn[Msg, NextMsg] option_map_selection_handler(
  handler : ((Int, TextRange?) -> Msg)?,
  map : (Msg) -> NextMsg,
) -> ((Int, TextRange?) -> NextMsg)? {
  match handler {
    Some(handler) => Some((caret, range) => map(handler(caret, range)))
    None => None
  }
}

///|
fn[Msg, NextMsg] option_map_prefix_handler(
  handler : ((Int) -> Msg)?,
  map : (Msg) -> NextMsg,
) -> ((Int) -> NextMsg)? {
  match handler {
    Some(handler) => Some(offset => map(handler(offset)))
    None => None
  }
}

///|
fn max_scroll_offset(min : Double, value : Double) -> Double {
  if value < min {
    min
  } else {
    value
  }
}

///|
fn[Msg, NextMsg] option_map_view_node(
  node : ViewNode[Msg]?,
  map : (Msg) -> NextMsg,
) -> ViewNode[NextMsg]? {
  match node {
    Some(node) => Some(map_view_node(node, map))
    None => None
  }
}