// (C) 2019-2026 GoodData Corporation
import { invariant } from "ts-invariant";
import { widgetType as getWidgetType, isDashboardLayout, isDashboardWidget, isInsight, isInsightWidget, isKpi, isKpiWidget, isKpiWithoutComparison, isVisualizationSwitcherWidget, isWidget, } from "@gooddata/sdk-model";
import { DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT, INSIGHT_WIDGET_SIZE_INFO_NEW_DEFAULT, KPI_WIDGET_SIZE_INFO_DEFAULT, MIN_VISUALIZATION_WIDTH, RICH_TEXT_WIDGET_SIZE_INFO_NEW_DEFAULT, VISUALIZATION_SWITCHER_WIDGET_SIZE_INFO_NEW_DEFAULT, fluidLayoutDescriptor, getInsightSizeInfo, } from "@gooddata/sdk-ui-ext";
import { isCustomWidget } from "../../model/types/layoutTypes.js";
import { DASHBOARD_LAYOUT_GRID_COLUMNS_COUNT } from "../dashboard/flexibleLayout/config.js";
import { getLayoutConfiguration } from "../dashboard/flexibleLayout/layoutConfiguration.js";
import { GRID_ROW_HEIGHT_IN_PX, KPI_WITHOUT_COMPARISON_SIZE_INFO, KPI_WITH_COMPARISON_SIZE_INFO, } from "./constants.js";
import { findItem, hasParent } from "./coordinates.js";
/**
 * @internal
 */
export function getSizeInfo(settings, widgetType, widgetContent) {
    if (widgetType === "kpi") {
        return getKpiSizeInfo(settings, widgetContent);
    }
    else if (widgetType === "richText") {
        return RICH_TEXT_WIDGET_SIZE_INFO_NEW_DEFAULT;
    }
    else if (widgetType === "IDashboardLayout") {
        return DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT;
    }
    else if (widgetType === "visualizationSwitcher" && !widgetContent) {
        return VISUALIZATION_SWITCHER_WIDGET_SIZE_INFO_NEW_DEFAULT;
    }
    return getVisualizationSizeInfo(settings, widgetContent);
}
/**
 * @internal
 */
export function getInsightPlaceholderSizeInfo(settings) {
    return getVisualizationSizeInfo(settings);
}
function getVisualizationSizeInfo(_settings, insight) {
    let sizeInfo;
    if (isInsight(insight)) {
        sizeInfo = getInsightSizeInfo(insight, _settings);
    }
    if (!sizeInfo) {
        return INSIGHT_WIDGET_SIZE_INFO_NEW_DEFAULT;
    }
    return sizeInfo;
}
function getKpiSizeInfo(_settings, kpi) {
    if (!isKpi(kpi)) {
        return KPI_WIDGET_SIZE_INFO_DEFAULT;
    }
    return isKpiWithoutComparison(kpi) ? KPI_WITHOUT_COMPARISON_SIZE_INFO : KPI_WITH_COMPARISON_SIZE_INFO;
}
/**
 * @internal
 */
export function getDashboardLayoutWidgetDefaultHeight(settings, widgetType, widgetContent) {
    const sizeInfo = getSizeInfo(settings, widgetType, widgetContent);
    return fluidLayoutDescriptor.toHeightInPx(sizeInfo.height.default);
}
/**
 * @internal
 */
export function getDashboardLayoutWidgetMinGridHeight(settings, widgetType, widgetContent) {
    const sizeInfo = getSizeInfo(settings, widgetType, widgetContent);
    return sizeInfo.height.min;
}
/**
 * @internal
 */
export function getDashboardLayoutWidgetMaxGridHeight(settings, widgetType, widgetContent) {
    const sizeInfo = getSizeInfo(settings, widgetType, widgetContent);
    return sizeInfo.height.max;
}
function getSectionHeight(rows, screen, emptyLayoutMinHeight) {
    return rows.reduce((allRowsHeight, row) => {
        const currentRowHeight = getRowHeight(row, screen, emptyLayoutMinHeight);
        return allRowsHeight + currentRowHeight;
    }, 0);
}
function getRowHeight(row, screen, emptyLayoutMinHeight) {
    return row.reduce((maxRowItemHeight, item) => {
        const sanitizedItemSize = implicitLayoutItemSizeFromXlSize(item.size.xl);
        const currentItemHeight = sanitizedItemSize[screen]?.gridHeight ?? emptyLayoutMinHeight;
        return Math.max(maxRowItemHeight, currentItemHeight);
    }, 0);
}
export function getContainerHeight(container, screen, settings) {
    const emptyLayoutMinHeight = getDashboardLayoutWidgetMinGridHeight(settings, container.widget.type);
    if (!isDashboardLayout(container.widget) || container.widget.sections.length === 0) {
        return emptyLayoutMinHeight;
    }
    return container.widget.sections.reduce((allSectionsHeight, section) => {
        const allScreenSizes = implicitLayoutItemSizeFromXlSize(container.size.xl);
        const rows = splitDashboardLayoutItemsAsRenderedGridRows(section.items, allScreenSizes, screen);
        const currentSectionHeight = getSectionHeight(rows, screen, emptyLayoutMinHeight);
        return allSectionsHeight + currentSectionHeight;
    }, 0);
}
/**
 * @internal
 */
