import { __decorate } from "tslib";
/* eslint-disable import/no-cycle */
/* eslint-disable no-param-reassign */
import { html, LitElement, nothing } from "lit";
import { property, query, queryAssignedElements, state } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { styleMap } from "lit/directives/style-map.js";
import { Easing } from "../motion/animation.js";
import '../../define/ui/ui-icon-button.js';
import '../../define/ui/ui-divider.js';
import '../../define/ui/ui-icon.js';
/**
 * Calculates value in percentages
 * @param w Element width
 * @param w0 Parent width
 * @return The percentage of element's width relative to parent.
 */
export function calcPercent(w, w0) {
    return (100 * w) / w0;
}
/**
 * A container for tabs.
 *
 * @fires select - A non bubbling event when selection change through user interaction. The `event.detail` object contains the `item` and `index` properties. It also has the `select` property with the value of the `selectedAttribute` on the tab.
 */
export default class UiTabs extends LitElement {
    constructor() {
        super();
        this.items = [];
        this.activeItem = null;
        this.previousItem = null;
        /**
         * The priority of the tabs.
         *
         * @default primary
         * @attribute
         */
        this.priority = 'primary';
        /**
         * The attribute on the `ui-tab` that indicates which value for `selected`
         * corresponds to which tab.
         *
         * @default aria-controls
         * @attribute
         */
        this.selectedAttribute = `aria-controls`;
        this.indicated = false;
        this.observer = new IntersectionObserver(this.intersectionCallback.bind(this), {
            threshold: 1.0,
            rootMargin: '0px',
        });
    }
    willUpdate(cp) {
        if (cp.has('isVisible')) {
            this.handleVisibility();
        }
        super.willUpdate(cp);
    }
    connectedCallback() {
        super.connectedCallback();
        if (!this.hasAttribute('role')) {
            this.setAttribute('role', 'tablist');
        }
        this.observer.observe(this);
    }
    disconnectedCallback() {
        super.disconnectedCallback();
        this.observer.unobserve(this);
    }
    intersectionCallback(entries) {
        const [entry] = entries;
        this.isVisible = entry.isIntersecting;
    }
    async updateItems() {
        const elements = this.assignedElements || [];
        const items = elements.filter(this.isTabItem, this);
        this.items = items;
        if (this.activeItem && !items.includes(this.activeItem)) {
            this.activeItem = null;
        }
        await this.updateComplete;
        this.ensureSelection();
    }
    /**
     * @return Whether the given element is a list item element.
     */
    isTabItem(element) {
        if (element.nodeType !== Node.ELEMENT_NODE) {
            return false;
        }
        return element.localName === 'ui-tab';
    }
    isSelectable(element) {
        if (element.disabled) {
            return false;
        }
        if (element.hidden && element.hasAttribute('hidden')) {
            return false;
        }
        return true;
    }
    ensureSelection() {
        const { selected, selectedAttribute, activeItem, items } = this;
        if (!selectedAttribute) {
            return;
        }
        const item = items.find(i => i.getAttribute(selectedAttribute) === selected);
        if (item && item === activeItem) {
            return;
        }
        this.makeSelection(item);
    }
    makeSelection(tab, focus = false) {
        const { activeItem } = this;
        if (activeItem === tab) {
            tab.highlight();
            return;
        }
        this.previousItem = activeItem;
        if (activeItem) {
            this.deselectItem(activeItem);
        }
        if (tab) {
            this.selectItem(tab, focus);
            this.positionPointer(tab, activeItem);
            if (this.activeItem) {
                // we set this here so we won't notify selection when initializing.
                this.notifySelect(tab);
            }
        }
    }
    notifySelect(item) {
        const index = this.items.indexOf(item);
        if (index === -1) {
            return;
        }
        const attrValue = item.getAttribute(this.selectedAttribute);
        this.dispatchEvent(new CustomEvent('select', {
            detail: {
                item,
                index,
                select: attrValue,
            }
        }));
    }
    selectItem(tab, focus = false) {
        this.activeItem = tab;
        if (this.hasAttribute('tabindex')) {
            this.removeAttribute('tabindex');
        }
        tab.setAttribute('aria-selected', 'true');
        tab.setAttribute('tabindex', '0');
        tab.selected = true;
        tab.priority = this.priority;
        tab.scrollIntoView({ inline: 'nearest', behavior: 'auto', block: 'nearest' });
        if (focus) {
            tab.focus();
        }
    }
    deselectItem(tab) {
        tab.setAttribute('aria-selected', 'false');
        tab.setAttribute('tabindex', '-1');
        tab.selected = false;
        tab.indicated = false;
    }
    handleVisibility() {
        const { previousItem, activeItem } = this;
        if (!activeItem) {
            return;
        }
        this.positionPointer(activeItem, previousItem);
    }
    async positionPointer(tab, old) {
        const { items, pointer, isVisible } = this;
        if (!isVisible) {
            return;
        }
        const index = items.indexOf(tab);
        if (index < 0 || !pointer) {
            this.pointerStyles = undefined;
            return;
        }
        if (!old) {
            // no start animation, just show indicator.
            tab.indicated = true;
            return;
        }
        const isPrimary = this.priority === 'primary';
        const final = isPrimary ? this.getPrimaryLeft(tab) : this.getSecondaryLeft(tab);
        if (this.pointerStyles && this.pointerStyles.left === `${final}px`) {
            return;
        }
        // first position this indicator in the place of the old one.
        // update the view and then run the animation.
        this.indicated = true;
        const left = isPrimary ? this.getPrimaryLeft(old) : this.getSecondaryLeft(old);
        this.pointerStyles = { left };
        await this.updateComplete;
        const frames = isPrimary ? this.getPrimaryKeyframes(left, final) : this.getSecondaryKeyframes(left, final);
        if (this.moveAnimation) {
            this.moveAnimation.cancel();
        }
        const moveAnimation = pointer.animate(frames, {
            duration: 360,
            iterations: 1,
            easing: Easing.DECELERATION,
        });
        const finalStyles = {
            left: `${final}px`,
        };
        moveAnimation.addEventListener('finish', () => {
            this.pointerStyles = finalStyles;
            this.moveAnimation = undefined;
            tab.indicated = true;
            this.indicated = false;
        });
        moveAnimation.addEventListener('cancel', () => {
            this.pointerStyles = finalStyles;
        });
        this.moveAnimation = moveAnimation;
    }
    getPrimaryLeft(tab) {
        const contentBox = this.content.getBoundingClientRect();
        const tabRect = tab.getBoundingClientRect();
        const leftFromParent = tabRect.x - contentBox.x;
        const offset = this.scrollable ? 48 : 0; // left button
        const left = leftFromParent + (tabRect.width / 2) - 20 + offset;
        return `${left}px`;
    }
    getSecondaryLeft(tab) {
        const contentBox = this.content.getBoundingClientRect();
        const tabRect = tab.getBoundingClientRect();
        const offset = this.scrollable ? 48 : 0; // left button
        const leftFromParent = tabRect.x - contentBox.x + offset;
        return `${leftFromParent}px`;
    }
    getPrimaryKeyframes(start, final) {
        return [
            {
                left: start,
                width: `40px`,
            },
            {
                width: `80px`,
            },
            {
                left: final,
                width: `40px`,
            }
        ];
    }
    getSecondaryKeyframes(start, final) {
        return [
            {
                left: start,
            },
            {
                left: final,
            }
        ];
    }
    contentClickHandler(e) {
        this.activateFromEvent(e);
    }
    contentKeyDownHandler(e) {
        if (['Enter', 'Space'].includes(e.code)) {
            this.activateFromEvent(e);
        }
    }
    activateFromEvent(e) {
        const { items } = this;
        const path = e.composedPath();
        let item;
        while (!item) {
            const next = path.shift();
            if (!next) {
                break;
            }
            if (items.includes(next)) {
                item = next;
            }
        }
        if (!item) {
            return;
        }
        this.makeSelection(item, true);
    }
    handleScrollLeft() {
        const { contentScroll = 0, content } = this;
        if (contentScroll === 0) {
            return;
        }
        let left = contentScroll - 80;
        if (left < 0) {
            left = 0;
        }
        this.contentScroll = left;
        content.scrollTo({
            behavior: 'smooth',
            left,
        });
    }
    handleScrollRight() {
        const { contentScroll = 0, content } = this;
        let left = contentScroll + 80;
        if (left + content.clientWidth > content.scrollWidth) {
            left = content.scrollWidth - content.clientWidth;
        }
        this.contentScroll = left;
        content.scrollTo({
            behavior: 'smooth',
            left,
        });
    }
    handleScroll(e) {
        const div = e.target;
        this.contentScroll = div.scrollLeft;
    }
    handleKeyDown(e) {
        if (e.code === 'ArrowRight') {
            e.preventDefault();
            const tab = this.activeItem ? this.getNextItem(this.activeItem) : this.items[0];
            this.makeSelection(tab, true);
        }
        else if (e.code === 'ArrowLeft') {
            e.preventDefault();
            const tab = this.activeItem ? this.getPreviousItem(this.activeItem) : this.items[0];
            this.makeSelection(tab, true);
        }
    }
    getPreviousItem(item) {
        const { items } = this;
        const curIndex = items.indexOf(item);
        if (curIndex < 0) {
            return item;
        }
        let i = curIndex;
        let result;
        do {
            i--;
            if (i === curIndex) {
                // looped back from the end, no active element to find.
                return item;
            }
            const tmp = items[i];
            if (!tmp) {
                i = items.length;
                continue;
            }
            if (this.isSelectable(tmp)) {
                result = tmp;
            }
        } while (!result);
        return result || item;
    }
    getNextItem(item) {
        const { items } = this;
        const curIndex = items.indexOf(item);
        if (curIndex < 0) {
            return item;
        }
        let i = curIndex;
        let next;
        do {
            i++;
            if (i === curIndex) {
                // looped back from the start, no active element to find.
                return item;
            }
            const tmp = items[i];
            if (!tmp) {
                i = -1;
                continue;
            }
            if (this.isSelectable(tmp)) {
                next = tmp;
            }
        } while (!next);
        return next || item;
    }
    render() {
        const classes = {
            surface: true,
            scrollable: !!this.scrollable,
        };
        return html `
    <div class="${classMap(classes)}">
      ${this.renderLeftScrollControl()}
      <div class="content" @scroll="${this.handleScroll}" @keydown="${this.handleKeyDown}">
        ${this.renderSlot()}
      </div>
      ${this.rightScrollControl()}
      ${this.renderIndicator()}
      <div class="divider"></div>
    </div>
    `;
    }
    renderSlot() {
        return html `<slot 
      @slotchange="${this.updateItems}" 
      @click="${this.contentClickHandler}" 
      @keydown="${this.contentKeyDownHandler}"></slot>`;
    }
    renderIndicator() {
        const classes = {
            indicator: true,
            indicated: this.indicated,
            primary: this.priority === 'primary',
            secondary: this.priority === 'secondary',
        };
        return html `
    <div class="${classMap(classes)}">
      <span role="presentation" class="pointer" style="${styleMap(this.pointerStyles || {})}"></span>
    </div>
    `;
    }
    renderLeftScrollControl() {
        if (!this.scrollable) {
            return nothing;
        }
        return html `
    <div class="scroll-control left">
      <ui-icon-button aria-label="Scroll tabs left" title="Scroll tabs left" @click="${this.handleScrollLeft}">
        <ui-icon icon="chevronLeft"></ui-icon>
      </ui-icon-button>
    </div>`;
    }
    rightScrollControl() {
        if (!this.scrollable) {
            return nothing;
        }
        return html `<div class="scroll-control right">
      <ui-icon-button aria-label="Scroll tabs right" title="Scroll tabs right" @click="${this.handleScrollRight}">
        <ui-icon icon="chevronRight"></ui-icon>
      </ui-icon-button>
    </div>`;
    }
}
__decorate([
    queryAssignedElements({ flatten: true })
], UiTabs.prototype, "assignedElements", void 0);
__decorate([
    query('.pointer')
], UiTabs.prototype, "pointer", void 0);
__decorate([
    query('.content')
], UiTabs.prototype, "content", void 0);
__decorate([
    property({ type: String, reflect: true })
], UiTabs.prototype, "priority", void 0);
__decorate([
    property({ type: Boolean })
], UiTabs.prototype, "scrollable", void 0);
__decorate([
    property({ type: String })
], UiTabs.prototype, "selected", void 0);
__decorate([
    property({ type: String })
], UiTabs.prototype, "selectedAttribute", void 0);
__decorate([
    state()
], UiTabs.prototype, "pointerStyles", void 0);
__decorate([
    state()
], UiTabs.prototype, "indicated", void 0);
__decorate([
    state()
], UiTabs.prototype, "isVisible", void 0);
//# sourceMappingURL=UiTabs.js.map