// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { mergeStyles } from '@fluentui/react';
import React, { useRef, useEffect, useState, useMemo } from 'react';
import { gridLayoutStyle } from './styles/GridLayout.styles';
/**
 * A component to lay out audio / video participants tiles in a call.
 *
 * @public
 */
export const GridLayout = (props) => {
    const { children, styles } = props;
    const numberOfChildren = React.Children.count(children);
    const [currentWidth, setCurrentWidth] = useState(0);
    const [currentHeight, setCurrentHeight] = useState(0);
    const containerRef = useRef(null);
    const observer = useRef(new ResizeObserver((entries) => {
        if (!entries[0]) {
            return;
        }
        const { width, height } = entries[0].contentRect;
        setCurrentWidth(width);
        setCurrentHeight(height);
    }));
    useEffect(() => {
        if (containerRef.current) {
            observer.current.observe(containerRef.current);
        }
        const currentObserver = observer.current;
        return () => currentObserver.disconnect();
    }, [observer, containerRef]);
    const gridProps = useMemo(() => {
        return calculateGridProps(numberOfChildren, currentWidth, currentHeight);
    }, [numberOfChildren, currentWidth, currentHeight]);
    const cssGridStyles = useMemo(() => createGridStyles(numberOfChildren, gridProps), [numberOfChildren, gridProps]);
    return React.createElement("div", { ref: containerRef, className: mergeStyles(gridLayoutStyle, cssGridStyles, styles === null || styles === void 0 ? void 0 : styles.root), "data-ui-id": "grid-layout" }, children);
};
/**
 * The cell aspect ratio we aim for in a grid
 */
const TARGET_CELL_ASPECT_RATIO = 16 / 9;
/**
 * The minimum cell aspect ratio we allow
 */
const MINIMUM_CELL_ASPECT_RATIO_ALLOWED = 8 / 9;
const isCloserThan = (a, b, target) => {
    return Math.abs(target - a) < Math.abs(target - b);
};
/**
 * Get the best GridProps to place a number of items in a grid as evenly as possible given the width and height of the grid
 * @param numberOfItems - number of items to place in grid
 * @param width - width of grid
 * @param height - height of grid
 * @returns GridProps
 */
