///|
fn aligned_rect(
  bounds : Rect,
  child_size : Size,
  alignment : Alignment,
) -> Rect {
  let width = min_double(bounds.size.width, child_size.width)
  let height = min_double(bounds.size.height, child_size.height)
  let dx = max_double(0.0, bounds.size.width - width) *
    (alignment.x + 1.0) /
    2.0
  let dy = max_double(0.0, bounds.size.height - height) *
    (alignment.y + 1.0) /
    2.0
  Rect::new(x=bounds.origin.x + dx, y=bounds.origin.y + dy, width~, height~)
}

///|
fn element_hit_testable(spec : ViewSpec) -> Bool {
  match spec {
    ButtonSpec(_) => true
    TextFieldSpec(_) => true
    RichTextEditorSpec(_) => true
    CheckboxSpec(_) => true
    PickerSpec(_) => true
    DatePickerSpec(_) => true
    SearchBarSpec(_) => true
    SheetSpec(_) => true
    ContainerSpec(data) => data.background is Some(_)
    ModifiedSpec(data) =>
      data.modifier is BackgroundModifier(_) ||
      data.modifier is BorderModifier(_) ||
      data.modifier is ShadowModifier(_) ||
      data.modifier is OnTapModifier(_) ||
      data.modifier is OnFileDropModifier(_) ||
      data.modifier is FocusableModifier(true)
    CustomSpec(_) | CustomLayoutSpec(_) => true
    ComponentSpec(_) => false
    _ => false
  }
}

