/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use std::cell::{LazyCell, OnceCell};
use std::convert::From;
use std::fmt;
use std::ops::{Add, AddAssign, Neg, Sub, SubAssign};

use app_units::{Au, MAX_AU};
use malloc_size_of_derive::MallocSizeOf;
use style::Zero;
use style::logical_geometry::{BlockFlowDirection, Direction, InlineBaseDirection, WritingMode};
use style::values::computed::{
    CSSPixelLength, LengthPercentage, MaxSize as StyleMaxSize, Percentage, Size as StyleSize,
};
use style::values::generics::length::GenericLengthPercentageOrAuto as AutoOr;
use style_traits::CSSPixel;

use crate::ContainingBlock;
use crate::sizing::ContentSizes;
use crate::style_ext::Clamp;

pub type PhysicalPoint<U> = euclid::Point2D<U, CSSPixel>;
pub type PhysicalSize<U> = euclid::Size2D<U, CSSPixel>;
pub type PhysicalVec<U> = euclid::Vector2D<U, CSSPixel>;
pub type PhysicalRect<U> = euclid::Rect<U, CSSPixel>;
pub type PhysicalSides<U> = euclid::SideOffsets2D<U, CSSPixel>;
pub type AuOrAuto = AutoOr<Au>;
pub type LengthPercentageOrAuto<'a> = AutoOr<&'a LengthPercentage>;

#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
pub struct LogicalVec2<T> {
    pub inline: T,
    pub block: T,
}

#[derive(Clone, Copy)]
pub struct LogicalRect<T> {
    pub start_corner: LogicalVec2<T>,
    pub size: LogicalVec2<T>,
}

#[derive(Clone, Copy, Debug, Default)]
pub struct LogicalSides<T> {
    pub inline_start: T,
    pub inline_end: T,
    pub block_start: T,
    pub block_end: T,
}

#[derive(Clone, Copy, Debug)]
pub(crate) struct LogicalSides1D<T> {
    pub start: T,
    pub end: T,
}

impl<T: fmt::Debug> fmt::Debug for LogicalVec2<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // Not using f.debug_struct on purpose here, to keep {:?} output somewhat compact
        f.write_str("Vec2 { i: ")?;
        self.inline.fmt(f)?;
        f.write_str(", b: ")?;
        self.block.fmt(f)?;
        f.write_str(" }")
    }
}

impl<T: Default> Default for LogicalVec2<T> {
    fn default() -> Self {
        Self {
            inline: T::default(),
            block: T::default(),
        }
    }
}

impl<T: Copy> From<T> for LogicalVec2<T> {
    fn from(value: T) -> Self {
        Self {
            inline: value,
            block: value,
        }
    }
}

impl<T> LogicalVec2<T> {
    pub fn map_inline_and_block_axes<U>(
        &self,
        inline_f: impl FnOnce(&T) -> U,
        block_f: impl FnOnce(&T) -> U,
    ) -> LogicalVec2<U> {
        LogicalVec2 {
            inline: inline_f(&self.inline),
            block: block_f(&self.block),
        }
    }
}

impl<T: Clone> LogicalVec2<Size<T>> {
    pub fn map_inline_and_block_sizes<U>(
        &self,
        inline_f: impl FnOnce(T) -> U,
        block_f: impl FnOnce(T) -> U,
    ) -> LogicalVec2<Size<U>> {
        self.map_inline_and_block_axes(|size| size.map(inline_f), |size| size.map(block_f))
    }
}

impl<T: Clone> LogicalVec2<T> {
    pub fn from_physical_size(physical_size: &PhysicalSize<T>, mode: WritingMode) -> Self {
        // https://drafts.csswg.org/css-writing-modes/#logical-to-physical
        let (i, b) = if mode.is_horizontal() {
            (&physical_size.width, &physical_size.height)
        } else {
            (&physical_size.height, &physical_size.width)
        };
        LogicalVec2 {
            inline: i.clone(),
            block: b.clone(),
        }
    }

    pub fn map<U>(&self, f: impl Fn(&T) -> U) -> LogicalVec2<U> {
        LogicalVec2 {
            inline: f(&self.inline),
            block: f(&self.block),
        }
    }

    pub(crate) fn map_with<U, V>(
        &self,
        other: &LogicalVec2<U>,
        f: impl Fn(&T, &U) -> V,
    ) -> LogicalVec2<V> {
        LogicalVec2 {
            inline: f(&self.inline, &other.inline),
            block: f(&self.block, &other.block),
        }
    }
}

impl<T: Add<Output = T> + Copy> Add<LogicalVec2<T>> for LogicalVec2<T> {
    type Output = LogicalVec2<T>;
    fn add(self, other: Self) -> Self::Output {
        LogicalVec2 {
            inline: self.inline + other.inline,
            block: self.block + other.block,
        }
    }
}

impl<T: Sub<Output = T> + Copy> Sub<LogicalVec2<T>> for LogicalVec2<T> {
    type Output = LogicalVec2<T>;
    fn sub(self, other: Self) -> Self::Output {
        LogicalVec2 {
            inline: self.inline - other.inline,
            block: self.block - other.block,
        }
    }
}

