/**
 * @license
 * Copyright Tyler Technologies, Inc. 
 * License: Apache-2.0
 */
import { NAVIGATION_KEYS, TAB_BAR_CONSTANTS } from './tab-bar-constants';
import { TAB_CONSTANTS } from '../tab/tab-constants';
export class TabBarCore {
    constructor(_adapter) {
        this._adapter = _adapter;
        // State
        this._isInitialized = false;
        this._disabled = false;
        this._vertical = false;
        this._clustered = false;
        this._stacked = false;
        this._secondary = false;
        this._inverted = false;
        this._autoActivate = false;
        this._scrollButtons = false;
        this._tabs = [];
        this._scrollButtonsVisible = false;
        this._tabsChangedListener = () => this._onTabsChanged();
        this._tabSelectedListener = (evt) => this._onTabSelected(evt);
        this._keydownListener = (evt) => this._onKeydown(evt);
        this._resizeListener = () => this._onResize();
        this._scrollListener = () => this._onScroll();
        this._scrollBackwardButtonListener = () => this._onScrollBackward();
        this._scrollForwardButtonListener = () => this._onScrollForward();
    }
    initialize() {
        this._adapter.initialize();
        this._adapter.addSlotListener(this._tabsChangedListener);
        this._adapter.addHostListener(TAB_CONSTANTS.events.SELECT, this._tabSelectedListener);
        this._adapter.addHostListener('keydown', this._keydownListener);
        this._adapter.setVertical(this._vertical);
        if (this._scrollButtons) {
            this._adapter.initializeContainerSizeObserver(this._resizeListener);
            this._adapter.initializeScrollObserver(this._scrollListener);
            this._updateScrollState();
        }
        this._tryScrollActiveTabIntoView();
        this._isInitialized = true;
    }
    destroy() {
        this._adapter.destroy();
        this._adapter.destroyContainerSizeObserver();
        this._adapter.destroyScrollObserver(this._scrollListener);
        this._isInitialized = false;
    }
    async _onTabsChanged() {
        this._tabs = this._adapter.getTabs();
        this._syncTabState();
        this._tryScrollActiveTabIntoView();
    }
    _onTabSelected(evt) {
        this._selectTab(evt.target);
    }
    async _onKeydown(evt) {
        const orientation = this._vertical ? 'vertical' : 'horizontal';
        const isNavigationKey = NAVIGATION_KEYS.get('default')?.has(evt.key) || NAVIGATION_KEYS.get(orientation)?.has(evt.key);
        if (!isNavigationKey) {
            return;
        }
        evt.preventDefault();
        let index = -1;
        if (evt.key === 'Home') {
            // Locate the first non-disabled tab
            index = this._tabs.findIndex(tab => !tab.disabled);
        }
        else if (evt.key === 'End') {
            // Locate the last non-disabled tab
            index = this._tabs.reduceRight((acc, tab, i) => (!tab.disabled && acc === -1 ? i : acc), -1);
        }
        else {
            // Locate the next or previous tab based on the key that was pressed
            const currentIndex = this._tabs.findIndex(tab => tab.matches(':focus'));
            const isPrevKey = evt.key === 'ArrowLeft' || evt.key === 'ArrowUp';
            const calcIndex = (startIndex) => {
                index = startIndex + (isPrevKey ? -1 : 1);
                index = index < 0 ? this._tabs.length - 1 : index % this._tabs.length;
                // Try to recurse until we find a non-disabled tab (unless all tabs are disabled already)
                const isAllTabsDisabled = this._tabs.every(tab => tab.disabled);
                if (!isAllTabsDisabled && this._tabs[index]?.disabled) {
                    calcIndex(index);
                }
            };
            calcIndex(currentIndex);
        }
        if (index === -1) {
            return;
        }
        if (this._autoActivate) {
            this._selectTab(this._tabs[index]);
        }
        else {
            this._tabs[index].focus({ preventScroll: true });
            await this._adapter.tryScrollTabIntoView(this._tabs[index]);
        }
    }
    async _selectTab(tab, emitEvent = true) {
        if (!tab || tab.disabled) {
            return;
        }
        const currentSelectedTab = this._tabs.find(t => t.selected);
        if (currentSelectedTab === tab) {
            return;
        }
        if (emitEvent) {
            const index = this._tabs.indexOf(tab);
            const event = new CustomEvent(TAB_BAR_CONSTANTS.events.CHANGE, {
                detail: { index },
                bubbles: true,
                cancelable: true,
                composed: true
            });
            this._adapter.dispatchHostEvent(event);
            if (event.defaultPrevented) {
                return;
            }
        }
        // Selecting a tab causes an animation of the indicator to start relative to the currently selected tab
        tab.selected = true;
        tab.focus({ preventScroll: true });
        await this._adapter.tryScrollTabIntoView(tab);
        // Always deselect the currently selected tab after selecting a new tab to allow
        // for the tab indicator animation to run properly (the newly selected tab attempts
        // to locate the current selected tab for its calculations)
        if (currentSelectedTab) {
            currentSelectedTab.selected = false;
        }
        this._activeTab = this._tabs.indexOf(tab);
    }
    /**
     * Ensures that all tabs have the correct state based on the tab bar state.
     *
     * This is called whenever a child tab is added to the DOM.
     */
    _syncTabState() {
        this._tabs.forEach((tab, index) => {
            tab.selected = index === this._activeTab;
            tab.disabled = this._disabled;
            tab.vertical = this._vertical;
            tab.stacked = this._stacked;
            tab.secondary = this._secondary;
            tab.inverted = this._inverted;
        });
    }
    /** Called when the container size changes, but only if scroll buttons are enabled. */
    _onResize() {
        this._detectScrollableStatus();
    }
    /** Called when the scroll container scrolls, but only if scroll buttons are enabled. */
    _onScroll() {
        this._updateScrollState();
    }
    /** Handles clicking the scroll backward button. */
    _onScrollBackward() {
        this._adapter.scroll('backward');
    }
    /** Handles clicking the scroll forward button. */
    _onScrollForward() {
        this._adapter.scroll('forward');
    }
    /** Determines whether scroll buttons should be displayed based on the size of the container. */
    _detectScrollableStatus() {
        const scrollable = this._adapter.isScrollable();
        if (this._scrollButtonsVisible === scrollable) {
            return;
        }
        this._adapter.setScrollButtons(scrollable);
        if (scrollable) {
            this._adapter.initializeScrollObserver(this._scrollListener);
            this._updateScrollState();
            this._adapter.setScrollBackwardButtonListener(this._scrollBackwardButtonListener);
            this._adapter.setScrollForwardButtonListener(this._scrollForwardButtonListener);
        }
        else {
            this._adapter.destroyScrollObserver(this._scrollListener);
        }
        this._scrollButtonsVisible = scrollable;
    }
    /** Updates the enabled/disabled state of the scroll buttons. */
    _updateScrollState() {
        const { isScrolledEnd, isScrolledStart } = this._adapter.getScrollState();
        const state = {
            backwardEnabled: !isScrolledStart,
            forwardEnabled: !isScrolledEnd
        };
        this._adapter.syncScrollButtons(state);
    }
    _tryScrollActiveTabIntoView() {
        if (this._tabScrollAnimationFrame) {
            window.cancelAnimationFrame(this._tabScrollAnimationFrame);
        }
        // We batch the tab scrolling into a single animation frame to unnecessarily scrolling for each tab
        this._tabScrollAnimationFrame = window.requestAnimationFrame(() => {
            this._tabScrollAnimationFrame = undefined;
            if (this._adapter.isScrollable()) {
                if (typeof this._activeTab === 'number' && this._activeTab >= 0 && this._tabs[this._activeTab]) {
                    this._adapter.tryScrollTabIntoView(this._tabs[this._activeTab]);
                }
            }
        });
    }
    get disabled() {
        return this._disabled;
    }
    set disabled(value) {
        value = Boolean(value);
        if (this._disabled !== value) {
            this._disabled = value;
            this._tabs.forEach(tab => (tab.disabled = this._disabled));
            this._adapter.toggleHostAttribute(TAB_BAR_CONSTANTS.attributes.DISABLED, this._disabled);
        }
    }
    get activeTab() {
        return this._activeTab;
    }
    set activeTab(value) {
        if (this._activeTab !== value) {
            this._activeTab = value ?? undefined;
            if (typeof this._activeTab === 'number') {
                const newSelectedTab = this._tabs[this._activeTab];
                this._selectTab(newSelectedTab, false);
                this._adapter.setHostAttribute(TAB_BAR_CONSTANTS.attributes.ACTIVE_TAB, String(this._activeTab));
            }
            else {
                this._tabs.forEach(tab => (tab.selected = false));
                this._adapter.removeHostAttribute(TAB_BAR_CONSTANTS.attributes.ACTIVE_TAB);
            }
        }
    }
    get vertical() {
        return this._vertical;
    }
    set vertical(value) {
        value = Boolean(value);
        if (this._vertical !== value) {
            this._vertical = value;
            if (this._isInitialized) {
                this._adapter.setVertical(this._vertical);
            }
            this._tabs.forEach(tab => (tab.vertical = this._vertical));
            if (this._scrollButtonsVisible) {
                this._adapter.updateScrollButtonIcons(this._vertical);
            }
            this._adapter.toggleHostAttribute(TAB_CONSTANTS.attributes.VERTICAL, this._vertical);
        }
    }
    get clustered() {
        return this._clustered;
    }
    set clustered(value) {
        value = Boolean(value);
        if (this._clustered !== value) {
            this._clustered = value;
            this._adapter.toggleHostAttribute(TAB_BAR_CONSTANTS.attributes.CLUSTERED, this._clustered);
        }
    }
    get stacked() {
        return this._stacked;
    }
    set stacked(value) {
        value = Boolean(value);
        if (this._stacked !== value) {
            this._stacked = value;
            this._tabs.forEach(tab => (tab.stacked = this._stacked));
            this._adapter.toggleHostAttribute(TAB_BAR_CONSTANTS.attributes.STACKED, this._stacked);
        }
    }
    get secondary() {
        return this._secondary;
    }
    set secondary(value) {
        value = Boolean(value);
        if (this._secondary !== value) {
            this._secondary = value;
            this._tabs.forEach(tab => (tab.secondary = this._secondary));
            this._adapter.toggleHostAttribute(TAB_BAR_CONSTANTS.attributes.SECONDARY, this._secondary);
        }
    }
    get inverted() {
        return this._inverted;
    }
    set inverted(value) {
        value = Boolean(value);
        if (this._inverted !== value) {
            this._inverted = value;
            this._tabs.forEach(tab => (tab.inverted = this._inverted));
            this._adapter.toggleHostAttribute(TAB_BAR_CONSTANTS.attributes.INVERTED, this._inverted);
        }
    }
    get autoActivate() {
        return this._autoActivate;
    }
    set autoActivate(value) {
        value = Boolean(value);
        if (this._autoActivate !== value) {
            this._autoActivate = value;
            this._adapter.toggleHostAttribute(TAB_BAR_CONSTANTS.attributes.AUTO_ACTIVATE, this._autoActivate);
        }
    }
    get scrollButtons() {
        return this._scrollButtons;
    }
    set scrollButtons(value) {
        value = Boolean(value);
        if (this._scrollButtons !== value) {
            this._scrollButtons = Boolean(value);
            if (this._isInitialized) {
                if (this._scrollButtons) {
                    this._adapter.initializeContainerSizeObserver(this._resizeListener);
                    this._adapter.initializeScrollObserver(this._scrollListener);
                    this._detectScrollableStatus();
                    this._updateScrollState();
                    this._tryScrollActiveTabIntoView();
                }
                else {
                    this._adapter.destroyContainerSizeObserver();
                    this._adapter.destroyScrollObserver(this._scrollListener);
                    this._adapter.setScrollButtons(this._scrollButtons);
                    this._scrollButtonsVisible = false;
                }
            }
            this._adapter.setHostAttribute(TAB_BAR_CONSTANTS.attributes.SCROLL_BUTTONS, String(this._scrollButtons));
        }
    }
}
