"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * Plugin Scenari that shrinks or expands elements of a page to avoid blank area.
 *  - Requires elements to be blocks.
 *  - The shrink algorithm handles the break points avoided inside ancestors,
 *    but does not check the validity of the new break point (the break-before of the next box is not known).
 *  - Should be included last.
 */
postscriptum.plugin('sc-scale-to-fit', (processor, options) => {
    const { css, util, layout } = postscriptum;
    const { logger, rects, ranges, walk } = util;
    const embeddedContent = ["img", "iframe", "embed", "object", "video"];
    function process(shrinkOnly = false) {
        const ctx = this.layoutContext;
        if (ctx.parentCtx)
            return;
        if (!options.allowExpandBeforeBreak && !walk.isBlockElement(ranges.nodeAtPoint(ctx.breakPoint)))
            return;
        const { breakedBox, boxes } = ctx;
        let lastBoxIndex = -1;
        if (breakedBox)
            lastBoxIndex = boxes.indexOf(breakedBox) - 1;
        else if (options.allowExpandBeforeBreak)
            lastBoxIndex = boxes.length - 1;
        if (lastBoxIndex == -1)
            return;
        const lastBox = boxes[lastBoxIndex];
        if (!lastBox || !lastBox.mainBlock)
            return;
        const pageBodyRect = rects.boundingScrollRect(this.currentFragment.body);
        const minBlankHeightProp = css.getCustomPluginProp(this.currentFragment.container, 'sc-stf-min-blank-height');
        if (!minBlankHeightProp)
            return;
        const minBlankHeight = css.computeLength(minBlankHeightProp, pageBodyRect.height);
        const blankHeight = pageBodyRect.bottom - lastBox.bottom;
        if (blankHeight < minBlankHeight)
            return;
        const dimByBlock = new Map();
        const shrinkByBlock = new Map();
        let shrinkBlocksHeight = 0;
        let shrinkBlocksMinHeight = 0;
        const expandByBlock = new Map();
        let expandBlocksHeight = 0;
        let expandBlocksMaxHeight = 0;
        for (const box of boxes) {
            if (box.mainBlock) {
                const elem = box.mainBlock;
                const shrinkProp = css.getCustomPluginProp(elem, 'sc-stf-max-shrink');
                const expandProp = css.getCustomPluginProp(elem, 'sc-stf-max-expand');
                if (shrinkProp || expandProp) {
                    let { width, height } = elem.getBoundingClientRect();
                    dimByBlock.set(elem, { width, height });
                    const style = getComputedStyle(elem);
                    const parentStyle = getComputedStyle(elem.parentElement);
                    const parentWidth = parseFloat(parentStyle.width);
                    const parentHeight = parseFloat(parentStyle.height);
                    if (shrinkProp) {
                        const minWidth = css.computeMinOrMaxLength(style.minWidth, parentWidth, 0);
                        const minHeight = css.computeMinOrMaxLength(style.minHeight, parentHeight, 0);
                        if (!isNaN(minWidth) && !isNaN(minHeight)) {
                            const shrink = Math.max(minWidth / width, minHeight / height, parseFloat(shrinkProp));
                            if (shrink < 1) {
                                shrinkByBlock.set(elem, shrink);
                                shrinkBlocksHeight += height;
                                shrinkBlocksMinHeight += height * shrink;
                            }
                        }
                    }
                    if (!shrinkOnly && expandProp && box != breakedBox) {
                        // TODO Handles the padding on the page body
                        const maxWidth = css.computeMinOrMaxLength(style.maxWidth, parentWidth, pageBodyRect.width);
                        const maxHeight = css.computeMinOrMaxLength(style.maxHeight, parentHeight, pageBodyRect.height);
                        if (!isNaN(maxWidth) && !isNaN(maxHeight)) {
                            const expand = Math.min(maxWidth / width, maxHeight / height, parseFloat(expandProp));
                            if (expand > 1) {
                                expandByBlock.set(elem, expand);
                                expandBlocksMaxHeight += height * expand;
                                expandBlocksHeight += height;
                            }
                        }
                    }
                }
                if (breakedBox && box == breakedBox)
                    break;
            }
        }
        if (breakedBox && shrinkByBlock.has(breakedBox.mainBlock) && !breakedBox.avoidBreakAfter) {
            let bottom = breakedBox.bottom;
            let breakAvoidedParent = layout.testAvoidBreakInside(breakedBox.mainBlock, ctx, this.avoidBreakTypes, ctx.breakPoint);
            if (breakAvoidedParent) {
                while (!walk.isStatic(breakAvoidedParent.nextElementSibling)) {
                    const parent = breakAvoidedParent.parentElement;
                    if (parent == ctx.root.parentNode)
                        break;
                    breakAvoidedParent = breakAvoidedParent.parentElement;
                }
                if (breakAvoidedParent != this.currentFragment.body)
                    bottom = util.rects.boundingScrollRect(breakAvoidedParent).bottom;
            }
            let overflowHeight = bottom - pageBodyRect.bottom;
            if (overflowHeight > 0 && overflowHeight <= shrinkBlocksHeight - shrinkBlocksMinHeight) {
                const sortedByShrink = Array.from(shrinkByBlock.keys()).sort((img1, img2) => {
                    return shrinkByBlock.get(img2) * dimByBlock.get(img2).height - shrinkByBlock.get(img1) * dimByBlock.get(img1).height;
                });
                let targetShrink = (shrinkBlocksHeight - overflowHeight) / shrinkBlocksHeight;
                let shrinkedCount = 0;
                for (const block of sortedByShrink) {
                    const shrink = Math.max(targetShrink, shrinkByBlock.get(block));
                    const { width, height } = dimByBlock.get(block);
                    const newHeight = height * shrink;
                    if (embeddedContent.includes(block.localName)) {
                        const ratio = width / height;
                        block.style.height = newHeight + 'px';
                        block.style.width = (newHeight * ratio) + 'px';
                        block.style.boxSizing = "border-box";
                    }
                    else {
                        const style = getComputedStyle(block);
                        const newWidth = width * shrink;
                        setTransformMargin(block, style, newWidth - width, newHeight - height);
                        block.style.scale = shrink.toString();
                    }
                    shrinkBlocksHeight -= height;
                    overflowHeight -= (height - newHeight);
                    targetShrink = (shrinkBlocksHeight - overflowHeight) / shrinkBlocksHeight;
                    shrinkedCount++;
                }
                ctx.breakPoint = breakAvoidedParent ? ranges.positionAfter(breakAvoidedParent) : ranges.rangeEnd(breakedBox.range);
                ctx.breakedBox = null;
                logger.info(`${shrinkByBlock.size} blocks shrinked.`, 'sc-scale-to-fit');
                return 'break-point-found';
            }
        }
        if (expandByBlock.size) {
            let underflowHeight = pageBodyRect.bottom - lastBox.bottom;
            const sortedByExpand = Array.from(expandByBlock.keys()).sort((img1, img2) => {
                return expandByBlock.get(img2) * dimByBlock.get(img2).height - expandByBlock.get(img1) * dimByBlock.get(img1).height;
            });
            let targetExpand = (expandBlocksHeight + underflowHeight) / expandBlocksHeight;
            let expandedCount = 0;
            for (const block of sortedByExpand) {
                const expand = Math.min(targetExpand, expandByBlock.get(block));
                const { width, height } = dimByBlock.get(block);
                const newHeight = height * expand;
                if (embeddedContent.includes(block.localName)) {
                    const ratio = width / height;
                    block.style.height = newHeight + 'px';
                    block.style.width = (newHeight * ratio) + 'px';
                    block.style.boxSizing = "border-box";
                }
                else {
                    const style = getComputedStyle(block);
                    const newWidth = width * expand;
                    setTransformMargin(block, style, newWidth - width, newHeight - height);
                    block.style.scale = expand.toString();
                }
                expandBlocksHeight -= height;
                underflowHeight -= (newHeight - height);
                targetExpand = (expandBlocksHeight + underflowHeight) / expandBlocksHeight;
                expandedCount++;
            }
            logger.info(`${expandByBlock.size} blocks expanded.`, 'sc-scale-to-fit');
            return 'break-point-found';
        }
    }
    processor.on('valid-break-point', function () {
        return process.call(this);
    });
    processor.on('no-break-point', function () {
        return process.call(this, !options.allowExpandBeforeBreak);
    });
    if (options.allowExpandBeforeBreak) {
        processor.on('no-overflow', function () {
            return process.call(this);
        });
    }
    function setTransformMargin(block, style, widthDelta, heightDelta) {
        let { marginTop, marginRight, marginBottom, marginLeft } = style;
        const transformOrigin = ["center", "center"];
        if (marginRight != "auto") {
            transformOrigin[0] = "left";
            block.style.marginRight = (parseFloat(marginRight) + widthDelta) + 'px';
        }
        else if (marginLeft != "auto") {
            transformOrigin[0] = "right";
            block.style.marginLeft = (parseFloat(marginLeft) + widthDelta) + 'px';
        }
        if (marginBottom != "auto") {
            transformOrigin[1] = "top";
            block.style.marginBottom = (parseFloat(marginBottom) + heightDelta) + 'px';
        }
        else if (marginTop != "auto") {
            transformOrigin[1] = "bottom";
            block.style.marginTop = (parseFloat(marginTop) + heightDelta) + 'px';
        }
        block.style.transformOrigin = transformOrigin.join(' ');
    }
}, { allowExpandBeforeBreak: false });