impl<T: AddAssign<T> + Copy> AddAssign<LogicalVec2<T>> for LogicalVec2<T> {
    fn add_assign(&mut self, other: LogicalVec2<T>) {
        self.inline += other.inline;
        self.block += other.block;
    }
}

impl<T: SubAssign<T> + Copy> SubAssign<LogicalVec2<T>> for LogicalVec2<T> {
    fn sub_assign(&mut self, other: LogicalVec2<T>) {
        self.inline -= other.inline;
        self.block -= other.block;
    }
}

impl<T: Neg<Output = T> + Copy> Neg for LogicalVec2<T> {
    type Output = LogicalVec2<T>;
    fn neg(self) -> Self::Output {
        Self {
            inline: -self.inline,
            block: -self.block,
        }
    }
}

impl<T: Zero> LogicalVec2<T> {
    pub fn zero() -> Self {
        Self {
            inline: T::zero(),
            block: T::zero(),
        }
    }
}

impl<T: Clone> LogicalVec2<AutoOr<T>> {
    pub fn auto_is(&self, f: impl Fn() -> T) -> LogicalVec2<T> {
        self.map(|t| t.auto_is(&f))
    }
}

impl<T: Zero> LogicalRect<T> {
    pub fn zero() -> Self {
        Self {
            start_corner: LogicalVec2::zero(),
            size: LogicalVec2::zero(),
        }
    }
}

impl fmt::Debug for LogicalRect<Au> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "Rect(i{}×b{} @ (i{},b{}))",
            self.size.inline.to_f32_px(),
            self.size.block.to_f32_px(),
            self.start_corner.inline.to_f32_px(),
            self.start_corner.block.to_f32_px(),
        )
    }
}

impl<T: Clone> LogicalVec2<T> {
    pub fn to_physical_size(&self, mode: WritingMode) -> PhysicalSize<T> {
        // https://drafts.csswg.org/css-writing-modes/#logical-to-physical
        let (x, y) = if mode.is_horizontal() {
            (&self.inline, &self.block)
        } else {
            (&self.block, &self.inline)
        };
        PhysicalSize::new(x.clone(), y.clone())
    }
}

impl<T: Copy + Neg<Output = T>> LogicalVec2<T> {
    pub fn to_physical_vector(&self, mode: WritingMode) -> PhysicalVec<T> {
        if mode.is_horizontal() {
            if mode.is_bidi_ltr() {
                PhysicalVec::new(self.inline, self.block)
            } else {
                PhysicalVec::new(-self.inline, self.block)
            }
        } else if mode.is_inline_tb() {
            PhysicalVec::new(self.block, self.inline)
        } else {
            PhysicalVec::new(-self.block, self.inline)
        }
    }
}

impl<T: Clone> LogicalSides<T> {
    pub fn from_physical(sides: &PhysicalSides<T>, mode: WritingMode) -> Self {
        // https://drafts.csswg.org/css-writing-modes/#logical-to-physical
        let block_flow = mode.block_flow_direction();
        let (bs, be) = match mode.block_flow_direction() {
            BlockFlowDirection::TopToBottom => (&sides.top, &sides.bottom),
            BlockFlowDirection::RightToLeft => (&sides.right, &sides.left),
            BlockFlowDirection::LeftToRight => (&sides.left, &sides.right),
        };
        use BlockFlowDirection::TopToBottom;
        let (is, ie) = match (block_flow, mode.inline_base_direction()) {
            (TopToBottom, InlineBaseDirection::LeftToRight) => (&sides.left, &sides.right),
            (TopToBottom, InlineBaseDirection::RightToLeft) => (&sides.right, &sides.left),
            (_, InlineBaseDirection::LeftToRight) => (&sides.top, &sides.bottom),
            (_, InlineBaseDirection::RightToLeft) => (&sides.bottom, &sides.top),
        };
        LogicalSides {
            inline_start: is.clone(),
            inline_end: ie.clone(),
            block_start: bs.clone(),
            block_end: be.clone(),
        }
    }
}

impl<T> LogicalSides<T> {
    pub fn map<U>(&self, f: impl Fn(&T) -> U) -> LogicalSides<U> {
        LogicalSides {
            inline_start: f(&self.inline_start),
            inline_end: f(&self.inline_end),
            block_start: f(&self.block_start),
            block_end: f(&self.block_end),
        }
    }

    pub fn map_inline_and_block_axes<U>(
        &self,
        inline_f: impl Fn(&T) -> U,
        block_f: impl Fn(&T) -> U,
    ) -> LogicalSides<U> {
        LogicalSides {
            inline_start: inline_f(&self.inline_start),
            inline_end: inline_f(&self.inline_end),
            block_start: block_f(&self.block_start),
            block_end: block_f(&self.block_end),
        }
    }

    pub fn inline_sum(&self) -> T::Output
    where
        T: Add + Copy,
    {
        self.inline_start + self.inline_end
    }

    pub fn block_sum(&self) -> T::Output
    where
        T: Add + Copy,
    {
        self.block_start + self.block_end
    }

    pub fn sum(&self) -> LogicalVec2<T::Output>
    where
        T: Add + Copy,
    {
        LogicalVec2 {
            inline: self.inline_sum(),
            block: self.block_sum(),
        }
    }

