import { h, Host } from "@stencil/core";
import { PlatformServiceName, } from "@limetech/lime-web-components";
import { processChatItems, reverseProcessedItems, } from "./process-chat-items";
import { ScrollAnchor } from "./scroll-anchor";
import { createHeightTransitionEndHandler, verifyHeightAnimationProperty, } from "./typing-indicator-scroll";
/**
 * Chat list
 * This component displays a list of chat items, and groups them by date.
 * Items in the list have a `ui` property that can be set to `mine` or `theirs`
 * to indicate whether they are incoming or outgoing messages.
 *
 * @exampleComponent limebb-example-chat-list-basic
 * @exampleComponent limebb-example-chat-list-others-are-typing
 * @exampleComponent limebb-example-chat-list-new-messages
 * @exampleComponent limebb-example-chat-list-reverse-order
 * @exampleComponent limebb-example-chat-list-with-promoted-action
 * @exampleComponent limebb-example-chat-list-with-helper-text
 * @exampleComponent limebb-example-chat-list-with-error
 * @exampleComponent limebb-example-chat-list-with-icon-list
 * @exampleComponent limebb-example-chat-list-with-wide-content
 * @beta
 */
export class ChatList {
    constructor() {
        /**
         * List of items to display in the feed.
         * These items must be sorted by their `timestamp` in ascending order
         * (oldest first, newest last). This matches the natural visual order
         * for the default `oldest-on-top` layout, keeping DOM order aligned
         * with visual order for copy/paste, screen readers, and tab order.
         *
         * See the [ChatListItem](#/type/ChatListItem/) type for the kinds of
         * items that can appear in the list.
         */
        this.items = [];
        /**
         * - `newest-on-top` displays new messages at the top of the list;
         * used if the chat input is located on top of the chat interface.
         * - `oldest-on-top` displays new messages at the bottom of the list (default);
         * used if the chat input is located at the bottom of the chat interface.
         *
         * :::important
         * Using this prop will _not_ automatically sort the chat items for you!
         * You still need to pass the items in the correct order (ascending order by timestamp — oldest first).
         * This prop only affects the direction of the elements in the user interface,
         * such as the location of the typing indicator or the scrollbars.
         * :::
         */
        this.order = 'oldest-on-top';
        this.processedItems = [];
        this.handleHeightTransitionEnd = createHeightTransitionEndHandler(() => { var _a; return (_a = this.scrollAnchor) === null || _a === void 0 ? void 0 : _a.repinIfSticky(); });
    }
    handleItemsChange() {
        let lastVisited = null;
        if (this.lastVisitedTimestamp) {
            lastVisited = parseToUTCDate(this.lastVisitedTimestamp);
        }
        const processed = processChatItems(this.items, this.dateTimeFormatter, lastVisited);
        this.processedItems =
            this.order === 'newest-on-top'
                ? reverseProcessedItems(processed)
                : processed;
    }
    componentWillLoad() {
        this.handleItemsChange();
        this.scrollAnchor = new ScrollAnchor(this.host, () => this.order, this.order);
    }
    componentDidLoad() {
        var _a;
        verifyHeightAnimationProperty((_a = this.host.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.typing-indicator-height-animation'));
    }
    componentWillRender() {
        if (this.items.length === 0) {
            // Nothing to measure or scroll against. `isSticky` retains
            // its last value (default `true`), so the first non-empty
            // render still pins correctly.
            return;
        }
        this.scrollAnchor.beforeRender();
    }
    componentDidRender() {
        if (this.items.length === 0) {
            return;
        }
        this.scrollAnchor.afterRender();
    }
    render() {
        return (h(Host, { key: '1684ed7ba9da966e3c8b8c1c9615738b825b28fa', class: {
                'is-empty': this.items.length === 0 &&
                    !this.isTypingIndicatorVisible,
            }, "aria-busy": this.isBusy(), role: "log" }, this.renderSpinner(), this.order === 'newest-on-top' &&
            this.renderTypingIndicatorBlock(), this.renderChatItems(), this.order === 'oldest-on-top' &&
            this.renderTypingIndicatorBlock()));
    }
    renderSpinner() {
        if (!this.loading) {
            return;
        }
        return h("limel-spinner", null);
    }
    renderTypingIndicatorBlock() {
        // The indicator's grid-template-rows transition grows the
        // container over ~0.46s. `componentDidRender` fires before the
        // transition starts, so `scrollHeight` at that moment doesn't
        // include the final height. `handleHeightTransitionEnd` re-pins
        // once the transition lands.
        return (h("div", { class: "typing-indicator-height-animation", onTransitionEnd: this.handleHeightTransitionEnd }, h("div", null, this.renderTypingIndicator())));
    }
    renderTypingIndicator() {
        if (!this.isTypingIndicatorVisible) {
            return;
        }
        return h("limebb-typing-indicator", null);
    }
    renderChatItems() {
        return this.processedItems.map((item) => {
            switch (item.type) {
                case 'group': {
                    return this.renderGroup(item);
                }
                case 'newItemIndicator': {
                    return this.renderIndicator();
                }
                case 'chat':
                case 'iconList': {
                    // `insertDateGroups` wraps every `chat` / `iconList`
                    // in a `group`, so these variants shouldn't appear
                    // at top level. If the processor pipeline is ever
                    // reordered, render nothing rather than double-
                    // render once the wrapping group includes them.
                    return null;
                }
                default: {
                    return assertNever(item);
                }
            }
        });
    }
    renderGroup(group) {
        const chatItems = group.items.map((item) => {
            switch (item.type) {
                case 'chat': {
                    return this.renderChatItem(item.item);
                }
                case 'iconList': {
                    return this.renderIconList(item.item);
                }
                case 'newItemIndicator': {
                    return this.renderIndicator();
                }
                case 'group': {
                    // Date groups don't nest. A compile-time `never`
                    // here would require a separate nested-item type;
                    // this runtime null return is the pragmatic
                    // middle ground.
                    return null;
                }
                default: {
                    return assertNever(item);
                }
            }
        });
        // Key the section by the group's heading (unique per date, and
        // stable across item additions). Must not vary with group
        // content: if it did, appending a new message would change the
        // key and cause Stencil to remount the whole section, briefly
        // collapsing scrollHeight and scrolling the list to the top
        // before `componentDidRender` scrolls it back.
        const sectionKey = 'date-group-' + group.heading;
        const headingId = `heading-${sectionKey}`.replaceAll(/[^a-zA-Z0-9_-]/g, '_');
        const dateHeading = (h("a", { id: headingId, class: "date-heading", role: "heading", "aria-level": 2 }, group.heading));
        return (h("section", { class: "date-group", key: sectionKey, "aria-labelledby": headingId }, dateHeading, chatItems));
    }
    renderChatItem(item) {
        return (h("limebb-chat-item", { platform: this.platform, context: this.context, item: item, helperText: item.helperText, hasError: item.hasError, key: item.id }));
    }
    renderIconList(item) {
        return h("limebb-chat-icon-list", { item: item, key: item.id });
    }
    renderIndicator() {
        return (h("div", { class: "new-items-indicator", role: "separator" }, h("hr", null), h("h3", null, this.translator.get('webclient.new'))));
    }
    isBusy() {
        return this.loading || this.isTypingIndicatorVisible ? 'true' : 'false';
    }
    get dateTimeFormatter() {
        return this.platform.get(PlatformServiceName.DateTimeFormatter);
    }
    get translator() {
        return this.platform.get(PlatformServiceName.Translate);
    }
    static get is() { return "limebb-chat-list"; }
    static get encapsulation() { return "shadow"; }
    static get originalStyleUrls() {
        return {
            "$": ["chat-list.scss"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["chat-list.css"]
        };
    }
    static get properties() {
        return {
            "platform": {
                "type": "unknown",
                "mutable": false,
                "complexType": {
                    "original": "LimeWebComponentPlatform",
                    "resolved": "LimeWebComponentPlatform",
                    "references": {
                        "LimeWebComponentPlatform": {
                            "location": "import",
                            "path": "@limetech/lime-web-components",
                            "id": "node_modules::LimeWebComponentPlatform",
                            "referenceLocation": "LimeWebComponentPlatform"
                        }
                    }
                },
                "required": true,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "inheritdoc",
                            "text": undefined
                        }],
                    "text": "Reference to the platform service container.\n\nUse this to access all platform services like repositories, HTTP client,\ncommand bus, navigator, and more. The platform instance is shared across\nall components in the same application context."
                },
                "getter": false,
                "setter": false
            },
            "context": {
                "type": "unknown",
                "mutable": false,
                "complexType": {
                    "original": "LimeWebComponentContext",
                    "resolved": "LimeWebComponentContext",
                    "references": {
                        "LimeWebComponentContext": {
                            "location": "import",
                            "path": "@limetech/lime-web-components",
                            "id": "node_modules::LimeWebComponentContext",
                            "referenceLocation": "LimeWebComponentContext"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "inheritdoc",
                            "text": undefined
                        }],
                    "text": "The context describing where this component is running.\n\nThe context provides information about the current limetype and record ID\n(if viewing/editing a specific record). Components can use this to load\nrelevant data and understand their operating environment."
                },
                "getter": false,
                "setter": false
            },
            "items": {
                "type": "unknown",
                "mutable": false,
                "complexType": {
                    "original": "ChatListItem[]",
                    "resolved": "ChatListItem[]",
                    "references": {
                        "ChatListItem": {
                            "location": "import",
                            "path": "./chat-list.types",
                            "id": "src/components/chat-list/chat-list.types.ts::ChatListItem",
                            "referenceLocation": "ChatListItem"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "List of items to display in the feed.\nThese items must be sorted by their `timestamp` in ascending order\n(oldest first, newest last). This matches the natural visual order\nfor the default `oldest-on-top` layout, keeping DOM order aligned\nwith visual order for copy/paste, screen readers, and tab order.\n\nSee the [ChatListItem](#/type/ChatListItem/) type for the kinds of\nitems that can appear in the list."
                },
                "getter": false,
                "setter": false,
                "defaultValue": "[]"
            },
            "loading": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "True if items have not finished loading."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "loading"
            },
            "isTypingIndicatorVisible": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Set to `true` to indicate that other chat participants are typing."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "is-typing-indicator-visible"
            },
            "lastVisitedTimestamp": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string | Date | null",
                    "resolved": "Date | null | string | undefined",
                    "references": {
                        "Date": {
                            "location": "global",
                            "id": "global::Date"
                        }
                    }
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "The date and time of the last time the user visited the Notification List.\nWhen provided, the feed will display a divider line between the\nlast visited items (the old items which have a `timestamp` earlier than\n`lastVisitedTimestamp`) and the newer items.\n\nIf there are no newer items, the divider line will not be displayed."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "last-visited-timestamp"
            },
            "order": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "'newest-on-top' | 'oldest-on-top'",
                    "resolved": "\"newest-on-top\" | \"oldest-on-top\"",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "- `newest-on-top` displays new messages at the top of the list;\nused if the chat input is located on top of the chat interface.\n- `oldest-on-top` displays new messages at the bottom of the list (default);\nused if the chat input is located at the bottom of the chat interface.\n\n:::important\nUsing this prop will _not_ automatically sort the chat items for you!\nYou still need to pass the items in the correct order (ascending order by timestamp \u2014 oldest first).\nThis prop only affects the direction of the elements in the user interface,\nsuch as the location of the typing indicator or the scrollbars.\n:::"
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "order",
                "defaultValue": "'oldest-on-top'"
            }
        };
    }
    static get elementRef() { return "host"; }
    static get watchers() {
        return [{
                "propName": "items",
                "methodName": "handleItemsChange"
            }, {
                "propName": "order",
                "methodName": "handleItemsChange"
            }];
    }
}
function parseToUTCDate(date) {
    if (typeof date === 'string') {
        return new Date(Date.parse(date));
    }
    return new Date(date); // This will automatically be UTC
}
function assertNever(value) {
    throw new Error(`Unexpected value in exhaustive switch: ${JSON.stringify(value)}`);
}
