///|
/// Layout modifiers: Padding, Frame, Flexible, Offset, Align, AspectRatio,
/// Spacing, LayoutPriority, MainAxisAlignment, CrossAxisAlignment,
/// IntrinsicWidth, IntrinsicHeight, Key.
pub(all) struct PaddingModifier {
  insets : Insets
}

///|
pub fn[Msg] PaddingModifier::to_any_modifier(
  self : PaddingModifier,
) -> AnyModifier[Msg] {
  let insets = self.insets
  {
    ..noop_modifier(),
    identity_kind: "Padding",
    declaration: ViewDeclaration::channels(
      layout=Exact(
        DeclarationKey::record(type_tag="Padding", fields=[
          ("insets", declaration_insets(insets)),
        ]),
      ),
      paint=Constant,
      semantics=Constant,
      platform=Constant,
    ),
    child_constraints: fn(c) { c.deflate(insets) },
    layout: fn(ctx) {
      let child_size = first_modifier_child_size(ctx.child_sizes)
      let size = ctx.constraints.constrain(child_size.inflate(insets))
      let frame = Rect::new(x=0.0, y=0.0, width=size.width, height=size.height)
      ViewLayoutResult::new(size~, child_frames=[frame.inset(insets)])
    },
  }
}

///|
pub(all) struct FrameModifier {
  width : Double?
  height : Double?
  min_width : Double?
  min_height : Double?
  max_width : Double?
  max_height : Double?
}

///|
fn frame_constraints(data : FrameModifier, c : Constraints) -> Constraints {
  match (data.width, data.height) {
    (Some(w), Some(h)) => c.tighten(width=w, height=h).loosen()
    (Some(w), None) => c.tighten(width=w).loosen()
    (None, Some(h)) => c.tighten(height=h).loosen()
    (None, None) => c.loosen()
  }
}

///|
fn frame_resolve(data : FrameModifier, child : Size) -> Size {
  let w = data.width.unwrap_or(child.width)
  let h = data.height.unwrap_or(child.height)
  let mw = data.min_width.unwrap_or(0.0)
  let mh = data.min_height.unwrap_or(0.0)
  let xw = data.max_width.unwrap_or(w)
  let xh = data.max_height.unwrap_or(h)
  Size::new(width=w, height=h).clamp({
    min: Size::new(width=mw, height=mh),
    max: Size::new(width=max_double(mw, xw), height=max_double(mh, xh)),
  })
}

///|
fn frame_child(data : FrameModifier, bounds : Rect) -> Rect {
  let s = frame_resolve(data, bounds.size)
  Rect::new(
    x=bounds.origin.x,
    y=bounds.origin.y,
    width=s.width,
    height=s.height,
  )
}

///|
pub fn[Msg] FrameModifier::to_any_modifier(
  self : FrameModifier,
) -> AnyModifier[Msg] {
  let data = self
  {
    ..noop_modifier(),
    identity_kind: "Frame",
    declaration: ViewDeclaration::channels(
      layout=Exact(
        DeclarationKey::record(type_tag="FrameModifier", fields=[
          ("width", declaration_option_double(data.width)),
          ("height", declaration_option_double(data.height)),
          ("min_width", declaration_option_double(data.min_width)),
          ("min_height", declaration_option_double(data.min_height)),
          ("max_width", declaration_option_double(data.max_width)),
          ("max_height", declaration_option_double(data.max_height)),
        ]),
      ),
      paint=Constant,
      semantics=Constant,
      platform=Constant,
    ),
    child_constraints: fn(c) { frame_constraints(data, c) },
    layout: fn(ctx) {
      let cs = first_modifier_child_size(ctx.child_sizes)
      let s = ctx.constraints.constrain(frame_resolve(data, cs))
      let cf = frame_child(
        data,
        Rect::new(x=0.0, y=0.0, width=s.width, height=s.height),
      )
      ViewLayoutResult::new(size=s, child_frames=[cf])
    },
  }
}

///|
pub(all) struct FlexibleModifier {
  weight : Double
}

///|
pub fn[Msg] FlexibleModifier::to_any_modifier(
  self : FlexibleModifier,
) -> AnyModifier[Msg] {
  {
    ..noop_modifier(),
    identity_kind: "Flexible",
    flex_weight: self.weight,
    layout: fn(ctx) {
      // Weighted children claim no main-axis base (the flex engine grants
      // slack by weight), but their cross-axis size still comes from the
      // content and the placement pass must hand the granted frame down to
      // the child. Returning a 0x0 child frame here collapsed every
      // `expanded`/`flexible` subtree (playground blank-editor bug).
      let child_size = match ctx.child_sizes {
        [s, ..] => s
        [] => Size::new(width=0.0, height=0.0)
      }
      let s = ctx.constraints.constrain(child_size)
      ViewLayoutResult::new(size=s, child_frames=[
        Rect::new(x=0.0, y=0.0, width=s.width, height=s.height),
      ])
    },
  }
}

///|
pub(all) struct OffsetModifier {
  dx : Double
  dy : Double
}