    pub fn to_physical(&self, mode: WritingMode) -> PhysicalSides<T>
    where
        T: Clone,
    {
        let top;
        let right;
        let bottom;
        let left;
        if mode.is_vertical() {
            if mode.is_vertical_lr() {
                left = self.block_start.clone();
                right = self.block_end.clone();
            } else {
                right = self.block_start.clone();
                left = self.block_end.clone();
            }

            if mode.is_inline_tb() {
                top = self.inline_start.clone();
                bottom = self.inline_end.clone();
            } else {
                bottom = self.inline_start.clone();
                top = self.inline_end.clone();
            }
        } else {
            top = self.block_start.clone();
            bottom = self.block_end.clone();
            if mode.is_bidi_ltr() {
                left = self.inline_start.clone();
                right = self.inline_end.clone();
            } else {
                right = self.inline_start.clone();
                left = self.inline_end.clone();
            }
        }
        PhysicalSides::new(top, right, bottom, left)
    }
}

impl<T: Copy> LogicalSides<T> {
    pub fn start_offset(&self) -> LogicalVec2<T> {
        LogicalVec2 {
            inline: self.inline_start,
            block: self.block_start,
        }
    }

    #[inline]
    pub(crate) fn inline_sides(&self) -> LogicalSides1D<T> {
        LogicalSides1D::new(self.inline_start, self.inline_end)
    }

    #[inline]
    pub(crate) fn block_sides(&self) -> LogicalSides1D<T> {
        LogicalSides1D::new(self.block_start, self.block_end)
    }
}

impl LogicalSides<LengthPercentage> {
    pub fn percentages_relative_to(&self, basis: Au) -> LogicalSides<Au> {
        self.map(|value| value.to_used_value(basis))
    }
}

impl LogicalSides<LengthPercentageOrAuto<'_>> {
    pub fn percentages_relative_to(&self, basis: Au) -> LogicalSides<AuOrAuto> {
        self.map(|value| value.map(|value| value.to_used_value(basis)))
    }
}

impl<T: Clone> LogicalSides<AutoOr<T>> {
    pub fn auto_is(&self, f: impl Fn() -> T) -> LogicalSides<T> {
        self.map(|s| s.auto_is(&f))
    }
}

impl<T: Add<Output = T> + Copy> Add<LogicalSides<T>> for LogicalSides<T> {
    type Output = LogicalSides<T>;

    fn add(self, other: Self) -> Self::Output {
        LogicalSides {
            inline_start: self.inline_start + other.inline_start,
            inline_end: self.inline_end + other.inline_end,
            block_start: self.block_start + other.block_start,
            block_end: self.block_end + other.block_end,
        }
    }
}

impl<T: Sub<Output = T> + Copy> Sub<LogicalSides<T>> for LogicalSides<T> {
    type Output = LogicalSides<T>;

    fn sub(self, other: Self) -> Self::Output {
        LogicalSides {
            inline_start: self.inline_start - other.inline_start,
            inline_end: self.inline_end - other.inline_end,
            block_start: self.block_start - other.block_start,
            block_end: self.block_end - other.block_end,
        }
    }
}

impl<T: Neg<Output = T> + Copy> Neg for LogicalSides<T> {
    type Output = LogicalSides<T>;
    fn neg(self) -> Self::Output {
        Self {
            inline_start: -self.inline_start,
            inline_end: -self.inline_end,
            block_start: -self.block_start,
            block_end: -self.block_end,
        }
    }
}

impl<T: Zero> LogicalSides<T> {
    pub(crate) fn zero() -> LogicalSides<T> {
        Self {
            inline_start: T::zero(),
            inline_end: T::zero(),
            block_start: T::zero(),
            block_end: T::zero(),
        }
    }
}

impl From<LogicalSides<CSSPixelLength>> for LogicalSides<Au> {
    fn from(value: LogicalSides<CSSPixelLength>) -> Self {
        Self {
            inline_start: value.inline_start.into(),
            inline_end: value.inline_end.into(),
            block_start: value.block_start.into(),
            block_end: value.block_end.into(),
        }
    }
}

impl From<LogicalSides<Au>> for LogicalSides<CSSPixelLength> {
    fn from(value: LogicalSides<Au>) -> Self {
        Self {
            inline_start: value.inline_start.into(),
            inline_end: value.inline_end.into(),
            block_start: value.block_start.into(),
            block_end: value.block_end.into(),
        }
    }
}

impl<T> LogicalSides1D<T> {
    #[inline]
    pub(crate) fn new(start: T, end: T) -> Self {
        Self { start, end }
    }
}

impl<T> LogicalSides1D<AutoOr<T>> {
    #[inline]
    pub(crate) fn either_specified(&self) -> bool {
        !self.start.is_auto() || !self.end.is_auto()
    }

    #[inline]
    pub(crate) fn either_auto(&self) -> bool {
        self.start.is_auto() || self.end.is_auto()
    }
}

impl<T: Add + Copy> LogicalSides1D<T> {
    #[inline]
    pub(crate) fn sum(&self) -> T::Output {
        self.start + self.end
    }
}

