// It distributes the free space to the flexible items and ensures that the size
        // of the flex items abide the min and max constraints. At the end of this
        // function the child nodes would have proper size. Prior using this function
        // please ensure that YGDistributeFreeSpaceFirstPass is called.

        private float DistributeFreeSpaceSecondPass(
            YogaNode node,
            FlexDirectionType mainAxis,
            FlexDirectionType crossAxis,
            float mainAxisOwnerSize,
            float availableInnerMainDim,
            float availableInnerCrossDim,
            float availableInnerWidth,
            float availableInnerHeight,
            bool flexBasisOverflows,
            MeasureMode measureModeCrossDim,
            bool performLayout,
            YogaConfig config)
        {
            float deltaFreeSpace = 0;
            var   isMainAxisRow  = mainAxis.IsRow();
            var   isNodeFlexWrap = node.FlexWrap != WrapType.NoWrap;

            foreach (var currentRelativeChild in RelativeChildren)
            {
                var childFlexBasis = currentRelativeChild.BoundAxisWithinMinAndMax(
                    mainAxis,
                    currentRelativeChild.Layout.ComputedFlexBasis.Unwrap(),
                    mainAxisOwnerSize);
                var updatedMainSize = childFlexBasis;

                if (RemainingFreeSpace.HasValue() && RemainingFreeSpace < 0)
                {
                    var flexShrinkScaledFactor = -currentRelativeChild.ResolveFlexShrink() * childFlexBasis;
                    // Is this child able to shrink?
                    if (flexShrinkScaledFactor != 0f)
                    {
                        float childSize;

                        if (TotalFlexShrinkScaledFactors.HasValue() && TotalFlexShrinkScaledFactors == 0f)
                        {
                            childSize = childFlexBasis + flexShrinkScaledFactor;
                        }
                        else
                        {
                            childSize = childFlexBasis + RemainingFreeSpace / TotalFlexShrinkScaledFactors * flexShrinkScaledFactor;
                        }

                        updatedMainSize = currentRelativeChild.BoundAxis(
                            mainAxis,
                            childSize,
                            availableInnerMainDim,
                            availableInnerWidth);
                    }
                }
                else if (RemainingFreeSpace.HasValue() && RemainingFreeSpace > 0)
                {
                    var flexGrowFactor = currentRelativeChild.ResolveFlexGrow();

                    // Is this child able to grow?
                    if (flexGrowFactor.HasValue() && flexGrowFactor != 0)
                    {
                        updatedMainSize = currentRelativeChild.BoundAxis(
                            mainAxis,
                            childFlexBasis + RemainingFreeSpace / TotalFlexGrowFactors * flexGrowFactor,
                            availableInnerMainDim,
                            availableInnerWidth);
                    }
                }

                deltaFreeSpace += updatedMainSize - childFlexBasis;

                var marginMain  = currentRelativeChild.GetMarginForAxis(mainAxis, availableInnerWidth);
                var marginCross = currentRelativeChild.GetMarginForAxis(crossAxis, availableInnerWidth);

                float       childCrossSize;
                var         childMainSize = updatedMainSize + marginMain;
                MeasureMode childCrossMeasureMode;
                var         childMainMeasureMode = MeasureMode.Exactly;

                if (currentRelativeChild.AspectRatio.HasValue)
                {
                    childCrossSize = isMainAxisRow
                        ? (childMainSize - marginMain) / currentRelativeChild.AspectRatio.Value
                        : (childMainSize - marginMain) * currentRelativeChild.AspectRatio.Value;
                    childCrossMeasureMode = MeasureMode.Exactly;

                    childCrossSize += marginCross;
                }
                else if (
                    availableInnerCrossDim.HasValue() &&
                    !currentRelativeChild.IsStyleDimensionDefined(crossAxis, availableInnerCrossDim) &&
                    measureModeCrossDim == MeasureMode.Exactly &&
                    !(isNodeFlexWrap && flexBasisOverflows) &&
                    node.AlignChild(currentRelativeChild) == AlignType.Stretch &&
                    currentRelativeChild.MarginLeadingValue(crossAxis).Unit !=
                    ValueUnit.Auto &&
                    currentRelativeChild.MarginTrailingValue(crossAxis).Unit !=
                    ValueUnit.Auto)
                {
                    childCrossSize        = availableInnerCrossDim;
                    childCrossMeasureMode = MeasureMode.Exactly;
                }
                else if (!currentRelativeChild.IsStyleDimensionDefined(crossAxis, availableInnerCrossDim))
                {
                    childCrossSize        = availableInnerCrossDim;
                    childCrossMeasureMode = childCrossSize.IsNaN()
                        ? MeasureMode.Undefined
                        : MeasureMode.AtMost;
                }
                else
                {
                    childCrossSize =
                        currentRelativeChild.ResolvedDimension[crossAxis.ToDimension()].ResolveValue(availableInnerCrossDim) + marginCross;
                    var isLoosePercentageMeasurement =
                        currentRelativeChild.ResolvedDimension[crossAxis.ToDimension()].Unit == ValueUnit.Percent &&
                        measureModeCrossDim != MeasureMode.Exactly;
                    childCrossMeasureMode =
                        childCrossSize.IsNaN() || isLoosePercentageMeasurement
                            ? MeasureMode.Undefined
                            : MeasureMode.Exactly;
                }

                currentRelativeChild.Calc.ConstrainMaxSizeForMode(
                    mainAxis,
                    availableInnerMainDim,
                    availableInnerWidth,
                    ref childMainMeasureMode,
                    ref childMainSize);
                currentRelativeChild.Calc.ConstrainMaxSizeForMode(
                    crossAxis,
                    availableInnerCrossDim,
                    availableInnerWidth,
                    ref childCrossMeasureMode,
                    ref childCrossSize);

                var requiresStretchLayout =
                    !currentRelativeChild.IsStyleDimensionDefined(crossAxis, availableInnerCrossDim) &&
                    node.AlignChild(currentRelativeChild) == AlignType.Stretch &&
                    currentRelativeChild.MarginLeadingValue(crossAxis).Unit !=
                    ValueUnit.Auto &&
                    currentRelativeChild.MarginTrailingValue(crossAxis).Unit != ValueUnit.Auto;

                var childWidth  = isMainAxisRow ? childMainSize : childCrossSize;
                var childHeight = !isMainAxisRow ? childMainSize : childCrossSize;

                var childWidthMeasureMode  = isMainAxisRow ? childMainMeasureMode : childCrossMeasureMode;
                var childHeightMeasureMode = !isMainAxisRow ? childMainMeasureMode : childCrossMeasureMode;

                // Recursively call the layout algorithm for this child with the updated main size.
                currentRelativeChild.Calc.LayoutInternal(
                    childWidth,
                    childHeight,
                    node.Layout.Direction,
                    childWidthMeasureMode,
                    childHeightMeasureMode,
                    availableInnerWidth,
                    availableInnerHeight,
                    performLayout && !requiresStretchLayout,
                    "flex",
                    config);
                node.Layout.HadOverflow = node.Layout.HadOverflow | currentRelativeChild.Layout.HadOverflow;
            }

            return(deltaFreeSpace);
        }
        internal void JustifyMainAxis(
            YogaNode node,
            int startOfLineIndex,
            FlexDirectionType mainAxis,
            FlexDirectionType crossAxis,
            MeasureMode measureModeMainDim,
            MeasureMode measureModeCrossDim,
            float mainAxisOwnerSize,
            float ownerWidth,
            float availableInnerMainDim,
            float availableInnerCrossDim,
            float availableInnerWidth,
            bool performLayout)
        {
            var style = (INodeStyle)node;
            var leadingPaddingAndBorderMain  = node.GetLeadingPaddingAndBorder(mainAxis, ownerWidth);
            var trailingPaddingAndBorderMain = node.GetTrailingPaddingAndBorder(mainAxis, ownerWidth);

            // If we are using "at most" rules in the main axis, make sure that
            // remainingFreeSpace is 0 when min main dimension is not given
            if (measureModeMainDim == MeasureMode.AtMost && RemainingFreeSpace > 0)
            {
                if (style.MinDimension(mainAxis.ToDimension()).Unit != ValueUnit.Undefined &&
                    style.MinDimension(mainAxis.ToDimension()).ResolveValue(mainAxisOwnerSize).HasValue())
                {
                    // This condition makes sure that if the size of main dimension(after
                    // considering child nodes main dim, leading and trailing padding etc)
                    // falls below min dimension, then the remainingFreeSpace is reassigned
                    // considering the min dimension

                    // `minAvailableMainDim` denotes minimum available space in which child
                    // can be laid out, it will exclude space consumed by padding and border.
                    var minAvailableMainDim =
                        style.MinDimension(mainAxis.ToDimension()).ResolveValue(mainAxisOwnerSize)
                        - leadingPaddingAndBorderMain
                        - trailingPaddingAndBorderMain;
                    var occupiedSpaceByChildNodes = availableInnerMainDim - RemainingFreeSpace;
                    RemainingFreeSpace = FloatMax(0, minAvailableMainDim - occupiedSpaceByChildNodes);
                }
                else
                {
                    RemainingFreeSpace = 0;
                }
            }

            var numberOfAutoMarginsOnCurrentLine = 0;

            for (var i = startOfLineIndex; i < EndOfLineIndex; i++)
            {
                var child = node.Children[i];
                if (child.PositionType == PositionType.Relative)
                {
                    if (child.MarginLeadingValue(mainAxis).Unit == ValueUnit.Auto)
                    {
                        numberOfAutoMarginsOnCurrentLine++;
                    }

                    if (child.MarginTrailingValue(mainAxis).Unit == ValueUnit.Auto)
                    {
                        numberOfAutoMarginsOnCurrentLine++;
                    }
                }
            }

            // In order to position the elements in the main axis, we have two
            // controls. The space between the beginning and the first element
            // and the space between each two elements.
            float leadingMainDim = 0;
            float betweenMainDim = 0;
            var   justifyContent = node.JustifyContent;

            if (numberOfAutoMarginsOnCurrentLine == 0)
            {
                switch (justifyContent)
                {
                case JustifyType.Center:
                    leadingMainDim = RemainingFreeSpace / 2;
                    break;

                case JustifyType.FlexEnd:
                    leadingMainDim = RemainingFreeSpace;
                    break;

                case JustifyType.SpaceBetween:
                    if (ItemsOnLine > 1)
                    {
                        betweenMainDim = FloatMax(RemainingFreeSpace, 0) / (ItemsOnLine - 1);
                    }
                    else
                    {
                        betweenMainDim = 0;
                    }

                    break;

                case JustifyType.SpaceEvenly:
                    // Space is distributed evenly across all elements
                    betweenMainDim = RemainingFreeSpace / (ItemsOnLine + 1);
                    leadingMainDim = betweenMainDim;
                    break;

                case JustifyType.SpaceAround:
                    // Space on the edges is half of the space between elements
                    betweenMainDim = RemainingFreeSpace / ItemsOnLine;
                    leadingMainDim = betweenMainDim / 2;
                    break;

                case JustifyType.FlexStart:
                    break;
                }
            }

            MainDim  = leadingPaddingAndBorderMain + leadingMainDim;
            CrossDim = 0;

            float maxAscentForCurrentLine  = 0;
            float maxDescentForCurrentLine = 0;
            var   isNodeBaselineLayout     = node.IsBaselineLayout();

            for (var i = startOfLineIndex; i < EndOfLineIndex; i++)
            {
                var child       = node.Children[i];
                var childStyle  = (INodeStyle)child;
                var childLayout = child.Layout;

                if (childStyle.Display == DisplayType.None)
                {
                    continue;
                }

                if (childStyle.PositionType == PositionType.Absolute &&
                    child.IsLeadingPositionDefined(mainAxis))
                {
                    if (performLayout)
                    {
                        child.Layout.Position[mainAxis.ToPositionEdge()] =
                            child.GetLeadingPosition(mainAxis, availableInnerMainDim)
                            + node.GetLeadingBorder(mainAxis)
                            + child.GetLeadingMargin(mainAxis, availableInnerWidth);
                    }
                }
                else
                {
                    // Now that we placed the element, we need to update the variables.
                    // We need to do that only for relative elements. Absolute elements
                    // do not take part in that phase.
                    if (childStyle.PositionType == PositionType.Relative)
                    {
                        if (child.MarginLeadingValue(mainAxis).Unit == ValueUnit.Auto)
                        {
                            MainDim += RemainingFreeSpace / numberOfAutoMarginsOnCurrentLine;
                        }

                        if (performLayout)
                        {
                            child.Layout.Position[mainAxis.ToPositionEdge()] =
                                childLayout.Position[mainAxis.ToPositionEdge()] + MainDim;
                        }

                        if (child.MarginTrailingValue(mainAxis).Unit == ValueUnit.Auto)
                        {
                            MainDim += RemainingFreeSpace / numberOfAutoMarginsOnCurrentLine;
                        }

                        var canSkipFlex =
                            !performLayout && measureModeCrossDim == MeasureMode.Exactly;
                        if (canSkipFlex)
                        {
                            // If we skipped the flex step, then we can't rely on the
                            // measuredDims because
                            // they weren't computed. This means we can't call
                            // YGNodeDimWithMargin.
                            MainDim += betweenMainDim
                                       + child.GetMarginForAxis(mainAxis, availableInnerWidth)
                                       + childLayout.ComputedFlexBasis.Unwrap();
                            CrossDim = availableInnerCrossDim;
                        }
                        else
                        {
                            // The main dimension is the sum of all the elements dimension plus
                            // the spacing.
                            MainDim += betweenMainDim + child.DimensionWithMargin(mainAxis, availableInnerWidth);

                            if (isNodeBaselineLayout)
                            {
                                // If the child is baseline aligned then the cross dimension is
                                // calculated by adding maxAscent and maxDescent from the baseline.
                                var ascent = child.Baseline() +
                                             child.GetLeadingMargin(FlexDirectionType.Column, availableInnerWidth);
                                var descent =
                                    child.Layout.MeasuredHeight
                                    + child.GetMarginForAxis(FlexDirectionType.Column, availableInnerWidth)
                                    - ascent;

                                maxAscentForCurrentLine  = FloatMax(maxAscentForCurrentLine, ascent);
                                maxDescentForCurrentLine = FloatMax(maxDescentForCurrentLine, descent);
                            }
                            else
                            {
                                // The cross dimension is the max of the elements dimension since
                                // there can only be one element in that cross dimension in the case
                                // when the items are not baseline aligned
                                CrossDim = FloatMax(CrossDim, child.DimensionWithMargin(crossAxis, availableInnerWidth));
                            }
                        }
                    }
                    else if (performLayout)
                    {
                        child.Layout.Position[mainAxis.ToPositionEdge()] =
                            childLayout.Position[mainAxis.ToPositionEdge()]
                            + node.GetLeadingBorder(mainAxis)
                            + leadingMainDim;
                    }
                }
            }

            MainDim += trailingPaddingAndBorderMain;

            if (isNodeBaselineLayout)
            {
                CrossDim = maxAscentForCurrentLine + maxDescentForCurrentLine;
            }
        }
 public NodePrint(YogaNode node, PrintOptionType options)
 {
     _node    = node ?? throw new ArgumentNullException(nameof(node));
     _options = options;
 }