export function getMinHeight(widgets, insightMap, screen, settings, defaultMin = 0) {
    const minimumHeights = widgets
        .filter((layoutItem) => isDashboardWidget(layoutItem.widget))
        .map((layoutItem) => {
        const widget = layoutItem.widget;
        if (isVisualizationSwitcherWidget(widget) && widget.visualizations.length > 0) {
            return Math.max(...getVisSwitcherDimension(widget, insightMap, getDashboardLayoutWidgetMinGridHeight, settings));
        }
        else if (isDashboardLayout(widget)) {
            return getContainerHeight(layoutItem, screen, settings);
        }
        return getDashboardLayoutWidgetMinGridHeight(settings, getExtendedWidgetType(widget), getWidgetContent(widget, insightMap));
    });
    return Math.max(defaultMin, ...minimumHeights);
}
const MAXIMUM_HEIGHT_OF_ROW_WITH_NESTED_WIDGETS = 2000;
function getExtendedWidgetType(widget) {
    return isWidget(widget) ? getWidgetType(widget) : widget.type;
}
function getWidgetContent(widget, insightMap) {
    if (isKpiWidget(widget)) {
        return widget.kpi;
    }
    else if (isInsightWidget(widget)) {
        return insightMap.get(widget.insight);
    }
    return undefined;
}
/**
 * @internal
 */
export function getMaxHeight(layoutItems, insightMap, screen, settings) {
    const containsNestedLayout = layoutItems.some((layoutItem) => isDashboardLayout(layoutItem.widget));
    if (containsNestedLayout) {
        return MAXIMUM_HEIGHT_OF_ROW_WITH_NESTED_WIDGETS;
    }
    const maxHeights = layoutItems
        .filter((layoutItem) => isDashboardWidget(layoutItem))
        .map((layoutItem) => {
        const widget = layoutItem.widget;
        if (isVisualizationSwitcherWidget(widget) && widget.visualizations.length > 0) {
            return Math.min(...getVisSwitcherDimension(widget, insightMap, getDashboardLayoutWidgetMaxGridHeight, settings));
        }
        if (isDashboardLayout(widget)) {
            return getContainerHeight(layoutItem, screen, settings);
        }
        return getDashboardLayoutWidgetMaxGridHeight(settings, getExtendedWidgetType(widget), getWidgetContent(widget, insightMap));
    });
    return Math.min(...maxHeights);
}
/**
 * @internal
 */
export function getDashboardLayoutWidgetMinGridWidth(settings, widgetType, widgetContent) {
    const sizeInfo = getSizeInfo(settings, widgetType, widgetContent);
    return sizeInfo.width.min;
}
function getVisSwitcherDimension(widget, insightMap, getDashboardLayoutWidgetGridDimension, settings) {
    return widget.visualizations.map((visualization) => {
        return getDashboardLayoutWidgetGridDimension(settings, getWidgetType(widget), insightMap.get(visualization.insight));
    });
}
/**
 * Calculates width for a single item based on the container direction
 */
function calculateItemWidth(item, direction, screen, settings, insightMap, emptyLayoutMinWidth) {
    const widget = item.widget;
    if (isDashboardLayout(widget)) {
        const itemDirection = getLayoutConfiguration(widget).direction;
        const layoutMinWidth = getDashboardLayoutWidgetMinGridWidth(settings, widget.type);
        return calculateRecursiveLayoutWidth(widget, itemDirection, screen, insightMap, settings, layoutMinWidth);
    }
    return direction === "row"
        ? (determineWidthForScreen(screen, item.size) ?? emptyLayoutMinWidth)
        : getDashboardLayoutWidgetMinGridWidth(settings, getExtendedWidgetType(widget), getWidgetContent(widget, insightMap));
}
/**
 * Calculates the maximum width of items in a container recursively based on direction
 */
function calculateRecursiveLayoutWidth(container, direction, screen, insightMap, settings, emptyLayoutMinWidth) {
    const items = container.sections.flatMap((section) => section.items).filter((item) => item.widget);
    if (items.length === 0) {
        return emptyLayoutMinWidth;
    }
    return items
        .map((item) => {
        const itemDirection = isDashboardLayout(item.widget)
            ? getLayoutConfiguration(item.widget).direction
            : direction;
        return calculateItemWidth(item, itemDirection, screen, settings, insightMap, emptyLayoutMinWidth);
    })
        .reduce((maxWidth, itemWidth) => Math.max(maxWidth, itemWidth), emptyLayoutMinWidth);
}
/**
 * @internal
 */