impl<T> LogicalRect<T> {
    pub fn max_inline_position(&self) -> T
    where
        T: Add<Output = T> + Copy,
    {
        self.start_corner.inline + self.size.inline
    }

    pub fn max_block_position(&self) -> T
    where
        T: Add<Output = T> + Copy,
    {
        self.start_corner.block + self.size.block
    }

    pub fn inflate(&self, sides: &LogicalSides<T>) -> Self
    where
        T: Add<Output = T> + Copy,
        T: Sub<Output = T> + Copy,
    {
        Self {
            start_corner: LogicalVec2 {
                inline: self.start_corner.inline - sides.inline_start,
                block: self.start_corner.block - sides.block_start,
            },
            size: LogicalVec2 {
                inline: self.size.inline + sides.inline_sum(),
                block: self.size.block + sides.block_sum(),
            },
        }
    }

    pub fn deflate(&self, sides: &LogicalSides<T>) -> Self
    where
        T: Add<Output = T> + Copy,
        T: Sub<Output = T> + Copy,
    {
        LogicalRect {
            start_corner: LogicalVec2 {
                inline: self.start_corner.inline + sides.inline_start,
                block: self.start_corner.block + sides.block_start,
            },
            size: LogicalVec2 {
                inline: self.size.inline - sides.inline_sum(),
                block: self.size.block - sides.block_sum(),
            },
        }
    }
}

impl LogicalRect<Au> {
    pub(crate) fn as_physical(
        &self,
        containing_block: Option<&ContainingBlock<'_>>,
    ) -> PhysicalRect<Au> {
        let mode = containing_block.map_or_else(WritingMode::horizontal_tb, |containing_block| {
            containing_block.style.writing_mode
        });
        let (x, y, width, height) = if mode.is_vertical() {
            // TODO: Bottom-to-top writing modes are not supported.
            (
                self.start_corner.block,
                self.start_corner.inline,
                self.size.block,
                self.size.inline,
            )
        } else {
            let y = self.start_corner.block;
            let x = match containing_block {
                Some(containing_block) if !mode.is_bidi_ltr() => {
                    containing_block.size.inline - self.max_inline_position()
                },
                _ => self.start_corner.inline,
            };
            (x, y, self.size.inline, self.size.block)
        };

        PhysicalRect::new(PhysicalPoint::new(x, y), PhysicalSize::new(width, height))
    }
}

impl From<LogicalVec2<CSSPixelLength>> for LogicalVec2<Au> {
    fn from(value: LogicalVec2<CSSPixelLength>) -> Self {
        LogicalVec2 {
            inline: value.inline.into(),
            block: value.block.into(),
        }
    }
}

impl From<LogicalVec2<Au>> for LogicalVec2<CSSPixelLength> {
    fn from(value: LogicalVec2<Au>) -> Self {
        LogicalVec2 {
            inline: value.inline.into(),
            block: value.block.into(),
        }
    }
}

impl From<LogicalRect<Au>> for LogicalRect<CSSPixelLength> {
    fn from(value: LogicalRect<Au>) -> Self {
        LogicalRect {
            start_corner: value.start_corner.into(),
            size: value.size.into(),
        }
    }
}

impl From<LogicalRect<CSSPixelLength>> for LogicalRect<Au> {
    fn from(value: LogicalRect<CSSPixelLength>) -> Self {
        LogicalRect {
            start_corner: value.start_corner.into(),
            size: value.size.into(),
        }
    }
}

pub(crate) trait ToLogical<Unit, LogicalType> {
    fn to_logical(&self, writing_mode: WritingMode) -> LogicalType;
}

impl<Unit: Copy> ToLogical<Unit, LogicalVec2<Unit>> for PhysicalSize<Unit> {
    fn to_logical(&self, writing_mode: WritingMode) -> LogicalVec2<Unit> {
        LogicalVec2::from_physical_size(self, writing_mode)
    }
}

impl<Unit: Copy> ToLogical<Unit, LogicalSides<Unit>> for PhysicalSides<Unit> {
    fn to_logical(&self, writing_mode: WritingMode) -> LogicalSides<Unit> {
        LogicalSides::from_physical(self, writing_mode)
    }
}

pub(crate) trait ToLogicalWithContainingBlock<LogicalType> {
    fn to_logical(&self, containing_block: &ContainingBlock) -> LogicalType;
}

impl ToLogicalWithContainingBlock<LogicalVec2<Au>> for PhysicalPoint<Au> {
    fn to_logical(&self, containing_block: &ContainingBlock) -> LogicalVec2<Au> {
        let writing_mode = containing_block.style.writing_mode;
        // TODO: Bottom-to-top and right-to-left vertical writing modes are not supported yet.
        if writing_mode.is_vertical() {
            LogicalVec2 {
                inline: self.y,
                block: self.x,
            }
        } else {
            LogicalVec2 {
                inline: if writing_mode.is_bidi_ltr() {
                    self.x
                } else {
                    containing_block.size.inline - self.x
                },
                block: self.y,
            }
        }
    }
}

