// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
fn[C] compute_leaf_layout(
  view : ChicleView[C],
  node_id : NodeId,
  known_dimensions : Size[Double?],
  available_space : Size[AvailableSpace],
  absolute_origin : Point[Double],
) -> Unit raise ChicleError {
  let tree = view.tree
  let node = match tree.nodes.get(node_id) {
    Some(n) => n
    None => raise InvalidNodeId(node_id)
  }
  // CSS compatibility: vertical padding/border percentages are resolved against the width.
  let resolved_padding = @util.resolve_rect_width_basis(
    node.style.padding,
    available_space,
  )
  let resolved_border = @util.resolve_rect_width_basis(
    node.style.border,
    available_space,
  )
  let scrollbar_w = node.style.scrollbar_width
  let scrollbar_x = match node.style.overflow.x {
    OverflowScroll => scrollbar_w
    _ => 0.0
  }
  let scrollbar_y = match node.style.overflow.y {
    OverflowScroll => scrollbar_w
    _ => 0.0
  }
  let horiz_non_scroll_inset = resolved_padding.left +
    resolved_padding.right +
    resolved_border.left +
    resolved_border.right
  let vert_non_scroll_inset = resolved_padding.top +
    resolved_padding.bottom +
    resolved_border.top +
    resolved_border.bottom
  let horiz_inset = horiz_non_scroll_inset + scrollbar_y
  let vert_inset = vert_non_scroll_inset + scrollbar_x
  let mut specified_width = @util.resolve_optional_dimension(
    node.style.size.width,
    available_space.width,
  )
  let mut specified_height = @util.resolve_optional_dimension(
    node.style.size.height,
    available_space.height,
  )
  let known_width = known_dimensions.width
  let known_height = known_dimensions.height

  // aspect-ratio: apply to the node's preferred size (not to known_dimensions).
  match node.style.aspect_ratio {
    Some(ratio) =>
      if ratio > 0.0 {
        match (specified_width, specified_height) {
          (Some(w), None) => specified_height = Some((w / ratio).round())
          (None, Some(h)) => specified_width = Some((h * ratio).round())
          _ => ()
        }
      }
    None => ()
  }
  let mut measure_known_dimensions = Size(
    width=match known_width {
      Some(w) => Some(w)
      None => specified_width
    },
    height=match known_height {
      Some(h) => Some(h)
      None => specified_height
    },
  )
  let resolved_max_width = @util.resolve_optional_dimension(
    node.style.max_size.width,
    available_space.width,
  )
  let resolved_max_height = @util.resolve_optional_dimension(
    node.style.max_size.height,
    available_space.height,
  )
  fn clamp_opt_to_max(v : Double?, max_v : Double?) -> Double? {
    match (v, max_v) {
      (Some(v0), Some(max0)) => Some(if v0 > max0 { max0 } else { v0 })
      _ => v
    }
  }
  measure_known_dimensions = Size(
    width=clamp_opt_to_max(measure_known_dimensions.width, resolved_max_width),
    height=clamp_opt_to_max(
      measure_known_dimensions.height,
      resolved_max_height,
    ),
  )
  let measure_available_space = Size(
    width=match (available_space.width, resolved_max_width) {
      (AvailDefinite(v), Some(max0)) =>
        @geometry.AvailDefinite(if v > max0 { max0 } else { v })
      _ => available_space.width
    },
    height=match (available_space.height, resolved_max_height) {
      (AvailDefinite(v), Some(max0)) =>
        AvailDefinite(if v > max0 { max0 } else { v })
      _ => available_space.height
    },
  )
  let measured = match
    find_leaf_measure_cache(
      node.cache,
      measure_known_dimensions,
      measure_available_space,
      ComputeSize,
    ) {
    Some(output) => output.size
    None => {
      let m = (view.measure_function)(
        measure_known_dimensions,
        measure_available_space,
        node_id,
        tree.node_context_data[node_id],
        node.style,
      )
      store_leaf_measure_cache(
        tree.nodes[node_id].cache,
        measure_known_dimensions,
        measure_available_space,
        ComputeSize,
        m,
      )
      m
    }
  }
  let raw_width = match known_width {
    Some(w) => w
    None =>
      match specified_width {
        Some(w) => w
        None => @util.max_double(measured.width + horiz_inset, horiz_inset)
      }
  }
  let raw_height = match known_height {
    Some(h) => h
    None =>
      match specified_height {
        Some(h) => h
        None => @util.max_double(measured.height + vert_inset, vert_inset)
      }
  }
  let raw_border_box = Size(width=raw_width, height=raw_height)
  let mut border_box_size = Size(
    width=@util.max_double(
      @util.clamp_dimension(
        raw_border_box.width,
        node.style.min_size.width,
        node.style.max_size.width,
        available_space.width,
      ),
      horiz_non_scroll_inset,
    ),
    height=@util.max_double(
      @util.clamp_dimension(
        raw_border_box.height,
        node.style.min_size.height,
        node.style.max_size.height,
        available_space.height,
      ),
      vert_non_scroll_inset,
    ),
  )
  // aspect-ratio: when width is auto and height is constrained by min/max,
  // prefer deriving the width from the clamped height.
  match node.style.aspect_ratio {
    Some(ratio) =>
      if ratio > 0.0 {
        let width_is_auto = known_width is None && specified_width is None
        let height_is_auto = known_height is None && specified_height is None
        let resolved_min_height = @util.resolve_optional_dimension(
          node.style.min_size.height,
          available_space.height,
        )
        let resolved_max_height = @util.resolve_optional_dimension(
          node.style.max_size.height,
          available_space.height,
        )
        let height_is_definite = known_height is Some(_) ||
          specified_height is Some(_) ||
          (match resolved_min_height {
            Some(min_h) => raw_border_box.height <= min_h
            None => false
          }) ||
          (match resolved_max_height {
            Some(max_h) => raw_border_box.height >= max_h
            None => false
          })
        if width_is_auto && height_is_definite {
          let ratio_width = @util.max_double(
            (border_box_size.height * ratio).round(),
            horiz_inset,
          )
          border_box_size = Size(
            width=@util.max_double(
              @util.clamp_dimension(
                ratio_width,
                node.style.min_size.width,
                node.style.max_size.width,
                available_space.width,
              ),
              horiz_inset,
            ),
            height=border_box_size.height,
          )
        } else {
          ()
        }
        if height_is_auto {
          let min_height = @util.max_double(
            (border_box_size.width / ratio).round(),
            vert_inset,
          )
          if min_height > border_box_size.height {
            border_box_size = Size(
              width=border_box_size.width,
              height=min_height,
            )
          } else {
            ()
          }
        } else {
          ()
        }
        border_box_size = Size(
          width=@util.max_double(
            @util.clamp_dimension(
              border_box_size.width,
              node.style.min_size.width,
              node.style.max_size.width,
              available_space.width,
            ),
            horiz_non_scroll_inset,
          ),
          height=@util.max_double(
            @util.clamp_dimension(
              border_box_size.height,
              node.style.min_size.height,
              node.style.max_size.height,
              available_space.height,
            ),
            vert_non_scroll_inset,
          ),
        )
      }
    None => ()
  }
  tree.set_unrounded_layout(node_id, {
    ..Layout::zero(),
    location: absolute_origin,
    size: border_box_size,
    content_size: measured,
    scrollbar_size: Size(width=scrollbar_y, height=scrollbar_x),
    border: resolved_border,
    padding: resolved_padding,
  })
  let resolved_margin = @util.resolve_rect_width_basis(
    node.style.margin,
    available_space,
  )
  set_effective_margin_states(
    tree,
    node_id,
    margin_collapse_state_from(resolved_margin.top),
    margin_collapse_state_from(resolved_margin.bottom),
  )
}