export function getMinWidth(widget, insightMap, screen, settings, direction) {
    if (isCustomWidget(widget)) {
        return MIN_VISUALIZATION_WIDTH;
    }
    if (isVisualizationSwitcherWidget(widget) && widget.visualizations.length > 0) {
        return Math.max(...getVisSwitcherDimension(widget, insightMap, getDashboardLayoutWidgetMinGridWidth, settings));
    }
    if (isDashboardLayout(widget)) {
        const emptyLayoutMinWidth = getDashboardLayoutWidgetMinGridWidth(settings, widget.type);
        return calculateRecursiveLayoutWidth(widget, direction, screen, insightMap, settings, emptyLayoutMinWidth);
    }
    return getDashboardLayoutWidgetMinGridWidth(settings, getExtendedWidgetType(widget), getWidgetContent(widget, insightMap));
}
export function normalizeItemSizeToParent(itemToCheck, itemPath, layout, settings, insightsMap, screen = "xl") {
    if (itemPath && hasParent(itemPath) && itemToCheck.widget) {
        const widget = itemToCheck.widget;
        const parent = findItem(layout, itemPath.slice(0, -1));
        const { direction } = getLayoutConfiguration(parent.widget);
        const minWidth = getMinWidth(widget, insightsMap, screen, settings, direction);
        const newSize = normalizeSizeToParent(itemToCheck.size, minWidth, parent, direction, screen);
        const sizeChanged = newSize.xl.gridWidth !== itemToCheck.size.xl.gridWidth;
        const item = {
            ...itemToCheck,
            size: newSize,
        };
        return {
            item,
            sizeChanged,
        };
    }
    return { item: itemToCheck, sizeChanged: false };
}
const getNormalizedParentWidth = (parentDirection, widthForScreen, parentWidth, itemMinWidth) => {
    if (widthForScreen > parentWidth) {
        return Math.max(parentWidth, itemMinWidth); // do not overflow parent width
    }
    return parentDirection === "column"
        ? parentWidth // auto size widget to the parent width when the target is a column container
        : widthForScreen; // use widget width when the target is a row container
};
function normalizeSizeToParent(itemSize, itemMinWidth, parent, parentDirection, screen = "xl") {
    const width = determineWidthForScreen(screen, itemSize);
    const parentWidth = determineWidthForScreen(screen, parent.size);
    return {
        xl: {
            gridHeight: itemSize.xl.gridHeight, // keep height untouched as the container can be extended freely in this direction
            gridWidth: getNormalizedParentWidth(parentDirection, width, parentWidth, itemMinWidth),
        },
    };
}
/**
 * @internal
 */
export function calculateWidgetMinHeight(layoutItem, currentSize, insightMap, settings, isExportMode) {
    let widgetType;
    let content;
    const widget = layoutItem.widget;
    if (isExportMode) {
        return undefined;
    }
    if (isWidget(widget)) {
        widgetType = getWidgetType(widget);
    }
    if (isInsightWidget(widget)) {
        content = insightMap.get(widget.insight);
    }
    if (isKpiWidget(widget)) {
        content = widget.kpi;
    }
    if (!currentSize || currentSize.heightAsRatio) {
        return undefined;
    }
    return (getDashboardLayoutItemHeight(currentSize) ||
        getDashboardLayoutWidgetDefaultHeight(settings, widgetType, content));
}
export const getDashboardLayoutItemHeight = (size) => {
    const { gridHeight } = size;
    if (gridHeight) {
        return getDashboardLayoutItemHeightForGrid(gridHeight);
    }
    return undefined;
};
export const getDashboardLayoutItemHeightForGrid = (gridHeight) => gridHeight * GRID_ROW_HEIGHT_IN_PX;
export const determineSizeForScreen = (screen, layoutItemSize) => {
    return {
        ...(layoutItemSize ? (layoutItemSize[screen] ?? {}) : {}),
        gridWidth: determineWidthForScreen(screen, layoutItemSize),
    };
};
export const determineWidthForScreen = (screen, layoutItemSize) => {
    // Determine if element has size set in metadata object for the current screen size
    const providedSizeForScreen = layoutItemSize?.[screen];
    // Use the provided size for the screen if it is known, otherwise determine the size for the current
    // screen if we at least know xl size from metadata object, otherwise expect the element to be root
    // element with that spans the full size.
    const itemSize = providedSizeForScreen ??
        implicitLayoutItemSizeFromXlSize(layoutItemSize?.xl ?? {
            gridWidth: DASHBOARD_LAYOUT_GRID_COLUMNS_COUNT,
        })[screen];
    // Expect element to be full size if we could not get the size for the current screen from the value above.
    return itemSize?.gridWidth ?? DASHBOARD_LAYOUT_GRID_COLUMNS_COUNT;
};
/**
 * Derive dashboard layout size for all screens from dashboard layout size defined for xl screen.
 * We have only xl size saved in metadata, this will create additional screen sizes based on xl.
 *
 * @param xlSize - dashboard layout size for xl screen
 */
