import { __assign, __decorate, __extends, __metadata } from "tslib";
import { ResizeGroupDirection } from './ResizeGroup.types';
import { Component, Prop } from 'vue-property-decorator';
import BaseComponent from '../BaseComponent';
var RESIZE_DELAY = 16;
/**
 * Returns a simple object is able to store measurements with a given key.
 */
export var getMeasurementCache = function () {
    var measurementsCache = {};
    return {
        /**
         * Checks if the provided data has a cacheKey. If it has a cacheKey and there is a
         * corresponding entry in the measurementsCache, then it will return that value.
         * Returns undefined otherwise.
         */
        getCachedMeasurement: function (data) {
            if (data && data.cacheKey && measurementsCache.hasOwnProperty(data.cacheKey)) {
                return measurementsCache[data.cacheKey];
            }
            return undefined;
        },
        /**
         * Should be called whenever there is a new measurement associated with a given data object.
         * If the data has a cacheKey, store that measurement in the measurementsCache.
         */
        addMeasurementToCache: function (data, measurement) {
            if (data.cacheKey) {
                measurementsCache[data.cacheKey] = measurement;
            }
        },
    };
};
/**
 * Returns a function that is able to compute the next state for the ResizeGroup given the current
 * state and any measurement updates.
 */
export var getNextResizeGroupStateProvider = function (measurementCache) {
    if (measurementCache === void 0) { measurementCache = getMeasurementCache(); }
    var _measurementCache = measurementCache;
    var _containerDimension;
    /**
     * Gets the width/height of the data rendered in a hidden div.
     * @param measuredData - The data corresponding to the measurement we wish to take.
     * @param getElementToMeasureDimension - A function that returns the measurement of the rendered data. Only called when the measurement
     * is not in the cache.
     */
    function _getMeasuredDimension(measuredData, getElementToMeasureDimension) {
        var cachedDimension = _measurementCache.getCachedMeasurement(measuredData);
        if (cachedDimension !== undefined) {
            return cachedDimension;
        }
        var measuredDimension = getElementToMeasureDimension();
        _measurementCache.addMeasurementToCache(measuredData, measuredDimension);
        return measuredDimension;
    }
    /**
     * Will get the next IResizeGroupState based on the current data while trying to shrink contents
     * to fit in the container.
     * @param data - The initial data point to start measuring.
     * @param onReduceData - Function that transforms the data into something that should render with less width/height.
     * @param getElementToMeasureDimension - A function that returns the measurement of the rendered data. Only called when the measurement
     * is not in the cache.
     */
    function _shrinkContentsUntilTheyFit(data, onReduceData, getElementToMeasureDimension) {
        var dataToMeasure = data;
        var measuredDimension = _getMeasuredDimension(data, getElementToMeasureDimension);
        while (measuredDimension > _containerDimension) {
            var nextMeasuredData = onReduceData(dataToMeasure);
            // We don't want to get stuck in an infinite render loop when there are no more
            // scaling steps, so implementations of onReduceData should return undefined when
            // there are no more scaling states to apply.
            if (nextMeasuredData === undefined) {
                return {
                    renderedData: dataToMeasure,
                    resizeDirection: undefined,
                    dataToMeasure: undefined,
                };
            }
            measuredDimension = _measurementCache.getCachedMeasurement(nextMeasuredData);
            // If the measurement isn't in the cache, we need to rerender with some data in a hidden div
            if (measuredDimension === undefined) {
                return {
                    dataToMeasure: nextMeasuredData,
                    resizeDirection: 'shrink',
                };
            }
            dataToMeasure = nextMeasuredData;
        }
        return {
            renderedData: dataToMeasure,
            resizeDirection: undefined,
            dataToMeasure: undefined,
        };
    }
    /**
     * This function should be called when the state changes in a manner that might allow for more content to fit
     * on the screen, such as the window width/height growing.
     * @param data - The initial data point to start measuring.
     * @param onGrowData - Function that transforms the data into something that may take up more space when rendering.
     * @param getElementToMeasureDimension - A function that returns the measurement of the rendered data. Only called when the measurement
     * is not in the cache.
     */
    function _growDataUntilItDoesNotFit(data, onGrowData, getElementToMeasureDimension, onReduceData) {
        var dataToMeasure = data;
        var measuredDimension = _getMeasuredDimension(data, getElementToMeasureDimension);
        while (measuredDimension < _containerDimension) {
            var nextMeasuredData = onGrowData(dataToMeasure);
            // We don't want to get stuck in an infinite render loop when there are no more
            // scaling steps, so implementations of onGrowData should return undefined when
            // there are no more scaling states to apply.
            if (nextMeasuredData === undefined) {
                return {
                    renderedData: dataToMeasure,
                    resizeDirection: undefined,
                    dataToMeasure: undefined,
                };
            }
            measuredDimension = _measurementCache.getCachedMeasurement(nextMeasuredData);
            // If the measurement isn't in the cache, we need to rerender with some data in a hidden div
            if (measuredDimension === undefined) {
                return {
                    dataToMeasure: nextMeasuredData,
                };
            }
            dataToMeasure = nextMeasuredData;
        }
        // Once the loop is done, we should now shrink until the contents fit.
        return __assign({ resizeDirection: 'shrink' }, _shrinkContentsUntilTheyFit(dataToMeasure, onReduceData, getElementToMeasureDimension));
    }
    /**
     * Handles an update to the container width/eheight. Should only be called when we knew the previous container width/height.
     * @param newDimension - The new width/height of the container.
     * @param fullDimensionData - The initial data passed in as a prop to resizeGroup.
     * @param renderedData - The data that was rendered prior to the container size changing.
     * @param onGrowData - Set to true if the Resize group has an onGrowData function.
     */
    function _updateContainerDimension(newDimension, fullDimensionData, renderedData, onGrowData) {
        var nextState;
        if (newDimension > _containerDimension) {
            if (onGrowData) {
                nextState = {
                    resizeDirection: 'grow',
                    dataToMeasure: onGrowData(renderedData),
                };
            }
            else {
                nextState = {
                    resizeDirection: 'shrink',
                    dataToMeasure: fullDimensionData,
                };
            }
        }
        else {
            nextState = {
                resizeDirection: 'shrink',
                dataToMeasure: renderedData,
            };
        }
        _containerDimension = newDimension;
        return __assign(__assign({}, nextState), { measureContainer: false });
    }
    function getNextState(props, currentState, getElementToMeasureDimension, newContainerDimension) {
        // If there is no new container width/height or data to measure, there is no need for a new state update
        if (newContainerDimension === undefined && currentState.dataToMeasure === undefined) {
            return undefined;
        }
        if (newContainerDimension) {
            // If we know what the last container size was and we rendered data at that width/height, we can do an optimized render
            if (_containerDimension && currentState.renderedData && !currentState.dataToMeasure) {
                return __assign(__assign({}, currentState), _updateContainerDimension(newContainerDimension, props.data, currentState.renderedData, props.onGrowData));
            }
            // If we are just setting the container width/height for the first time, we can't do any optimizations
            _containerDimension = newContainerDimension;
        }
        var nextState = __assign(__assign({}, currentState), { measureContainer: false });
        if (currentState.dataToMeasure) {
            if (currentState.resizeDirection === 'grow' && props.onGrowData) {
                nextState = __assign(__assign({}, nextState), _growDataUntilItDoesNotFit(currentState.dataToMeasure, props.onGrowData, getElementToMeasureDimension, props.onReduceData));
            }
            else {
                nextState = __assign(__assign({}, nextState), _shrinkContentsUntilTheyFit(currentState.dataToMeasure, props.onReduceData, getElementToMeasureDimension));
            }
        }
        return nextState;
    }
    /** Function that determines if we need to render content for measurement based on the measurement cache contents. */
    function shouldRenderDataForMeasurement(dataToMeasure) {
        if (!dataToMeasure || _measurementCache.getCachedMeasurement(dataToMeasure) !== undefined) {
            return false;
        }
        return true;
    }
    function getInitialResizeGroupState(data) {
        return {
            dataToMeasure: __assign({}, data),
            resizeDirection: 'grow',
            measureContainer: true,
        };
    }
    return {
        getNextState: getNextState,
        shouldRenderDataForMeasurement: shouldRenderDataForMeasurement,
        getInitialResizeGroupState: getInitialResizeGroupState,
    };
};
// Styles for the hidden div used for measurement
var hiddenDivStyles = { position: 'fixed', visibility: 'hidden' };
var hiddenParentStyles = { position: 'relative' };
var ResizeGroupBase = /** @class */ (function (_super) {
    __extends(ResizeGroupBase, _super);
    function ResizeGroupBase() {
        var _this = _super !== null && _super.apply(this, arguments) || this;
        _this.nextResizeGroupStateProvider = getNextResizeGroupStateProvider();
        _this.hasRenderedContent = false;
        _this.dataToMeasure = {};
        _this.measureContainer = false;
        _this.renderedData = {};
        _this.resizeDirection = 'grow';
        return _this;
    }
    ResizeGroupBase.prototype.created = function () {
        var state = this.nextResizeGroupStateProvider.getInitialResizeGroupState(this.data);
        this.dataToMeasure = state.dataToMeasure;
        this.measureContainer = state.measureContainer;
        this.renderedData = state.renderedData;
        this.resizeDirection = state.resizeDirection;
    };
    ResizeGroupBase.prototype.mounted = function () {
        this.events.on(window, 'resize', this._async.debounce(this._onResize, RESIZE_DELAY, { leading: true }));
        this.componentDidUpdate();
    };
    ResizeGroupBase.prototype.updated = function () {
        this.componentDidUpdate();
    };
    ResizeGroupBase.prototype.componentDidUpdate = function () {
        console.log('componentDidUpdate');
        this.dataToMeasure = this.data;
        this.resizeDirection = 'grow';
        this.measureContainer = true;
        if (this.renderedData) {
            this.hasRenderedContent = true;
            if (this.dataDidRender) {
                this.dataDidRender(this.renderedData);
            }
        }
        this._afterComponentRendered(this.direction);
    };
    ResizeGroupBase.prototype._afterComponentRendered = function (direction) {
        var _this = this;
        this._async.requestAnimationFrame(function () {
            var containerDimension;
            if (_this.measureContainer && _this.$refs.root) {
                var boundingRect = _this.$refs.root.getBoundingClientRect();
                containerDimension = direction && direction === ResizeGroupDirection.vertical ? boundingRect.height : boundingRect.width;
            }
            var nextState = _this.nextResizeGroupStateProvider.getNextState(_this.$props, _this.$data, function () {
                var refToMeasure = !_this.hasRenderedContent ? _this.$refs.initialHiddenDiv : _this.$refs.updateHiddenDiv;
                if (!refToMeasure) {
                    return 0;
                }
                return direction && direction === ResizeGroupDirection.vertical
                    ? refToMeasure.scrollHeight
                    : refToMeasure.scrollWidth;
            }, containerDimension);
            if (nextState) {
                _this.dataToMeasure = nextState.dataToMeasure;
                _this.measureContainer = nextState.measureContainer;
                _this.renderedData = nextState.renderedData;
                _this.resizeDirection = nextState.resizeDirection;
            }
        });
    };
    ResizeGroupBase.prototype._onResize = function () {
        if (this.$refs.root) {
            this.measureContainer = true;
        }
    };
    ResizeGroupBase.prototype.render = function (h) {
        var _a = this, className = _a.className, dataToMeasure = _a.dataToMeasure, renderedData = _a.renderedData;
        var dataNeedsMeasuring = this.nextResizeGroupStateProvider.shouldRenderDataForMeasurement(dataToMeasure);
        var isInitialMeasure = !this.hasRenderedContent && dataNeedsMeasuring;
        return h('div', { class: className, attrs: { ref: 'root' } }, [
            h('div', { style: hiddenParentStyles }, [
                dataNeedsMeasuring && !isInitialMeasure && h('div', {
                    ref: 'updateHiddenDiv',
                    style: hiddenDivStyles,
                }, 'Test'),
                h('div', {
                    ref: 'initialHiddenDiv',
                    style: isInitialMeasure ? hiddenDivStyles : undefined,
                }, this.$slots.default),
            ]),
        ]);
    };
    __decorate([
        Prop(),
        __metadata("design:type", Object)
    ], ResizeGroupBase.prototype, "data", void 0);
    __decorate([
        Prop({ type: Number, default: ResizeGroupDirection.horizontal }),
        __metadata("design:type", Object)
    ], ResizeGroupBase.prototype, "direction", void 0);
    __decorate([
        Prop({ type: Function, default: null }),
        __metadata("design:type", Function)
    ], ResizeGroupBase.prototype, "dataDidRender", void 0);
    ResizeGroupBase = __decorate([
        Component
    ], ResizeGroupBase);
    return ResizeGroupBase;
}(BaseComponent));
export { ResizeGroupBase };
//# sourceMappingURL=ResizeGroup.base.js.map