/*!
 * Built by Revolist OU ❤️
 */
import { addMissingItems, getFirstItem, getLastItem, getUpdatedItemsByPosition, clampViewportCoordinate, getViewportMaxCoordinate, isActiveRange, setItemSizes, updateMissingAndRange, isActiveRangeOutsideLastItem, } from "./viewport.helpers";
import { createStore } from "@stencil/store";
import { setStore } from "../../utils";
/**
 * Viewport store
 * Used for virtualization (process of rendering only visible part of data)
 * Redraws viewport based on position and dimension
 */
function initialState() {
    return {
        // virtual item information per rendered item
        items: [],
        // virtual dom item order to render
        start: 0,
        end: 0,
        // size of virtual viewport in px
        virtualSize: 0,
        // total number of items
        realCount: 0,
        // size of viewport in px
        clientSize: 0,
        // logical-to-physical render offset used when scroll space is compressed
        renderOffset: 0,
    };
}
/**
 * Viewport store class
 */
export class ViewportStore {
    get lastCoordinate() {
        return this.lastKnownScroll;
    }
    set lastCoordinate(value) {
        this.lastKnownScroll = value;
    }
    constructor(type) {
        this.type = type;
        // last coordinate for store position restore
        this.lastKnownScroll = 0;
        this.store = createStore(initialState());
    }
    /**
     * Render viewport based on coordinate
     * It's the main method for draw
     * Use force if you want to re-render viewport
     */
    setViewPortCoordinate(position, dimension, force = false) {
        const viewportSize = this.store.get('virtualSize');
        // no visible data to calculate
        if (!viewportSize) {
            return;
        }
        const frameOffset = 1;
        const singleOffsetInPx = dimension.originItemSize * frameOffset;
        // add offset to virtual size from both sides
        const outsize = singleOffsetInPx * 2;
        // math virtual size is based on visible area + 2 items outside of visible area
        const virtualSize = viewportSize + outsize;
        const maxCoordinate = getViewportMaxCoordinate(dimension, viewportSize, frameOffset);
        let pos = clampViewportCoordinate(position, dimension, viewportSize, frameOffset);
        // store last coordinate for further restore on redraw
        this.lastCoordinate = pos;
        // actual position is less than first item start based on offset
        pos -= singleOffsetInPx;
        pos = pos < 0 ? 0 : pos < maxCoordinate ? pos : maxCoordinate;
        let allItems;
        // if force clear all items and start from 0
        if (force) {
            allItems = {
                items: [],
                start: 0,
                end: 0,
            };
        }
        else {
            allItems = this.getItems();
        }
        const firstItem = getFirstItem(allItems);
        const lastItem = getLastItem(allItems);
        let toUpdate = {};
        // left position changed
        // verify if new position is in range of previously rendered first item
        if (!isActiveRange(pos, dimension.realSize, firstItem, lastItem)) {
            toUpdate = Object.assign(Object.assign({}, toUpdate), getUpdatedItemsByPosition(pos, allItems, this.store.get('realCount'), virtualSize, dimension));
            this.setViewport(Object.assign({}, toUpdate));
            // verify is render area is outside of last item
        }
        else if (isActiveRangeOutsideLastItem(pos, virtualSize, firstItem, lastItem)) {
            const items = [...allItems.items];
            // check is any item missing for fulfill content
            const missing = addMissingItems(firstItem, this.store.get('realCount'), virtualSize + pos - firstItem.start, allItems, {
                sizes: dimension.sizes,
                originItemSize: dimension.originItemSize,
            });
            // update missing items
            if (missing.length) {
                const range = {
                    start: this.store.get('start'),
                    end: this.store.get('end'),
                };
                updateMissingAndRange(items, missing, range);
                toUpdate = Object.assign(Object.assign(Object.assign({}, toUpdate), { items: [...items] }), range);
                this.setViewport(Object.assign({}, toUpdate));
            }
        }
    }
    /**
     * Set sizes for existing items
     */
    setOriginalSizes(size) {
        const items = this.store.get('items');
        const count = items.length;
        // viewport not inited
        if (!count) {
            return;
        }
        setStore(this.store, {
            items: setItemSizes(items, this.store.get('start'), size, this.lastCoordinate),
        });
    }
    getItems() {
        return {
            items: this.store.get('items'),
            start: this.store.get('start'),
            end: this.store.get('end'),
        };
    }
    setViewport(data) {
        // drop items on virtual size change, require a new item set
        // drop items on real size change, require a new item set
        if (typeof data.realCount === 'number' || typeof data.virtualSize === 'number') {
            data = Object.assign(Object.assign({}, data), { items: data.items || [] });
        }
        setStore(this.store, data);
    }
}