impl ToLogicalWithContainingBlock<LogicalRect<Au>> for PhysicalRect<Au> {
    fn to_logical(&self, containing_block: &ContainingBlock) -> LogicalRect<Au> {
        let inline_start;
        let block_start;
        let inline;
        let block;

        let writing_mode = containing_block.style.writing_mode;
        if writing_mode.is_vertical() {
            // TODO: Bottom-to-top and right-to-left vertical writing modes are not supported yet.
            inline = self.size.height;
            block = self.size.width;
            block_start = self.origin.x;
            inline_start = self.origin.y;
        } else {
            inline = self.size.width;
            block = self.size.height;
            block_start = self.origin.y;
            if writing_mode.is_bidi_ltr() {
                inline_start = self.origin.x;
            } else {
                inline_start = containing_block.size.inline - (self.origin.x + self.size.width);
            }
        }
        LogicalRect {
            start_corner: LogicalVec2 {
                inline: inline_start,
                block: block_start,
            },
            size: LogicalVec2 { inline, block },
        }
    }
}

/// The possible values accepted by the sizing properties.
/// <https://drafts.csswg.org/css-sizing/#sizing-properties>
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Size<T> {
    /// Represents an `auto` value for the preferred and minimum size properties,
    /// or `none` for the maximum size properties.
    /// <https://drafts.csswg.org/css-sizing/#valdef-width-auto>
    /// <https://drafts.csswg.org/css-sizing/#valdef-max-width-none>
    Initial,
    /// <https://drafts.csswg.org/css-sizing/#valdef-width-min-content>
    MinContent,
    /// <https://drafts.csswg.org/css-sizing/#valdef-width-max-content>
    MaxContent,
    /// <https://drafts.csswg.org/css-sizing-4/#valdef-width-fit-content>
    FitContent,
    /// <https://drafts.csswg.org/css-sizing-3/#funcdef-width-fit-content>
    FitContentFunction(T),
    /// <https://drafts.csswg.org/css-sizing-4/#valdef-width-stretch>
    Stretch,
    /// Represents a numeric `<length-percentage>`, but resolved as a `T`.
    /// <https://drafts.csswg.org/css-sizing/#valdef-width-length-percentage-0>
    Numeric(T),
}

impl<T: Copy> Copy for Size<T> {}

impl<T> Default for Size<T> {
    #[inline]
    fn default() -> Self {
        Self::Initial
    }
}

impl<T> Size<T> {
    #[inline]
    pub(crate) fn is_initial(&self) -> bool {
        matches!(self, Self::Initial)
    }
}

impl<T: Clone> Size<T> {
    #[inline]
    pub(crate) fn to_numeric(&self) -> Option<T> {
        match self {
            Self::Numeric(numeric) => Some(numeric).cloned(),
            _ => None,
        }
    }

    #[inline]
    pub(crate) fn map<U>(&self, f: impl FnOnce(T) -> U) -> Size<U> {
        match self {
            Size::Initial => Size::Initial,
            Size::MinContent => Size::MinContent,
            Size::MaxContent => Size::MaxContent,
            Size::FitContent => Size::FitContent,
            Size::FitContentFunction(size) => Size::FitContentFunction(f(size.clone())),
            Size::Stretch => Size::Stretch,
            Size::Numeric(numeric) => Size::Numeric(f(numeric.clone())),
        }
    }
}

impl From<StyleSize> for Size<LengthPercentage> {
    fn from(size: StyleSize) -> Self {
        match size {
            StyleSize::LengthPercentage(lp) => Size::Numeric(lp.0),
            StyleSize::Auto => Size::Initial,
            StyleSize::MinContent => Size::MinContent,
            StyleSize::MaxContent => Size::MaxContent,
            StyleSize::FitContent => Size::FitContent,
            StyleSize::FitContentFunction(lp) => Size::FitContentFunction(lp.0),
            StyleSize::Stretch => Size::Stretch,
            StyleSize::AnchorSizeFunction(_) | StyleSize::AnchorContainingCalcFunction(_) => {
                unreachable!("anchor-size() should be disabled")
            },
        }
    }
}

impl From<StyleMaxSize> for Size<LengthPercentage> {
    fn from(max_size: StyleMaxSize) -> Self {
        match max_size {
            StyleMaxSize::LengthPercentage(lp) => Size::Numeric(lp.0),
            StyleMaxSize::None => Size::Initial,
            StyleMaxSize::MinContent => Size::MinContent,
            StyleMaxSize::MaxContent => Size::MaxContent,
            StyleMaxSize::FitContent => Size::FitContent,
            StyleMaxSize::FitContentFunction(lp) => Size::FitContentFunction(lp.0),
            StyleMaxSize::Stretch => Size::Stretch,
            StyleMaxSize::AnchorSizeFunction(_) | StyleMaxSize::AnchorContainingCalcFunction(_) => {
                unreachable!("anchor-size() should be disabled")
            },
        }
    }
}

impl Size<LengthPercentage> {
    #[inline]
    pub(crate) fn to_percentage(&self) -> Option<Percentage> {
        self.to_numeric()
            .and_then(|length_percentage| length_percentage.to_percentage())
    }