export function implicitLayoutItemSizeFromXlSize(xlSize) {
    const xlWidth = xlSize.gridWidth;
    const xlHeight = xlSize.gridHeight;
    const ratio = xlSize.heightAsRatio;
    switch (xlWidth) {
        case 0:
            return dashboardLayoutItemSizeForAllScreens(0, 0, 0, 0, 0, 0, 0);
        case 1:
            return dashboardLayoutItemSizeForAllScreens(ratio, xlHeight, xlWidth, xlWidth, 2, 6, 12);
        case 2:
            return dashboardLayoutItemSizeForAllScreens(ratio, xlHeight, xlWidth, xlWidth, 4, 6, 12);
        case 3:
        case 4:
        case 5:
        case 6:
        case 7:
        case 8:
        case 9:
            return dashboardLayoutItemSizeForAllScreens(ratio, xlHeight, xlWidth, xlWidth, 6, 12, 12);
        case 10:
            return dashboardLayoutItemSizeForAllScreens(ratio, xlHeight, xlWidth, xlWidth, 12, 12, 12);
        case 11:
            return dashboardLayoutItemSizeForAllScreens(ratio, xlHeight, xlWidth, xlWidth, 12, 12, 12);
        case 12:
            return dashboardLayoutItemSizeForAllScreens(ratio, xlHeight, xlWidth, xlWidth, 12, 12, 12);
        default:
            invariant(false, `Unsupported xlWidth: ${xlWidth}`);
    }
}
/**
 * Create dashboard layout item size for all screens,
 * with identical height, defined as ratio,
 * but different width, defined as grid items count.
 *
 * @param heightAsRatio - height as ratio to the width, defined in percents
 * @param gridHeight - height as number of grid rows
 * @param xl - width as grid items count for xl screen
 * @param lg - width as grid items count for lg screen
 * @param md - width as grid items count for md screen
 * @param sm - width as grid items count for sm screen
 * @param xs - width as grid items count for xs screen
 */
function dashboardLayoutItemSizeForAllScreens(heightAsRatio, gridHeight, xl, lg, md, sm, xs) {
    if (gridHeight) {
        return {
            xl: {
                gridWidth: xl,
                gridHeight,
            },
            lg: {
                gridWidth: lg,
                gridHeight,
            },
            md: {
                gridWidth: md,
                gridHeight,
            },
            sm: {
                gridWidth: sm,
                gridHeight,
            },
            xs: {
                gridWidth: xs,
                gridHeight,
            },
        };
    }
    return {
        xl: {
            gridWidth: xl,
            heightAsRatio,
        },
        lg: {
            gridWidth: lg,
            heightAsRatio,
        },
        md: {
            gridWidth: md,
            heightAsRatio,
        },
        sm: {
            gridWidth: sm,
            heightAsRatio,
        },
        xs: {
            gridWidth: xs,
            heightAsRatio,
        },
    };
}
/**
 * Divide the items into a list representing the future rows of the grid.
 * This is useful for performing item transformations, depending on how they really appear in the grid.
 *
 * @param items - dashboard layout items
 * @param parentLayoutSize - the size of parent layout, undefined if the items are in the root layout
 * @param screen - responsive screen class
 */
export function splitDashboardLayoutItemsAsRenderedGridRows(items, parentLayoutSize, screen) {
    const parentLayoutColumnWidth = parentLayoutSize?.[screen]?.gridWidth ?? DASHBOARD_LAYOUT_GRID_COLUMNS_COUNT;
    const renderedRows = [];
    let currentRowWidth = 0;
    let currentRow = [];
    items.forEach((item) => {
        const itemSize = determineSizeForScreen(screen, item.size);
        if (itemSize === null || itemSize === undefined) {
            throw Error("Item size for current screen is undefined");
        }
        if (currentRowWidth + itemSize.gridWidth > parentLayoutColumnWidth) {
            renderedRows.push(currentRow);
            currentRow = [];
            currentRowWidth = 0;
        }
        currentRow.push(item);
        currentRowWidth = currentRowWidth + itemSize.gridWidth;
    });
    if (currentRow.length > 0) {
        renderedRows.push(currentRow);
    }
    return renderedRows;
}
//# sourceMappingURL=sizing.js.map