var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
        r = Reflect.decorate(decorators, target, key, desc);
    else
        for (var i = decorators.length - 1; i >= 0; i--)
            if (d = decorators[i])
                r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { h, writeTask } from "@stencil/core";
import { defaultIconPack } from "../../exports";
import { SyncWithStore } from "../../utils/sync-with-store";
import { debounce } from "lodash-es";
const MAX_VISIBLE_ITEMS = 20;
const OVERSCAN_BUFFER = 5;
/**
 * A component which renders list of messages.
 */
export class RtkMessageListView {
    constructor() {
        /** Maximum visible messages */
        this.visibleItemsCount = MAX_VISIBLE_ITEMS;
        /** Estimated height of an item */
        this.estimateItemSize = 100;
        /** Icon pack */
        this.iconPack = defaultIconPack;
        this.isFetching = false;
        this.autoScroll = true;
        this.totalHeight = 0;
        this.sizes = new Map();
        this.lastScrollTop = 0;
        this.scrollToBottomRetries = 0;
        this.elementObserver = (() => {
            let _ro = null;
            const get = () => {
                if (_ro) {
                    return _ro;
                }
                else if (typeof ResizeObserver !== 'undefined') {
                    return (_ro = new ResizeObserver((entries) => {
                        entries.forEach((entry) => {
                            this.measureElement(entry.target, entry);
                        });
                    }));
                }
                else {
                    return null;
                }
            };
            return {
                disconnect: () => { var _a; return (_a = get()) === null || _a === void 0 ? void 0 : _a.disconnect(); },
                observe: (target) => { var _a; return (_a = get()) === null || _a === void 0 ? void 0 : _a.observe(target, { box: 'border-box' }); },
                unobserve: (target) => { var _a; return (_a = get()) === null || _a === void 0 ? void 0 : _a.unobserve(target); },
            };
        })();
        this.measureElement = (node, entry) => {
            if (!node)
                return;
            const id = node.dataset.id;
            if (this.sizes.has(id)) {
                this.elementObserver.unobserve(node);
                return;
            }
            if (entry) {
                const box = entry.borderBoxSize[0];
                if (box && box.blockSize > 0) {
                    this.saveItemSize(id, box.blockSize);
                    this.elementObserver.unobserve(node);
                    return;
                }
            }
            const rect = node.getBoundingClientRect();
            if (rect.height > 0)
                this.saveItemSize(id, rect.height);
        };
        this.getVisibleItems = () => {
            return this.messages.slice(this.range.start, this.range.end + 1);
        };
        this.updateVisibleItems = (start, end) => {
            const total = this.messages.length;
            let newStart = start;
            let newEnd = end;
            if (total <= this.visibleItemsCount) {
                // render all
                newStart = 0;
                newEnd = total - 1;
            }
            else if (end - start < this.visibleItemsCount - 1) {
                // if range is less then visible, adjust start based on end
                newStart = this.range.end - this.visibleItemsCount + 1;
            }
            if (this.range.start !== newStart) {
                this.range = { start: newStart, end: newEnd };
                this.totalHeight = this.getRangeSize(0, total);
            }
        };
        this.getEstimatedItemSize = () => {
            return this.estimateItemSize;
        };
        this.getRangeSize = (start, end) => {
            let total = 0;
            let itemSize = 0;
            for (let index = start; index < end; index++) {
                itemSize = this.sizes.get(this.messages[index].id);
                total = total + (!!itemSize ? itemSize : this.getEstimatedItemSize());
            }
            return total;
        };
        this.getScrollTop = () => {
            return this.$listRef ? Math.ceil(this.$listRef.scrollTop) : 0;
        };
        this.getClientHeight = () => {
            return this.$listRef ? Math.ceil(this.$listRef.clientHeight) : 0;
        };
        this.getScrollHeight = () => {
            return this.$listRef ? Math.ceil(this.$listRef.scrollHeight) : 0;
        };
        this.getItemsScrolled = () => {
            const offset = this.lastScrollTop;
            if (offset <= 0) {
                return 0;
            }
            let low = 0;
            let middle = 0;
            let middleOffset = 0;
            let high = this.messages.length;
            while (low <= high) {
                middle = (low + high) >>> 1;
                middleOffset = this.getRangeSize(0, middle);
                if (middleOffset === offset) {
                    return middle;
                }
                else if (middleOffset < offset) {
                    low = middle + 1;
                }
                else if (middleOffset > offset) {
                    high = middle - 1;
                }
            }
            return low > 0 ? --low : 0;
        };
        this.getEndByStart = (start) => {
            return Math.min(start + this.visibleItemsCount, this.messages.length - 1);
        };
        this.scrollToOffset = (offset) => {
            if (this.$listRef) {
                this.$listRef.scrollTop = offset;
            }
        };
        this.scrollToIndex = (index) => {
            if (index >= this.messages.length - 1) {
                this.scrollToBottom();
            }
            else {
                const offset = index < 1 ? 0 : this.getRangeSize(0, index);
                this.scrollToOffset(offset);
            }
        };
        this.scrollToBottom = () => {
            if (!this.$listEndRef)
                return;
            writeTask(() => {
                this.$listEndRef.scrollIntoView();
                if (this.getScrollHeight() - (this.getScrollTop() + this.getClientHeight()) > 0 &&
                    this.scrollToBottomRetries < 10) {
                    setTimeout(() => {
                        this.scrollToBottom();
                    }, 1000 / 60);
                }
                else {
                    this.scrollToBottomRetries = 0;
                    this.autoScroll = true;
                }
            });
        };
        this.handleScroll = async () => {
            if (this.isFetching)
                return;
            const scrollTop = this.getScrollTop();
            const direction = scrollTop < this.lastScrollTop || scrollTop === 0 ? 'UP' : 'DOWN';
            this.lastScrollTop = scrollTop;
            if (this.loadMore && scrollTop === 0 && direction === 'UP' && this.isFetching === false) {
                this.isFetching = true;
                const newMessages = await this.loadMore(this.messages[0]);
                if (newMessages && newMessages.length) {
                    this.messages = [...newMessages, ...this.messages];
                }
                this.isFetching = false;
            }
            if (direction === 'UP') {
                this.handleTop();
            }
            else if (direction === 'DOWN') {
                this.handleBottom();
            }
        };
        this.handleTop = () => {
            const scrolledItems = this.getItemsScrolled();
            if (scrolledItems <= this.range.end - OVERSCAN_BUFFER) {
                this.autoScroll = false;
            }
            if (scrolledItems > this.range.start + OVERSCAN_BUFFER) {
                return;
            }
            const newStart = Math.max(this.range.start - OVERSCAN_BUFFER, 0);
            this.updateVisibleItems(newStart, this.getEndByStart(newStart));
        };
        this.handleBottom = () => {
            const scrolledItems = this.getItemsScrolled();
            if (scrolledItems < this.range.start + OVERSCAN_BUFFER) {
                return;
            }
            const newStart = this.range.start + OVERSCAN_BUFFER;
            const newEnd = this.getEndByStart(newStart);
            if (newEnd === this.messages.length - 1) {
                this.updateVisibleItems(newEnd - this.visibleItemsCount, newEnd);
            }
            else {
                this.updateVisibleItems(newStart, newEnd);
            }
        };
        this.updateTotalHeight = debounce(() => {
            this.totalHeight = this.getRangeSize(0, this.messages.length);
        }, 1000 / 30, { leading: true });
        this.rendererInternal = (containerElement, message, index) => {
            if (!containerElement)
                return;
            if (containerElement.dataset.id === message.id)
                return;
            const viewElement = this.renderer(message, index);
            if (containerElement.hasChildNodes) {
                containerElement.innerHTML = '';
            }
            this.elementObserver.observe(containerElement);
            containerElement.dataset.id = message.id;
            containerElement.appendChild(viewElement);
        };
    }
    connectedCallback() {
        const total = this.messages.length - 1;
        this.range = { start: total - this.visibleItemsCount, end: total };
        this.updateVisibleItems(this.range.start, this.range.end);
        this.totalHeight = this.getRangeSize(0, total);
    }
    componentDidLoad() {
        if (this.autoScroll) {
            this.scrollToBottom();
        }
    }
    messagesUpdated(newValue, previousValue) {
        if (newValue.length > previousValue.length) {
            const diff = newValue.length - previousValue.length;
            this.updateVisibleItems(diff, this.getEndByStart(diff));
            this.scrollToIndex(this.range.start);
        }
    }
    saveItemSize(id, height) {
        this.sizes.set(id, Math.round(height));
        this.updateTotalHeight();
    }
    render() {
        return (h("div", { key: '6564a301cf646ed53993d924111206cf4a382833', class: "scrollbar content-wrapper", ref: (el) => (this.$listRef = el), onScroll: this.handleScroll }, h("div", { key: '81469bde1e421f0a881c3ad33b30a601fc54901c', class: "scroller" }, h("div", { key: '5e0c8dd617aa7ed5517cde108539dc23265a29e2', style: {
                height: `${this.totalHeight}px`,
            } }), h("div", { key: 'f994d2627b4787f803b9c7ac00f574dbd034f924', class: "smallest-dom-element", id: "list-end", ref: (el) => (this.$listEndRef = el) })), h("div", { key: '4de62649e894f024ecb7d5767f6eefc702544964', class: "content", style: {
                transform: `translateY(${this.getRangeSize(0, this.range.start)}px)`,
            } }, this.isFetching && (h("div", { key: 'dfa9a9abbc190896f80aa90191ab654fee7b7d8f', class: "loader" }, h("rtk-spinner", { key: '416b800f9d481689860340aff00fe30f30255603', size: "md" }))), this.getVisibleItems().map((msg, index) => (h("div", { key: msg.id, ref: (el) => this.rendererInternal(el, msg, index) }))))));
    }
    static get is() { return "rtk-message-list-view"; }
    static get originalStyleUrls() {
        return {
            "$": ["rtk-message-list-view.css"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["rtk-message-list-view.css"]
        };
    }
    static get properties() {
        return {
            "messages": {
                "type": "unknown",
                "mutable": false,
                "complexType": {
                    "original": "Message[]",
                    "resolved": "Message[]",
                    "references": {
                        "Message": {
                            "location": "global",
                            "id": "global::Message"
                        }
                    }
                },
                "required": true,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Messages to render"
                },
                "getter": false,
                "setter": false
            },
            "renderer": {
                "type": "unknown",
                "mutable": false,
                "complexType": {
                    "original": "(message: Message, index: number) => HTMLElement",
                    "resolved": "(message: Message, index: number) => HTMLElement",
                    "references": {
                        "Message": {
                            "location": "global",
                            "id": "global::Message"
                        },
                        "HTMLElement": {
                            "location": "global",
                            "id": "global::HTMLElement"
                        }
                    }
                },
                "required": true,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Render function of the message"
                },
                "getter": false,
                "setter": false
            },
            "loadMore": {
                "type": "unknown",
                "mutable": false,
                "complexType": {
                    "original": "(lastMessage: Message) => Promise<Message[]>",
                    "resolved": "(lastMessage: Message) => Promise<Message[]>",
                    "references": {
                        "Message": {
                            "location": "global",
                            "id": "global::Message"
                        },
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Function to load more messages.\nMessages returned from this will be preprended"
                },
                "getter": false,
                "setter": false
            },
            "visibleItemsCount": {
                "type": "number",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Maximum visible messages"
                },
                "getter": false,
                "setter": false,
                "attribute": "visible-items-count",
                "reflect": false,
                "defaultValue": "MAX_VISIBLE_ITEMS"
            },
            "estimateItemSize": {
                "type": "number",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Estimated height of an item"
                },
                "getter": false,
                "setter": false,
                "attribute": "estimate-item-size",
                "reflect": false,
                "defaultValue": "100"
            },
            "iconPack": {
                "type": "unknown",
                "mutable": false,
                "complexType": {
                    "original": "IconPack",
                    "resolved": "{ people: string; people_checked: string; chat: string; poll: string; participants: string; rocket: string; call_end: string; share: string; mic_on: string; mic_off: string; video_on: string; video_off: string; share_screen_start: string; share_screen_stop: string; share_screen_person: string; clock: string; dismiss: string; send: string; search: string; more_vertical: string; chevron_down: string; chevron_up: string; chevron_left: string; chevron_right: string; settings: string; wifi: string; speaker: string; speaker_off: string; download: string; full_screen_maximize: string; full_screen_minimize: string; copy: string; attach: string; image: string; emoji_multiple: string; image_off: string; disconnected: string; wand: string; recording: string; subtract: string; stop_recording: string; warning: string; pin: string; pin_off: string; spinner: string; breakout_rooms: string; add: string; shuffle: string; edit: string; delete: string; back: string; save: string; web: string; checkmark: string; spotlight: string; join_stage: string; leave_stage: string; pip_off: string; pip_on: string; signal_1: string; signal_2: string; signal_3: string; signal_4: string; signal_5: string; start_livestream: string; stop_livestream: string; viewers: string; debug: string; info: string; devices: string; horizontal_dots: string; ai_sparkle: string; meeting_ai: string; captionsOn: string; captionsOff: string; play: string; pause: string; fastForward: string; minimize: string; maximize: string; }",
                    "references": {
                        "IconPack": {
                            "location": "import",
                            "path": "../../exports",
                            "id": "src/exports.ts::IconPack"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Icon pack"
                },
                "getter": false,
                "setter": false,
                "defaultValue": "defaultIconPack"
            }
        };
    }
    static get states() {
        return {
            "range": {},
            "isFetching": {},
            "autoScroll": {},
            "totalHeight": {}
        };
    }
    static get watchers() {
        return [{
                "propName": "messages",
                "methodName": "messagesUpdated"
            }];
    }
}
__decorate([
    SyncWithStore()
], RtkMessageListView.prototype, "iconPack", void 0);