    /// Resolves percentages in a preferred size, against the provided basis.
    /// If the basis is missing, percentages are considered cyclic.
    /// <https://www.w3.org/TR/css-sizing-3/#preferred-size-properties>
    /// <https://www.w3.org/TR/css-sizing-3/#cyclic-percentage-size>
    #[inline]
    pub(crate) fn resolve_percentages_for_preferred(&self, basis: Option<Au>) -> Size<Au> {
        match self {
            Size::Numeric(numeric) => numeric
                .maybe_to_used_value(basis)
                .map_or(Size::Initial, Size::Numeric),
            Size::FitContentFunction(numeric) => {
                // Under discussion in https://github.com/w3c/csswg-drafts/issues/11805
                numeric
                    .maybe_to_used_value(basis)
                    .map_or(Size::FitContent, Size::FitContentFunction)
            },
            _ => self.map(|_| unreachable!("This shouldn't be called for keywords")),
        }
    }

    /// Resolves percentages in a maximum size, against the provided basis.
    /// If the basis is missing, percentages are considered cyclic.
    /// <https://www.w3.org/TR/css-sizing-3/#preferred-size-properties>
    /// <https://www.w3.org/TR/css-sizing-3/#cyclic-percentage-size>
    #[inline]
    pub(crate) fn resolve_percentages_for_max(&self, basis: Option<Au>) -> Size<Au> {
        match self {
            Size::Numeric(numeric) => numeric
                .maybe_to_used_value(basis)
                .map_or(Size::Initial, Size::Numeric),
            Size::FitContentFunction(numeric) => {
                // Under discussion in https://github.com/w3c/csswg-drafts/issues/11805
                numeric
                    .maybe_to_used_value(basis)
                    .map_or(Size::MaxContent, Size::FitContentFunction)
            },
            _ => self.map(|_| unreachable!("This shouldn't be called for keywords")),
        }
    }
}

impl LogicalVec2<Size<LengthPercentage>> {
    pub(crate) fn percentages_relative_to_basis(
        &self,
        basis: &LogicalVec2<Au>,
    ) -> LogicalVec2<Size<Au>> {
        LogicalVec2 {
            inline: self.inline.map(|value| value.to_used_value(basis.inline)),
            block: self.block.map(|value| value.to_used_value(basis.block)),
        }
    }
}

impl Size<Au> {
    /// Resolves a preferred size into a numerical value.
    /// <https://www.w3.org/TR/css-sizing-3/#preferred-size-properties>
    #[inline]
    pub(crate) fn resolve_for_preferred<F: FnOnce() -> ContentSizes>(
        &self,
        automatic_size: Size<Au>,
        stretch_size: Option<Au>,
        content_size: &LazyCell<ContentSizes, F>,
    ) -> Au {
        match self {
            Self::Initial => {
                assert!(!automatic_size.is_initial());
                automatic_size.resolve_for_preferred(automatic_size, stretch_size, content_size)
            },
            Self::MinContent => content_size.min_content,
            Self::MaxContent => content_size.max_content,
            Self::FitContentFunction(size) => content_size.shrink_to_fit(*size),
            Self::FitContent => {
                content_size.shrink_to_fit(stretch_size.unwrap_or_else(|| content_size.max_content))
            },
            Self::Stretch => stretch_size.unwrap_or_else(|| content_size.max_content),
            Self::Numeric(numeric) => *numeric,
        }
    }

    /// Resolves a minimum size into a numerical value.
    /// <https://www.w3.org/TR/css-sizing-3/#min-size-properties>
    #[inline]
    pub(crate) fn resolve_for_min<F: FnOnce() -> ContentSizes>(
        &self,
        get_automatic_minimum_size: impl FnOnce() -> Au,
        stretch_size: Option<Au>,
        content_size: &LazyCell<ContentSizes, F>,
        is_table: bool,
    ) -> Au {
        let result = match self {
            Self::Initial => get_automatic_minimum_size(),
            Self::MinContent => content_size.min_content,
            Self::MaxContent => content_size.max_content,
            Self::FitContentFunction(size) => content_size.shrink_to_fit(*size),
            Self::FitContent => content_size.shrink_to_fit(stretch_size.unwrap_or_default()),
            Self::Stretch => stretch_size.unwrap_or_default(),
            Self::Numeric(numeric) => *numeric,
        };
        if is_table {
            // In addition to the specified minimum, the inline size of a table is forced to be
            // at least as big as its min-content size.
            //
            // Note that if there are collapsed columns, only the inline size of the table grid will
            // shrink, while the size of the table wrapper (being computed here) won't be affected.
            // However, collapsed rows should typically affect the block size of the table wrapper,
            // so it might be wrong to use this function for that case.
            // This is being discussed in https://github.com/w3c/csswg-drafts/issues/11408
            result.max(content_size.min_content)
        } else {
            result
        }
    }