///|
pub fn[Msg] OffsetModifier::to_any_modifier(
  self : OffsetModifier,
) -> AnyModifier[Msg] {
  let dx = self.dx
  let dy = self.dy
  {
    ..noop_modifier(),
    identity_kind: "Offset",
    declaration: ViewDeclaration::channels(
      layout=Exact(
        DeclarationKey::record(type_tag="Offset", fields=[
          ("dx", DeclarationKey::double(dx)),
          ("dy", DeclarationKey::double(dy)),
        ]),
      ),
      paint=Constant,
      semantics=Constant,
      platform=Constant,
    ),
    layout: fn(ctx) {
      let cs = first_modifier_child_size(ctx.child_sizes)
      let s = ctx.constraints.constrain(cs)
      ViewLayoutResult::new(size=s, child_frames=[
        Rect::new(x=0.0, y=0.0, width=s.width, height=s.height).offset(dx~, dy~),
      ])
    },
  }
}

///|
pub(all) struct AlignModifier {
  alignment : Alignment
}

///|
fn mod_aligned_rect(bounds : Rect, cs : Size, a : Alignment) -> Rect {
  let w = min_double(bounds.size.width, cs.width)
  let h = min_double(bounds.size.height, cs.height)
  let dx = max_double(0.0, bounds.size.width - w) * (a.x + 1.0) / 2.0
  let dy = max_double(0.0, bounds.size.height - h) * (a.y + 1.0) / 2.0
  Rect::new(x=bounds.origin.x + dx, y=bounds.origin.y + dy, width=w, height=h)
}

///|
pub fn[Msg] AlignModifier::to_any_modifier(
  self : AlignModifier,
) -> AnyModifier[Msg] {
  let a = self.alignment
  {
    ..noop_modifier(),
    identity_kind: "Align",
    layout: fn(ctx) {
      let cs = first_modifier_child_size(ctx.child_sizes)
      let s = ctx.constraints.constrain(cs)
      ViewLayoutResult::new(size=s, child_frames=[
        mod_aligned_rect(
          Rect::new(x=0.0, y=0.0, width=s.width, height=s.height),
          cs,
          a,
        ),
      ])
    },
  }
}

///|
pub(all) struct AspectRatioModifier {
  ratio : Double
}

///|
fn mod_ar_size(s : Size, r : Double) -> Size {
  if s.width > 0.0 {
    Size::new(width=s.width, height=s.width / r)
  } else if s.height > 0.0 {
    Size::new(width=s.height * r, height=s.height)
  } else {
    s
  }
}

///|
fn mod_ar_rect(f : Rect, r : Double) -> Rect {
  let s = mod_ar_size(f.size, r).clamp(Constraints::tight(f.size))
  Rect::new(x=f.origin.x, y=f.origin.y, width=s.width, height=s.height)
}

///|
pub fn[Msg] AspectRatioModifier::to_any_modifier(
  self : AspectRatioModifier,
) -> AnyModifier[Msg] {
  let r = self.ratio
  {
    ..noop_modifier(),
    identity_kind: "AspectRatio",
    layout: fn(ctx) {
      let cs = first_modifier_child_size(ctx.child_sizes)
      let s = ctx.constraints.constrain(mod_ar_size(cs, r))
      ViewLayoutResult::new(size=s, child_frames=[
        mod_ar_rect(Rect::new(x=0.0, y=0.0, width=s.width, height=s.height), r),
      ])
    },
  }
}

///|
pub(all) struct SpacingModifier {
  spacing : Double
}

///|
pub fn[Msg] SpacingModifier::to_any_modifier(
  _self : SpacingModifier,
) -> AnyModifier[Msg] {
  { ..noop_modifier(), identity_kind: "Spacing" }
}

///|
pub(all) struct LayoutPriorityModifier {
  priority : Double
}

///|
pub fn[Msg] LayoutPriorityModifier::to_any_modifier(
  _self : LayoutPriorityModifier,
) -> AnyModifier[Msg] {
  { ..noop_modifier(), identity_kind: "LayoutPriority" }
}

///|
pub(all) struct MainAxisAlignmentModifier {
  alignment : MainAxisAlignment
}

///|
pub fn[Msg] MainAxisAlignmentModifier::to_any_modifier(
  _self : MainAxisAlignmentModifier,
) -> AnyModifier[Msg] {
  { ..noop_modifier(), identity_kind: "MainAxisAlignment" }
}

///|
pub(all) struct CrossAxisAlignmentModifier {
  alignment : CrossAxisAlignment
}

///|
pub fn[Msg] CrossAxisAlignmentModifier::to_any_modifier(
  _self : CrossAxisAlignmentModifier,
) -> AnyModifier[Msg] {
  { ..noop_modifier(), identity_kind: "CrossAxisAlignment" }
}

///|
pub(all) struct IntrinsicWidthModifier {}

///|
pub fn[Msg] IntrinsicWidthModifier::to_any_modifier(
  _self : IntrinsicWidthModifier,
) -> AnyModifier[Msg] {
  { ..noop_modifier(), identity_kind: "IntrinsicWidth" }
}

///|
pub(all) struct IntrinsicHeightModifier {}

///|
pub fn[Msg] IntrinsicHeightModifier::to_any_modifier(
  _self : IntrinsicHeightModifier,
) -> AnyModifier[Msg] {
  { ..noop_modifier(), identity_kind: "IntrinsicHeight" }
}

///|
pub(all) struct KeyModifier {
  key : Key
}

///|
pub fn[Msg] KeyModifier::to_any_modifier(
  self : KeyModifier,
) -> AnyModifier[Msg] {
  let key = self.key
  { ..noop_modifier(), identity_kind: "Key", identity_key: Some(key) }
}