export const calculateGridProps = (numberOfItems, width, height) => {
    if (numberOfItems <= 0) {
        return {
            fillDirection: 'horizontal',
            rows: 0,
            columns: 0
        };
    }
    // If width or height are 0 then we return rows and column evenly
    if (width <= 0 || height <= 0) {
        return {
            fillDirection: 'horizontal',
            rows: Math.ceil(Math.sqrt(numberOfItems)),
            columns: Math.ceil(Math.sqrt(numberOfItems))
        };
    }
    const aspectRatio = width / height;
    // Approximate how many rows to divide the grid to achieve cells close to the TARGET_CELL_ASPECT_RATIO
    let rows = Math.floor(Math.sqrt(TARGET_CELL_ASPECT_RATIO / aspectRatio * numberOfItems)) || 1;
    // Make sure rows do not exceed numberOfItems
    rows = Math.min(rows, numberOfItems);
    // Given the rows, get the minimum columns needed to create enough cells for the number of items
    let columns = Math.ceil(numberOfItems / rows);
    // Default fill direction to horizontal
    let fillDirection = 'horizontal';
    while (rows < numberOfItems) {
        // If cell aspect ratio is less than MINIMUM_CELL_ASPECT_RATIO_ALLOWED then try more rows
        if (rows / columns * aspectRatio < MINIMUM_CELL_ASPECT_RATIO_ALLOWED) {
            rows += 1;
            columns = Math.ceil(numberOfItems / rows);
            continue;
        }
        if (numberOfItems < rows * columns) {
            // We need to check that stretching columns vertically will result in only one less cell in stretched columns.
            // Likewise, we need to check that stretching rows horizonally will result in only one less cell in stretched rows.
            // e.g. For 4 rows, 2 columns, but only 6 items, we cannot stretch vertically because that would result in a
            // column of 2 cells which is less by more than 1 compared to the unstretched column.
            //  _________
            // |____|    |
            // |____|____|
            // |____|    |
            // |____|____|
            const canStretchVertically = numberOfItems >= rows + (columns - 1) * (rows - 1);
            const canStretchHorizontally = numberOfItems >= columns + (rows - 1) * (columns - 1);
            if (!canStretchVertically && !canStretchHorizontally) {
                rows += 1;
                columns = Math.ceil(numberOfItems / rows);
                continue;
            }
            else if (!canStretchVertically) {
                break;
            }
            else if (!canStretchHorizontally) {
                fillDirection = 'vertical';
                break;
            }
            // We need to figure out whether the big cells should stretch horizontally or vertically
            // to fill in the empty spaces
            // e.g. For 2 rows, 3 columns, but only 5 items, we need to choose whether to stretch cells
            //       horizontally            or           vertically
            //  ______________________               _______________________
            // |       |       |      |             |       |       |       |
            // |_______|_______|______|             |_______|_______|       |
            // |           |          |             |       |       |       |
            // |___________|__________|             |_______|_______|_______|
            // Calculate the aspect ratio of big cells stretched horizontally
            const horizontallyStretchedCellRatio = rows / (columns - 1) * aspectRatio;
            // Calculate the aspect ratio of big cells stretched vertically
            const verticallyStretchedCellRatio = (rows - 1) / columns * aspectRatio;
            // We know the horizontally stretched cells aspect ratio is higher than MINIMUM_CELL_ASPECT_RATIO_ALLOWED. If vertically stretched cells
            // is also higher than the MINIMUM_CELL_ASPECT_RATIO_ALLOWED, then choose which aspect ratio is better.
            if (verticallyStretchedCellRatio >= MINIMUM_CELL_ASPECT_RATIO_ALLOWED) {
                // If vertically stetched cell has an aspect ratio closer to TARGET_CELL_ASPECT_RATIO then change the fill direction to vertical
                if (isCloserThan(verticallyStretchedCellRatio, horizontallyStretchedCellRatio, TARGET_CELL_ASPECT_RATIO)) {
                    fillDirection = 'vertical';
                }
            }
        }
        break;
    }
    return {
        fillDirection,
        rows,
        columns
    };
};
/**
 * Creates a styles classname with CSS Grid related styles given GridProps and the number of items to distribute as evenly as possible.
 * @param numberOfItems - number of items to place in grid
 * @param gridProps - GridProps that define the number of rows, number of columns, and the fill direction
 * @returns - classname
 */
export const createGridStyles = (numberOfItems, gridProps) => {
    const isHorizontal = gridProps.fillDirection === 'horizontal';
    // Blocks are either rows or columns depending on whether we fill horizontally or vertically. Each block may differ in the number of cells.
    const blocks = isHorizontal ? gridProps.rows : gridProps.columns;
    const smallCellsPerBlock = Math.ceil(numberOfItems / blocks);
    const bigCellsPerBlock = Math.floor(numberOfItems / blocks);
    const numBigCells = (gridProps.rows * gridProps.columns - numberOfItems) * bigCellsPerBlock;
    // Get grid units
    // e.g. If some blocks have 2 big cells while others have 3 small cells, we need to work with 6 units per block
    const units = smallCellsPerBlock * bigCellsPerBlock;
    const gridStyles = isHorizontal ? {
        gridTemplateColumns: `repeat(${units}, minmax(0, 1fr))`,
        gridTemplateRows: `repeat(${blocks}, minmax(0, 1fr))`,
        gridAutoFlow: 'row'
    } : {
        gridTemplateColumns: `repeat(${blocks}, minmax(0, 1fr))`,
        gridTemplateRows: `repeat(${units}, minmax(0, 1fr))`,
        gridAutoFlow: 'column'
    };
    const smallCellStyle = isHorizontal ? {
        '> *': {
            gridColumn: `auto / span ${units / smallCellsPerBlock}`
        }
    } : {
        '> *': {
            gridRow: `auto / span ${units / smallCellsPerBlock}`
        }
    };
    // If there are big cells, we are choosing to place the latest children into the big cells.
    // That is why we use the '> *:nth-last-child(-n + ${numBigCells})' CSS selector below
    const bigCellStyle = numBigCells ? {
        [`> *:nth-last-child(-n + ${numBigCells})`]: isHorizontal ? {
            gridColumn: `auto / span ${units / bigCellsPerBlock}`
        } : {
            gridRow: `auto / span ${units / bigCellsPerBlock}`
        }
    } : {};
    return mergeStyles(gridStyles, smallCellStyle, bigCellStyle);
};
//# sourceMappingURL=GridLayout.js.map