    /// Resolves a maximum size into a numerical value.
    /// <https://www.w3.org/TR/css-sizing-3/#max-size-properties>
    #[inline]
    pub(crate) fn resolve_for_max<F: FnOnce() -> ContentSizes>(
        &self,
        stretch_size: Option<Au>,
        content_size: &LazyCell<ContentSizes, F>,
    ) -> Option<Au> {
        Some(match self {
            Self::Initial => return None,
            Self::MinContent => content_size.min_content,
            Self::MaxContent => content_size.max_content,
            Self::FitContentFunction(size) => content_size.shrink_to_fit(*size),
            Self::FitContent => content_size.shrink_to_fit(stretch_size.unwrap_or(MAX_AU)),
            Self::Stretch => return stretch_size,
            Self::Numeric(numeric) => *numeric,
        })
    }

    /// Tries to resolve an extrinsic size into a numerical value.
    /// Extrinsic sizes are those based on the context of an element, without regard for its contents.
    /// <https://drafts.csswg.org/css-sizing-3/#extrinsic>
    ///
    /// Returns `None` if either:
    /// - The size is intrinsic.
    /// - The size is the initial one.
    ///   TODO: should we allow it to behave as `stretch` instead of assuming it's intrinsic?
    /// - The provided `stretch_size` is `None` but we need its value.
    #[inline]
    pub(crate) fn maybe_resolve_extrinsic(&self, stretch_size: Option<Au>) -> Option<Au> {
        match self {
            Self::Initial |
            Self::MinContent |
            Self::MaxContent |
            Self::FitContent |
            Self::FitContentFunction(_) => None,
            Self::Stretch => stretch_size,
            Self::Numeric(numeric) => Some(*numeric),
        }
    }
}

/// Represents the sizing constraint that the preferred, min and max sizing properties
/// impose on one axis.
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
pub(crate) enum SizeConstraint {
    /// Represents a definite preferred size, clamped by minimum and maximum sizes (if any).
    Definite(Au),
    /// Represents an indefinite preferred size that allows a range of values between
    /// the first argument (minimum size) and the second one (maximum size).
    MinMax(Au, Option<Au>),
}

impl Default for SizeConstraint {
    #[inline]
    fn default() -> Self {
        Self::MinMax(Au::default(), None)
    }
}

impl SizeConstraint {
    #[inline]
    pub(crate) fn new(preferred_size: Option<Au>, min_size: Au, max_size: Option<Au>) -> Self {
        preferred_size.map_or_else(
            || Self::MinMax(min_size, max_size),
            |size| Self::Definite(size.clamp_between_extremums(min_size, max_size)),
        )
    }

    #[inline]
    pub(crate) fn is_definite(self) -> bool {
        matches!(self, Self::Definite(_))
    }

    #[inline]
    pub(crate) fn to_definite(self) -> Option<Au> {
        match self {
            Self::Definite(size) => Some(size),
            _ => None,
        }
    }
}

impl From<Option<Au>> for SizeConstraint {
    fn from(size: Option<Au>) -> Self {
        size.map(SizeConstraint::Definite).unwrap_or_default()
    }
}

#[derive(Clone, Default)]
pub(crate) struct Sizes {
    /// <https://drafts.csswg.org/css-sizing-3/#preferred-size-properties>
    pub preferred: Size<Au>,
    /// <https://drafts.csswg.org/css-sizing-3/#min-size-properties>
    pub min: Size<Au>,
    /// <https://drafts.csswg.org/css-sizing-3/#max-size-properties>
    pub max: Size<Au>,
}

impl Sizes {
    #[inline]
    pub(crate) fn new(preferred: Size<Au>, min: Size<Au>, max: Size<Au>) -> Self {
        Self {
            preferred,
            min,
            max,
        }
    }

    /// Resolves the three sizes into a single numerical value.
    #[inline]
    pub(crate) fn resolve(
        &self,
        axis: Direction,
        automatic_size: Size<Au>,
        get_automatic_minimum_size: impl FnOnce() -> Au,
        stretch_size: Option<Au>,
        get_content_size: impl FnOnce() -> ContentSizes,
        is_table: bool,
    ) -> Au {
        if is_table && axis == Direction::Block {
            // The intrinsic block size of a table already takes sizing properties into account,
            // but it can be a smaller amount if there are collapsed rows.
            // Therefore, disregard sizing properties and just defer to the intrinsic size.
            // This is being discussed in https://github.com/w3c/csswg-drafts/issues/11408
            return get_content_size().max_content;
        }
        let (preferred, min, max) = self.resolve_each(
            automatic_size,
            get_automatic_minimum_size,
            stretch_size,
            get_content_size,
            is_table,
        );
        preferred.clamp_between_extremums(min, max)
    }

    /// Resolves each of the three sizes into a numerical value, separately.
    /// - The 1st returned value is the resolved preferred size.
    /// - The 2nd returned value is the resolved minimum size.
    /// - The 3rd returned value is the resolved maximum size. `None` means no maximum.
    #[inline]
    pub(crate) fn resolve_each(
        &self,
        automatic_size: Size<Au>,
        get_automatic_minimum_size: impl FnOnce() -> Au,
        stretch_size: Option<Au>,
        get_content_size: impl FnOnce() -> ContentSizes,
        is_table: bool,
    ) -> (Au, Au, Option<Au>) {
        // The provided `get_content_size` is a FnOnce but we may need its result multiple times.
        // A LazyCell will only invoke it once if needed, and then reuse the result.
        let content_size = LazyCell::new(get_content_size);
        (
            self.preferred
                .resolve_for_preferred(automatic_size, stretch_size, &content_size),
            self.min.resolve_for_min(
                get_automatic_minimum_size,
                stretch_size,
                &content_size,
                is_table,
            ),
            self.max.resolve_for_max(stretch_size, &content_size),
        )
    }