///|
fn ElementNode::render_with_environment(
  self : ElementNode,
  placed : PlacedNode,
  focused : ElementId?,
  environment : RenderEnvironment,
) -> RenderNode {
  let bounds = placed.frame
  if environment.matches_default_cache_key() &&
    !self.dirty.needs_paint &&
    !self.dirty.needs_layout &&
    self.render_cache.cached_render_frame == Some(bounds) &&
    self.render_cache.cached_render_focused == focused {
    match self.render_cache.cached_render {
      Some(render) => return render
      None => ()
    }
  }
  let children : Array[RenderNode] = []
  let commands : Array[DrawCommand] = []
  let post_commands : Array[DrawCommand] = []
  match self.spec {
    EmptySpec => ()
    SpacerSpec(_) => ()
    TextSpec(data) =>
      commands.push(
        DrawText(
          TextRun::new(
            text=data.text,
            frame=bounds,
            font=environment_font(environment, data.font),
            color=environment_color(environment, data.color),
          ),
        ),
      )
    ButtonSpec(data) => {
      let background = match self.control.button_state {
        ButtonState::Normal => data.background
        ButtonState::Hovered => data.hovered_background
        ButtonState::Pressed => data.pressed_background
      }
      let rounded = RoundedRect::new(rect=bounds, radius=data.corner_radius)
      match data.shadow {
        Some(shadow) =>
          commands.push(
            DrawShadow(shadow_style_to_spec(shadow, bounds, data.corner_radius)),
          )
        None => ()
      }
      commands.push(FillRoundedRectBrush(rounded, background))
      match data.border {
        Some(border) =>
          if border.width > 0.0 {
            commands.push(
              StrokeRoundedRectBrush(rounded, border.brush, border.width),
            )
          }
        None => ()
      }
      let foreground = environment.foreground.unwrap_or(data.foreground)
      commands.push(
        DrawText(
          TextRun::new(
            text=data.text,
            frame=bounds,
            font=environment_font(environment, data.font),
            color=environment_color(environment, foreground),
          ),
        ),
      )
    }
    TextFieldSpec(data) =>
      append_text_field_commands(
        commands,
        self,
        data,
        bounds,
        focused == Some(self.id),
        environment,
      )
    RichTextEditorSpec(data) =>
      append_rich_text_editor_commands(
        commands,
        self,
        data,
        bounds,
        focused == Some(self.id),
        environment,
      )
    ImageSpec(data) => {
      let frame = match data.size {
        Some(size) =>
          Rect::new(
            x=bounds.origin.x,
            y=bounds.origin.y,
            width=size.width,
            height=size.height,
          )
        None => bounds
      }
      let corner_radius = environment.corner_radius.unwrap_or(
        data.corner_radius,
      )
      if corner_radius > 0.0 {
        commands.push(PushClip(frame))
      }
      commands.push(
        DrawImage({
          source: data.source,
          frame,
          opacity: data.opacity,
          fit: data.fit,
        }),
      )
      if corner_radius > 0.0 {
        commands.push(PopClip)
      }
    }
    CheckboxSpec(data) =>
      append_checkbox_commands(commands, data, bounds, environment)
    PickerSpec(data) =>
      append_picker_commands(commands, self, data, bounds, environment)
    DatePickerSpec(data) =>
      append_datepicker_commands(commands, self, data, bounds, environment)
    SearchBarSpec(data) =>
      append_searchbar_commands(
        commands, self, data, bounds, environment, focused,
      )
    SheetSpec(data) => {
      // Sheet background and shadow
      let rounded = RoundedRect::new(rect=bounds, radius=data.corner_radius)
      match data.shadow {
        Some(shadow) =>
          commands.push(
            DrawShadow(shadow_style_to_spec(shadow, bounds, data.corner_radius)),
          )
        None => ()
      }
      commands.push(FillRoundedRectBrush(rounded, data.background))
      // Render the content child
      if self.children.length() > 0 && placed.children.length() > 0 {
        children.push(
          self.children[0].render_with_environment(
            placed.children[0],
            focused,
            environment,
          ),
        )
      }
    }
    FlexSpec(_) =>
      for index, child in self.children {
        if index < placed.children.length() {
          children.push(
            child.render_with_environment(
              placed.children[index],
              focused,
              environment,
            ),
          )
        }
      }
    PaddingSpec(_) =>
      if self.children.length() > 0 && placed.children.length() > 0 {
        children.push(
          self.children[0].render_with_environment(
            placed.children[0],
            focused,
            environment,
          ),
        )
      }
    ContainerSpec(data) => {
      let frame = match data.size {
        Some(size) =>
          Rect::new(
            x=bounds.origin.x,
            y=bounds.origin.y,
            width=size.width,
            height=size.height,
          )
        None => bounds
      }
      match data.background {
        Some(brush) =>
          if environment.corner_radius.unwrap_or(data.corner_radius) > 0.0 {
            commands.push(
              FillRoundedRectBrush(
                RoundedRect::new(
                  rect=frame,
                  radius=environment.corner_radius.unwrap_or(data.corner_radius),
                ),
                brush,
              ),
            )
          } else {
            commands.push(
              FillRoundedRectBrush(RoundedRect::new(rect=frame), brush),
            )
          }
        None => ()
      }
      match data.shadow {
        Some(shadow) =>
          commands.push(
            DrawShadow(
              shadow_style_to_spec(
                shadow,
                frame,
                environment.corner_radius.unwrap_or(data.corner_radius),
              ),
            ),
          )
        None => ()
      }
      match data.border {
        Some(border) =>
          if border.width > 0.0 {
            commands.push(
              StrokeRoundedRectBrush(
                RoundedRect::new(
                  rect=frame,
                  radius=environment.corner_radius.unwrap_or(data.corner_radius),
                ),
                border.brush,
                border.width,
              ),
            )
          }
        None => ()
      }
      if self.children.length() > 0 && placed.children.length() > 0 {
        children.push(
          self.children[0].render_with_environment(
            placed.children[0],
            focused,
            environment,
          ),
        )
      }
    }
    FrameSpec(_) =>
      if self.children.length() > 0 && placed.children.length() > 0 {
        children.push(
          self.children[0].render_with_environment(
            placed.children[0],
            focused,
            environment,
          ),
        )
      }
    StackSpec(_) =>
      for child in self.children {
        let index = children.length()
        if index < placed.children.length() {
          children.push(
            child.render_with_environment(
              placed.children[index],
              focused,
              environment,
            ),
          )
        }
      }
    ScrollSpec(data) => {
      let viewport = data.viewport.unwrap_or(bounds.size)
      let clip = Rect::new(
        x=bounds.origin.x,
        y=bounds.origin.y,
        width=viewport.width,
        height=viewport.height,
      )
      commands.push(PushClip(clip))
      if self.children.length() > 0 && placed.children.length() > 0 {
        children.push(
          self.children[0].render_with_environment(
            placed.children[0],
            focused,
            environment,
          ),
        )
      }
      post_commands.push(PopClip)
    }
    GridSpec(_) =>
      for index, child in self.children {
        if index < placed.children.length() {
          children.push(
            child.render_with_environment(
              placed.children[index],
              focused,
              environment,
            ),
          )
        }
      }
    ListSpec(_) =>
      for index, child in self.children {
        if index < placed.children.length() {
          children.push(
            child.render_with_environment(
              placed.children[index],
              focused,
              environment,
            ),
          )
        }
      }
    ModifiedSpec(data) =>
      render_modified_node(
        self, data, placed, focused, environment, children, commands, post_commands,
      )
    CustomSpec(data) =>
      for command in (data.paint)(bounds) {
        commands.push(command)
      }
    CustomLayoutSpec(data) => {
      for command in (data.paint)(bounds) {
        commands.push(command)
      }
      for index, child in self.children {
        if index < placed.children.length() {
          children.push(
            child.render_with_environment(
              placed.children[index],
              focused,
              environment,
            ),
          )
        }
      }
    }
    ComponentSpec(_) =>
      if self.children.length() > 0 && placed.children.length() > 0 {
        children.push(
          self.children[0].render_with_environment(
            placed.children[0],
            focused,
            environment,
          ),
        )
      }
  }
  self.dirty.clear()
  let render = {
    element_id: self.id,
    frame: bounds,
    children,
    hit_testable: element_hit_testable(self.spec),
    paint_commands: commands,
    post_paint_commands: post_commands,
  }
  if environment.matches_default_cache_key() {
    self.render_cache.cached_render_frame = Some(bounds)
    self.render_cache.cached_render_focused = focused
    self.render_cache.cached_render = Some(render)
  }
  render
}