/*!
 * Built by Revolist OU ❤️
 */
import { h, Host, } from "@stencil/core";
import GridResizeService from "../revoGrid/viewport.resize.service";
import LocalScrollService, { getContentSize } from "../../services/local.scroll.service";
import { LocalScrollTimer } from "../../services/local.scroll.timer";
import { CONTENT_SLOT, FOOTER_SLOT, HEADER_SLOT, } from "../revoGrid/viewport.helpers";
/**
 * Viewport scroll component for RevoGrid
 * @slot - content
 * @slot header - header
 * @slot footer - footer
 */
export class RevogrViewportScroll {
    constructor() {
        /**
         * Width of inner content
         */
        this.contentWidth = 0;
        /**
         * Height of inner content
         */
        this.contentHeight = 0;
        this.noHorizontalScrollTransfer = false;
    }
    async setScroll(e) {
        var _a;
        this.localScrollTimer.latestScrollUpdate(e.dimension);
        (_a = this.localScrollService) === null || _a === void 0 ? void 0 : _a.setScroll(e);
    }
    /**
     * update on delta in case we don't know existing position or external change
     * @param e
     */
    async changeScroll(e, silent = false) {
        var _a, _b, _c, _d;
        if (silent) {
            if (e.coordinate && this.verticalScroll) {
                switch (e.dimension) {
                    // for mobile devices to skip negative scroll loop. only on vertical scroll
                    case 'rgRow':
                        this.verticalScroll.style.transform = `translateY(${-1 * e.coordinate}px)`;
                        break;
                }
            }
            return;
        }
        if (e.delta) {
            let currentPhysicalCoordinate = 0;
            switch (e.dimension) {
                case 'rgCol':
                    currentPhysicalCoordinate = this.horizontalScroll.scrollLeft;
                    break;
                case 'rgRow':
                    currentPhysicalCoordinate = (_b = (_a = this.verticalScroll) === null || _a === void 0 ? void 0 : _a.scrollTop) !== null && _b !== void 0 ? _b : 0;
                    break;
            }
            return (_d = (_c = this.localScrollService) === null || _c === void 0 ? void 0 : _c.setScrollByDelta(e, currentPhysicalCoordinate)) !== null && _d !== void 0 ? _d : e;
        }
        return e;
    }
    /**
     * Dispatch this event to trigger vertical mouse wheel from plugins
     */
    mousewheelVertical({ detail: e, }) {
        this.verticalMouseWheel(e);
    }
    /**
     * Dispatch this event to trigger horizontal mouse wheel from plugins
     */
    mousewheelHorizontal({ detail: e, }) {
        this.horizontalMouseWheel(e);
    }
    /**
     * Allows to use outside listener
     */
    scrollApply({ detail: { type, coordinate }, }) {
        this.applyOnScroll(type, coordinate, true);
    }
    connectedCallback() {
        /**
         * Bind scroll functions for farther usage
         */
        // allow mousewheel for all devices including mobile
        this.verticalMouseWheel = this.onVerticalMouseWheel.bind(this, 'rgRow', 'deltaY');
        this.horizontalMouseWheel = this.onHorizontalMouseWheel.bind(this, 'rgCol', 'deltaX');
        this.localScrollTimer = new LocalScrollTimer('ontouchstart' in document.documentElement ? 0 : 10);
        /**
         * Create local scroll service
         */
        this.localScrollService = new LocalScrollService({
            // to improve safari smoothnes on scroll
            // skipAnimationFrame: isSafariDesktop(),
            runScroll: e => this.scrollViewport.emit(e),
            applyScroll: e => {
                this.localScrollTimer.setCoordinate(e);
                switch (e.dimension) {
                    case 'rgCol':
                        // this will trigger on scroll event
                        this.horizontalScroll.scrollLeft = e.coordinate;
                        break;
                    case 'rgRow':
                        if (this.verticalScroll) {
                            // this will trigger on scroll event
                            this.verticalScroll.scrollTop = e.coordinate;
                            // for mobile devices to skip negative scroll loop. only on vertical scroll
                            if (this.verticalScroll.style.transform) {
                                this.verticalScroll.style.transform = '';
                            }
                        }
                        break;
                }
            },
        });
    }
    componentDidLoad() {
        // track viewport resize
        this.resizeService = new GridResizeService(this.horizontalScroll, (entry) => {
            var _a, _b, _c, _d, _e, _f, _g, _h;
            const els = {};
            let calculatedHeight = entry.height || 0;
            if (calculatedHeight) {
                calculatedHeight -=
                    ((_b = (_a = this.header) === null || _a === void 0 ? void 0 : _a.clientHeight) !== null && _b !== void 0 ? _b : 0) +
                        ((_d = (_c = this.footer) === null || _c === void 0 ? void 0 : _c.clientHeight) !== null && _d !== void 0 ? _d : 0);
            }
            els.rgRow = {
                size: calculatedHeight,
                contentSize: this.contentHeight,
                scroll: (_f = (_e = this.verticalScroll) === null || _e === void 0 ? void 0 : _e.scrollTop) !== null && _f !== void 0 ? _f : 0,
                noScroll: false,
            };
            const calculatedWidth = entry.width || 0;
            els.rgCol = {
                size: calculatedWidth,
                contentSize: this.contentWidth,
                scroll: this.horizontalScroll.scrollLeft,
                noScroll: this.colType !== 'rgCol',
            };
            this.setScrollParams({
                rgRow: calculatedHeight,
                rgCol: calculatedWidth,
            });
            // Process changes in order: width first, then height
            const dimensions = ['rgCol', 'rgRow'];
            for (const dimension of dimensions) {
                const item = els[dimension];
                if (!item)
                    continue;
                this.resizeViewport.emit({
                    dimension,
                    size: item.size,
                    rowHeader: this.rowHeader,
                });
                if (item.noScroll) {
                    continue;
                }
                (_g = this.localScrollService) === null || _g === void 0 ? void 0 : _g.scroll((_h = item.scroll) !== null && _h !== void 0 ? _h : 0, dimension, true);
                // track scroll visibility on outer element change
                this.setScrollVisibility(dimension, item.size, item.contentSize);
            }
        }, [this.footer, this.header]);
    }
    /**
     * Check if scroll present or not per type
     * Trigger this method on inner content size change or on outer element size change
     * If inner content bigger then outer size then scroll is present and mousewheel binding required
     * @param type - dimension type 'rgRow/y' or 'rgCol/x'
     * @param size - outer content size
     * @param innerContentSize - inner content size
     */
    setScrollVisibility(type, size, innerContentSize) {
        // test if scroll present
        const hasScroll = size < innerContentSize;
        let el;
        // event reference for binding
        switch (type) {
            case 'rgCol':
                el = this.horizontalScroll;
                break;
            case 'rgRow':
                el = this.verticalScroll;
                break;
        }
        // based on scroll visibility assign or remove class and event
        if (hasScroll) {
            el === null || el === void 0 ? void 0 : el.classList.add(`scroll-${type}`);
        }
        else {
            el === null || el === void 0 ? void 0 : el.classList.remove(`scroll-${type}`);
        }
        this.scrollchange.emit({ type, hasScroll });
    }
    disconnectedCallback() {
        var _a;
        (_a = this.resizeService) === null || _a === void 0 ? void 0 : _a.destroy();
    }
    async componentDidRender() {
        var _a, _b, _c, _d;
        this.setScrollParams({
            rgRow: (_b = (_a = this.verticalScroll) === null || _a === void 0 ? void 0 : _a.clientHeight) !== null && _b !== void 0 ? _b : 0,
            rgCol: this.horizontalScroll.clientWidth,
        });
        this.setScrollVisibility('rgRow', (_d = (_c = this.verticalScroll) === null || _c === void 0 ? void 0 : _c.clientHeight) !== null && _d !== void 0 ? _d : 0, this.contentHeight);
        this.setScrollVisibility('rgCol', this.horizontalScroll.clientWidth, this.contentWidth);
    }
    setScrollParams(clientSize) {
        this.localScrollService.setParams({
            contentSize: this.contentHeight,
            clientSize: clientSize.rgRow,
            virtualSize: 0,
        }, 'rgRow');
        this.localScrollService.setParams({
            contentSize: this.contentWidth,
            clientSize: clientSize.rgCol,
            virtualSize: 0,
        }, 'rgCol');
    }
    render() {
        var _a, _b;
        const physicalContentHeight = getContentSize(this.contentHeight, (_b = (_a = this.verticalScroll) === null || _a === void 0 ? void 0 : _a.clientHeight) !== null && _b !== void 0 ? _b : 0);
        const physicalContentWidth = getContentSize(this.contentWidth, 0);
        return (h(Host, { key: 'ec8d907976c1d50f7aab3c263be3f0249a274df6', onWheel: this.horizontalMouseWheel, onScroll: (e) => this.applyScroll('rgCol', e) }, h("div", { key: 'e35696a7993ac94261426b45c28d488cdc42b7f0', class: "inner-content-table", style: { width: `${physicalContentWidth}px` } }, h("div", { key: 'a6997451e01eacda1d27d4efa1d74e1748626218', class: "header-wrapper", ref: e => (this.header = e) }, h("slot", { key: '1d401e87d32d5b1531c2211723b552bbc894f22c', name: HEADER_SLOT })), h("div", { key: 'ceab6f9e812d6ca9a0aa376afcd2562a17f505e0', class: "vertical-inner", ref: el => (this.verticalScroll = el), onWheel: this.verticalMouseWheel, onScroll: (e) => this.applyScroll('rgRow', e) }, h("div", { key: 'a9556578a23d6efddec2e982e863aec064042154', class: "content-wrapper", style: { height: `${physicalContentHeight}px` } }, h("slot", { key: '0ae01f9736b9740612e75261f6e3abebda533377', name: CONTENT_SLOT }))), h("div", { key: '09c2565d4ed449a43820f92d97b6558fca3758e7', class: "footer-wrapper", ref: e => (this.footer = e) }, h("slot", { key: '1ffb08ff8138a560cc09d82e3fe22a53e502aafe', name: FOOTER_SLOT })))));
    }
    /**
     * Extra layer for scroll event monitoring, where MouseWheel event is not passing
     * We need to trigger scroll event in case there is no mousewheel event
     */
    async applyScroll(type, e) {
        if (!(e.target instanceof HTMLElement)) {
            return;
        }
        let scroll = 0;
        switch (type) {
            case 'rgCol':
                scroll = e.target.scrollLeft;
                break;
            case 'rgRow':
                scroll = e.target.scrollTop;
                break;
        }
        // for mobile devices to skip negative scroll loop
        if (scroll < 0) {
            this.silentScroll.emit({ dimension: type, coordinate: scroll });
            return;
        }
        this.applyOnScroll(type, scroll);
    }
    /**
     * Applies change on scroll event only if mousewheel event happened some time ago
     */
    applyOnScroll(type, coordinate, outside = false) {
        const lastScrollUpdate = () => {
            var _a;
            (_a = this.localScrollService) === null || _a === void 0 ? void 0 : _a.scroll(coordinate, type, undefined, undefined, outside);
        };
        // apply after throttling
        if (this.localScrollTimer.isReady(type, coordinate)) {
            lastScrollUpdate();
        }
        else {
            this.localScrollTimer.throttleLastScrollUpdate(type, coordinate, () => lastScrollUpdate());
        }
    }
    /**
     * On vertical mousewheel event
     * @param type
     * @param delta
     * @param e
     */
    onVerticalMouseWheel(type, delta, e) {
        var _a, _b, _c, _d, _e, _f, _g, _h;
        const scrollTop = (_b = (_a = this.verticalScroll) === null || _a === void 0 ? void 0 : _a.scrollTop) !== null && _b !== void 0 ? _b : 0;
        const clientHeight = (_d = (_c = this.verticalScroll) === null || _c === void 0 ? void 0 : _c.clientHeight) !== null && _d !== void 0 ? _d : 0;
        const scrollHeight = (_f = (_e = this.verticalScroll) === null || _e === void 0 ? void 0 : _e.scrollHeight) !== null && _f !== void 0 ? _f : 0;
        // Detect if the user has reached the bottom
        const atBottom = scrollTop + clientHeight >= scrollHeight && e.deltaY > 0;
        const atTop = scrollTop === 0 && e.deltaY < 0;
        if (!atBottom && !atTop) {
            (_g = e.preventDefault) === null || _g === void 0 ? void 0 : _g.call(e);
        }
        const pos = scrollTop + e[delta];
        (_h = this.localScrollService) === null || _h === void 0 ? void 0 : _h.scroll(pos, type, undefined, e[delta]);
        this.localScrollTimer.latestScrollUpdate(type);
    }
    /**
     * On horizontal mousewheel event
     * @param type
     * @param delta
     * @param e
     */
    onHorizontalMouseWheel(type, delta, e) {
        var _a, _b, _c, _d;
        if (!e.deltaX) {
            return;
        }
        const { scrollLeft, scrollWidth, clientWidth } = this.horizontalScroll;
        // Detect if the user has reached the right end
        const atRight = scrollLeft + clientWidth >= scrollWidth && e.deltaX > 0;
        // Detect if the user has reached the left end
        const atLeft = scrollLeft === 0 && e.deltaX < 0;
        if (this.noHorizontalScrollTransfer) {
            if (!atRight && !atLeft) {
                const nextScrollLeft = scrollLeft + e[delta];
                (_a = e.preventDefault) === null || _a === void 0 ? void 0 : _a.call(e);
                this.horizontalScroll.scrollLeft = nextScrollLeft;
                (_b = this.localScrollService) === null || _b === void 0 ? void 0 : _b.scroll(this.horizontalScroll.scrollLeft, type, undefined, e[delta]);
                this.localScrollTimer.latestScrollUpdate(type);
            }
            return;
        }
        if (!atRight && !atLeft) {
            (_c = e.preventDefault) === null || _c === void 0 ? void 0 : _c.call(e);
        }
        const pos = scrollLeft + e[delta];
        (_d = this.localScrollService) === null || _d === void 0 ? void 0 : _d.scroll(pos, type, undefined, e[delta]);
        this.localScrollTimer.latestScrollUpdate(type);
    }
    static get is() { return "revogr-viewport-scroll"; }
    static get originalStyleUrls() {
        return {
            "$": ["revogr-viewport-scroll-style.scss"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["revogr-viewport-scroll-style.css"]
        };
    }
    static get properties() {
        return {
            "rowHeader": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Enable row header"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "row-header"
            },
            "contentWidth": {
                "type": "number",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Width of inner content"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "content-width",
                "defaultValue": "0"
            },
            "contentHeight": {
                "type": "number",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Height of inner content"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "content-height",
                "defaultValue": "0"
            },
            "colType": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "DimensionCols | 'rowHeaders'",
                    "resolved": "\"colPinEnd\" | \"colPinStart\" | \"rgCol\" | \"rowHeaders\"",
                    "references": {
                        "DimensionCols": {
                            "location": "import",
                            "path": "@type",
                            "id": "src/types/index.ts::DimensionCols",
                            "referenceLocation": "DimensionCols"
                        }
                    }
                },
                "required": true,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": ""
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "col-type"
            },
            "noHorizontalScrollTransfer": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": ""
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "no-horizontal-scroll-transfer",
                "defaultValue": "false"
            }
        };
    }
    static get events() {
        return [{
                "method": "scrollViewport",
                "name": "scrollviewport",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Before scroll event"
                },
                "complexType": {
                    "original": "ViewPortScrollEvent",
                    "resolved": "D",
                    "references": {
                        "ViewPortScrollEvent": {
                            "location": "import",
                            "path": "@type",
                            "id": "src/types/index.ts::ViewPortScrollEvent",
                            "referenceLocation": "ViewPortScrollEvent"
                        }
                    }
                }
            }, {
                "method": "resizeViewport",
                "name": "resizeviewport",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Viewport resize"
                },
                "complexType": {
                    "original": "ViewPortResizeEvent",
                    "resolved": "{ dimension: DimensionType; size: number; rowHeader?: boolean | undefined; }",
                    "references": {
                        "ViewPortResizeEvent": {
                            "location": "import",
                            "path": "@type",
                            "id": "src/types/index.ts::ViewPortResizeEvent",
                            "referenceLocation": "ViewPortResizeEvent"
                        }
                    }
                }
            }, {
                "method": "scrollchange",
                "name": "scrollchange",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Triggered on scroll change, can be used to get information about scroll visibility"
                },
                "complexType": {
                    "original": "{\n    type: DimensionType;\n    hasScroll: boolean;\n  }",
                    "resolved": "{ type: DimensionType; hasScroll: boolean; }",
                    "references": {
                        "DimensionType": {
                            "location": "import",
                            "path": "@type",
                            "id": "src/types/index.ts::DimensionType",
                            "referenceLocation": "DimensionType"
                        }
                    }
                }
            }, {
                "method": "silentScroll",
                "name": "scrollviewportsilent",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Silently scroll to coordinate\nMade to align negative coordinates for mobile devices"
                },
                "complexType": {
                    "original": "ViewPortScrollEvent",
                    "resolved": "D",
                    "references": {
                        "ViewPortScrollEvent": {
                            "location": "import",
                            "path": "@type",
                            "id": "src/types/index.ts::ViewPortScrollEvent",
                            "referenceLocation": "ViewPortScrollEvent"
                        }
                    }
                }
            }];
    }
    static get methods() {
        return {
            "setScroll": {
                "complexType": {
                    "signature": "(e: ViewPortScrollEvent) => Promise<void>",
                    "parameters": [{
                            "name": "e",
                            "type": "{ dimension: DimensionType; coordinate: number; delta?: number | undefined; outside?: boolean | undefined; }",
                            "docs": ""
                        }],
                    "references": {
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        },
                        "ViewPortScrollEvent": {
                            "location": "import",
                            "path": "@type",
                            "id": "src/types/index.ts::ViewPortScrollEvent",
                            "referenceLocation": "ViewPortScrollEvent"
                        }
                    },
                    "return": "Promise<void>"
                },
                "docs": {
                    "text": "",
                    "tags": []
                }
            },
            "changeScroll": {
                "complexType": {
                    "signature": "(e: ViewPortScrollEvent, silent?: boolean) => Promise<ViewPortScrollEvent | undefined>",
                    "parameters": [{
                            "name": "e",
                            "type": "{ dimension: DimensionType; coordinate: number; delta?: number | undefined; outside?: boolean | undefined; }",
                            "docs": ""
                        }, {
                            "name": "silent",
                            "type": "boolean",
                            "docs": ""
                        }],
                    "references": {
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        },
                        "ViewPortScrollEvent": {
                            "location": "import",
                            "path": "@type",
                            "id": "src/types/index.ts::ViewPortScrollEvent",
                            "referenceLocation": "ViewPortScrollEvent"
                        }
                    },
                    "return": "Promise<ViewPortScrollEvent | undefined>"
                },
                "docs": {
                    "text": "update on delta in case we don't know existing position or external change",
                    "tags": [{
                            "name": "param",
                            "text": "e"
                        }]
                }
            },
            "applyScroll": {
                "complexType": {
                    "signature": "(type: DimensionType, e: UIEvent) => Promise<void>",
                    "parameters": [{
                            "name": "type",
                            "type": "\"rgCol\" | \"rgRow\"",
                            "docs": ""
                        }, {
                            "name": "e",
                            "type": "UIEvent",
                            "docs": ""
                        }],
                    "references": {
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        },
                        "DimensionType": {
                            "location": "import",
                            "path": "@type",
                            "id": "src/types/index.ts::DimensionType",
                            "referenceLocation": "DimensionType"
                        },
                        "UIEvent": {
                            "location": "global",
                            "id": "global::UIEvent"
                        }
                    },
                    "return": "Promise<void>"
                },
                "docs": {
                    "text": "Extra layer for scroll event monitoring, where MouseWheel event is not passing\nWe need to trigger scroll event in case there is no mousewheel event",
                    "tags": []
                }
            }
        };
    }
    static get elementRef() { return "horizontalScroll"; }
    static get listeners() {
        return [{
                "name": "mousewheel-vertical",
                "method": "mousewheelVertical",
                "target": undefined,
                "capture": false,
                "passive": false
            }, {
                "name": "mousewheel-horizontal",
                "method": "mousewheelHorizontal",
                "target": undefined,
                "capture": false,
                "passive": false
            }, {
                "name": "scroll-coordinate",
                "method": "scrollApply",
                "target": undefined,
                "capture": false,
                "passive": false
            }];
    }
}