    /// Tries to extrinsically resolve the three sizes into a single [`SizeConstraint`].
    /// Values that are intrinsic or need `stretch_size` when it's `None` are handled as such:
    /// - On the preferred size, they make the returned value be an indefinite [`SizeConstraint::MinMax`].
    /// - On the min size, they are treated as `auto`, enforcing the automatic minimum size.
    /// - On the max size, they are treated as `none`, enforcing no maximum.
    #[inline]
    pub(crate) fn resolve_extrinsic(
        &self,
        automatic_size: Size<Au>,
        automatic_minimum_size: Au,
        stretch_size: Option<Au>,
    ) -> SizeConstraint {
        let (preferred, min, max) =
            self.resolve_each_extrinsic(automatic_size, automatic_minimum_size, stretch_size);
        SizeConstraint::new(preferred, min, max)
    }

    /// Tries to extrinsically resolve each of the three sizes into a numerical value, separately.
    /// This can't resolve values that are intrinsic or need `stretch_size` but it's `None`.
    /// - The 1st returned value is the resolved preferred size. If it can't be resolved then
    ///   the returned value is `None`. Note that this is different than treating it as `auto`.
    ///   TODO: This needs to be discussed in <https://github.com/w3c/csswg-drafts/issues/11387>.
    /// - The 2nd returned value is the resolved minimum size. If it can't be resolved then we
    ///   treat it as the initial `auto`, returning the automatic minimum size.
    /// - The 3rd returned value is the resolved maximum size. If it can't be resolved then we
    ///   treat it as the initial `none`, returning `None`.
    #[inline]
    pub(crate) fn resolve_each_extrinsic(
        &self,
        automatic_size: Size<Au>,
        automatic_minimum_size: Au,
        stretch_size: Option<Au>,
    ) -> (Option<Au>, Au, Option<Au>) {
        (
            if self.preferred.is_initial() {
                automatic_size.maybe_resolve_extrinsic(stretch_size)
            } else {
                self.preferred.maybe_resolve_extrinsic(stretch_size)
            },
            self.min
                .maybe_resolve_extrinsic(stretch_size)
                .unwrap_or(automatic_minimum_size),
            self.max.maybe_resolve_extrinsic(stretch_size),
        )
    }
}

struct LazySizeData<'a> {
    sizes: &'a Sizes,
    axis: Direction,
    automatic_size: Size<Au>,
    get_automatic_minimum_size: fn() -> Au,
    stretch_size: Option<Au>,
    is_table: bool,
}

/// Represents a size that can't be fully resolved until the intrinsic size
/// is known. This is useful in the block axis, since the intrinsic size
/// depends on layout, but the other inputs are known beforehand.
pub(crate) struct LazySize<'a> {
    result: OnceCell<Au>,
    data: Option<LazySizeData<'a>>,
}

impl<'a> LazySize<'a> {
    pub(crate) fn new(
        sizes: &'a Sizes,
        axis: Direction,
        automatic_size: Size<Au>,
        get_automatic_minimum_size: fn() -> Au,
        stretch_size: Option<Au>,
        is_table: bool,
    ) -> Self {
        Self {
            result: OnceCell::new(),
            data: Some(LazySizeData {
                sizes,
                axis,
                automatic_size,
                get_automatic_minimum_size,
                stretch_size,
                is_table,
            }),
        }
    }

    /// Creates a [`LazySize`] that will resolve to the intrinsic size.
    /// Should be equivalent to [`LazySize::new()`] with default parameters,
    /// but avoiding the trouble of getting a reference to a [`Sizes::default()`]
    /// which lives long enough.
    ///
    /// TODO: It's not clear what this should do if/when [`LazySize::resolve()`]
    /// is changed to accept a [`ContentSizes`] as the intrinsic size.
    pub(crate) fn intrinsic() -> Self {
        Self {
            result: OnceCell::new(),
            data: None,
        }
    }

    /// Resolves the [`LazySize`] into [`Au`], caching the result.
    /// The argument is a callback that computes the intrinsic size lazily.
    ///
    /// TODO: The intrinsic size should probably be a [`ContentSizes`] instead of [`Au`].
    pub(crate) fn resolve(&self, get_content_size: impl FnOnce() -> Au) -> Au {
        *self.result.get_or_init(|| {
            let Some(ref data) = self.data else {
                return get_content_size();
            };
            data.sizes.resolve(
                data.axis,
                data.automatic_size,
                data.get_automatic_minimum_size,
                data.stretch_size,
                || get_content_size().into(),
                data.is_table,
            )
        })
    }
}

impl From<Au> for LazySize<'_> {
    /// Creates a [`LazySize`] that will resolve to the given [`Au`],
    /// ignoring the intrinsic size.
    fn from(value: Au) -> Self {
        let result = OnceCell::new();
        result.set(value).unwrap();
        LazySize { result, data: None }
    }
}