import { __decorate, __metadata } from "tslib";
import { h, Host, writeTask, } from "@stencil/core";
import { ListenToAnimation } from "../../utils/animation";
import { BEM } from "../../utils/bem";
import { ListenToBreakpoints } from "../../utils/breakpoints";
import { ListenToFocus } from "../../utils/focus";
import { FOCUS_KEYS } from "../../utils/focus-visible";
import { defaultBalAriaForm } from "../../utils/form";
import { stopEventBubbling } from "../../utils/form-input";
import { raf } from "../../utils/helpers";
import { isArrowDownKey, isArrowLeftKey, isArrowRightKey, isArrowUpKey, isEndKey, isEnterKey, isHomeKey, isSpaceKey, } from "../../utils/keyboard";
import { Logger } from "../../utils/log";
import { ListenToWindowResize } from "../../utils/resize";
import { ListenToVisibility } from "../../utils/visibility";
export class Segment {
    constructor() {
        this.keyboardMode = true;
        this.isVertical = false;
        this.isMobile = false;
        this.maxWidth = 0;
        this.ariaForm = defaultBalAriaForm;
        /**
         * PUBLIC PROPERTY API
         * ------------------------------------------------------
         */
        /**
         * If `true`, the segment is shown red.
         */
        this.invalid = false;
        /**
         * If `true`, in Angular reactive forms the control will not be set invalid
         */
        this.autoInvalidOff = false;
        /**
         * If `true`, the user cannot interact with the segment.
         */
        this.disabled = false;
        /**
         * If `true`, the segment items are presented vertical as a list.
         */
        this.vertical = false;
        /**
         * If `true`, and is vertical then the list height is limited and scrollable.
         */
        this.scrollable = false;
        /**
         * If `true`, the element uses the whole width
         */
        this.expanded = false;
        /**
         * LISTENERS
         * ------------------------------------------------------
         */
        this.hasFocus = false;
        this.getSegmentItem = (selector) => {
            var _a, _b;
            const items = this.allItems.filter(item => !item.disabled);
            const currIndex = items.findIndex(item => item === document.activeElement.closest('bal-segment-item'));
            switch (selector) {
                case 'current':
                    return items[currIndex];
                case 'first':
                    return items[0];
                case 'last':
                    return items[items.length - 1];
                case 'next':
                    return (_a = items[currIndex + 1]) !== null && _a !== void 0 ? _a : items[0];
                case 'previous':
                    return (_b = items[currIndex - 1]) !== null && _b !== void 0 ? _b : items[items.length - 1];
                default:
                    return null;
            }
        };
        /**
         * PRIVATE EVENT HANDLERS
         * ------------------------------------------------------
         */
        this.onPointerDown = () => {
            this.keyboardMode = false;
        };
        this.onSlottedItemsChange = () => {
            /**
             * When the slotted segment buttons change we need to
             * ensure that the new segment buttons are checked if
             * the value matches the segment button value.
             */
            this.valueChanged(this.value);
            this.defineWidth();
        };
        this.onClick = (ev) => {
            if (this.disabled) {
                return;
            }
            const current = ev.target;
            const previous = this.checked;
            // If the current element is a segment then that means
            // the user tried to swipe to a segment button and
            // click a segment button at the same time so we should
            // not update the checked segment button
            if (current.tagName === 'BAL-SEGMENT') {
                return;
            }
            if (current && current.setFocus) {
                current.setFocus();
            }
            if (previous) {
                this.checkButton(previous, current);
            }
            else {
                this.setCheckedClasses();
                this.checkButton(current, current);
            }
            if (current !== previous) {
                this.emitValueChange();
            }
        };
    }
    createLogger(log) {
        this.log = log;
    }
    invalidChanged() {
        this.allItems.map(item => (item.invalid = this.invalid));
    }
    disabledChanged() {
        this.allItems.map(item => (item.disabled = this.disabled));
    }
    valueChanged(value) {
        /**
         * `balSelect` is emitted every time the value changes (internal or external changes).
         * Used by `bal-segment-item` to determine if the button should be checked.
         */
        this.balSelect.emit(value);
        this.setCheckedClasses();
    }
    /**
     * LIFECYCLE
     * ------------------------------------------------------
     */
    connectedCallback() {
        this.el.addEventListener('touchstart', this.onPointerDown);
        this.el.addEventListener('mousedown', this.onPointerDown);
        this.disabledChanged();
        this.isVertical = this.vertical;
    }
    disconnectedCallback() {
        this.el.removeEventListener('touchstart', this.onPointerDown);
        this.el.removeEventListener('mousedown', this.onPointerDown);
    }
    async componentDidLoad() {
        this.setCheckedClasses();
        this.defineWidth();
    }
    focusInListener(ev) {
        this.balFocus.emit(ev);
    }
    focusOutListener(ev) {
        this.balBlur.emit(ev);
    }
    animationListener() {
        const childRect = this.el.getBoundingClientRect();
        this.maxWidth = childRect.width;
        this.windowResizeListener();
    }
    visibilityListener() {
        const childRect = this.el.getBoundingClientRect();
        this.maxWidth = childRect.width;
        this.windowResizeListener();
    }
    breakpointListener(breakpoints) {
        this.isMobile = breakpoints.mobile;
    }
    windowResizeListener() {
        if (this.vertical === false && this.maxWidth > 0) {
            const parent = this.el.parentElement.getBoundingClientRect();
            if (parent.width < this.maxWidth) {
                //
                // element in horizontal is to big
                this.emitVerticalChange(true);
            }
            else {
                //
                // element has enough space in parent element
                this.emitVerticalChange(false);
            }
        }
        else {
            this.emitVerticalChange(true);
        }
    }
    listenOnKeyDownOutside() {
        this.keyboardMode = true;
    }
    listenOnKeyDown(ev) {
        this.keyboardMode = FOCUS_KEYS.includes(ev.key);
        let forceChange = false;
        let current;
        if (isSpaceKey(ev) || isEnterKey(ev)) {
            stopEventBubbling(ev);
            current = this.getSegmentItem('current');
            forceChange = this.value !== current.value;
            this.value = current.value;
        }
        else if (isArrowUpKey(ev) || isArrowLeftKey(ev)) {
            stopEventBubbling(ev);
            current = this.getSegmentItem('previous');
        }
        else if (isArrowDownKey(ev) || isArrowRightKey(ev)) {
            stopEventBubbling(ev);
            current = this.getSegmentItem('next');
        }
        else if (isHomeKey(ev)) {
            stopEventBubbling(ev);
            current = this.getSegmentItem('first');
        }
        else if (isEndKey(ev)) {
            stopEventBubbling(ev);
            current = this.getSegmentItem('last');
        }
        if (!current) {
            return;
        }
        const previous = this.checked;
        if (current !== previous || forceChange) {
            this.checkButton(previous, current);
            this.emitValueChange();
        }
        if (current) {
            current.setFocus();
        }
    }
    /**
     * PUBLIC METHODS
     * ------------------------------------------------------
     */
    /**
     * @internal
     */
    async setAriaForm(ariaForm) {
        this.ariaForm = Object.assign({}, ariaForm);
    }
    /**
     * GETTERS
     * ------------------------------------------------------
     */
    get allItems() {
        return Array.from(this.el.querySelectorAll('bal-segment-item'));
    }
    get checked() {
        return this.allItems.find(item => item.value === this.value);
    }
    /**
     * PRIVATE METHODS
     * ------------------------------------------------------
     */
    emitValueChange() {
        this.balChange.emit(this.value);
    }
    emitVerticalChange(isVertical) {
        if (this.isVertical !== isVertical) {
            this.isVertical = isVertical;
            this.balVertical.emit(this.isVertical);
        }
    }
    defineWidth() {
        raf(() => {
            if (this.isVertical === false) {
                const childRect = this.el.getBoundingClientRect();
                this.maxWidth = childRect.width;
            }
            this.windowResizeListener();
        });
    }
    getIndicator(item) {
        if (item) {
            const root = item.shadowRoot || item;
            return root.querySelector('.bal-segment-item__indicator');
        }
        return null;
    }
    checkButton(previous, current) {
        const previousIndicator = this.getIndicator(previous);
        const currentIndicator = this.getIndicator(current);
        if (previousIndicator === null || currentIndicator === null) {
            return;
        }
        const previousClientRect = previousIndicator.getBoundingClientRect();
        const currentClientRect = currentIndicator.getBoundingClientRect();
        const widthDelta = previousClientRect.width / currentClientRect.width;
        const xPosition = previousClientRect.left - currentClientRect.left;
        // Scale the indicator width to match the previous indicator width
        // and translate it on top of the previous indicator
        let transform = `translate3d(${xPosition}px, 0, 0) scaleX(${widthDelta})`;
        if (this.isVertical) {
            const heightDelta = previousClientRect.height / currentClientRect.height;
            const yPosition = previousClientRect.top - currentClientRect.top;
            // Scale the indicator width to match the previous indicator width
            // and translate it on top of the previous indicator
            transform = `translate3d(0, ${yPosition}px, 0) scaleY(${heightDelta})`;
        }
        writeTask(() => {
            // Remove the transition before positioning on top of the previous indicator
            currentIndicator.classList.remove('bal-segment-item__indicator--animated');
            currentIndicator.style.setProperty('transform', transform);
            // Force a repaint to ensure the transform happens
            currentIndicator.getBoundingClientRect();
            // Add the transition to move the indicator into place
            currentIndicator.classList.add('bal-segment-item__indicator--animated');
            // Remove the transform to slide the indicator back to the button clicked
            currentIndicator.style.setProperty('transform', '');
        });
        this.value = current.value;
        this.setCheckedClasses();
    }
    setCheckedClasses() {
        const items = this.allItems;
        const index = items.findIndex(item => item.value === this.value);
        const next = index + 1;
        const previous = index - 1;
        for (const item of items) {
            item.classList.remove('bal-segment-item--after-checked');
            item.classList.remove('bal-segment-item--before-checked');
        }
        if (next < items.length) {
            items[next].classList.add('bal-segment-item--after-checked');
        }
        if (previous < items.length && previous >= 0) {
            items[previous].classList.add('bal-segment-item--before-checked');
        }
    }
    /**
     * RENDER
     * ------------------------------------------------------
     */
    render() {
        const { invalid, isVertical, scrollable, keyboardMode, expanded, isMobile, disabled } = this;
        const block = BEM.block('segment');
        return (h(Host, { key: '08fb99b658d704d85ab181d55d7bdbce7ee40fd8', role: "radiogroup", id: this.ariaForm.controlId, "aria-labelledby": this.ariaForm.labelId, "aria-describedby": this.ariaForm.messageId, class: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, block.class()), block.modifier('invalid').class(invalid)), block.modifier('vertical').class(isVertical)), block.modifier('scrollable').class(scrollable)), block.modifier('keyboard').class(keyboardMode)), block.modifier('disabled').class(disabled)), block.modifier('expanded').class((expanded || isMobile) && !isVertical)), onClick: this.onClick }, h("slot", { key: '432741fe51dc5c83ea241506687de13469afc149', onSlotchange: this.onSlottedItemsChange })));
    }
    static get is() { return "bal-segment"; }
    static get originalStyleUrls() {
        return {
            "$": ["bal-segment.sass"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["bal-segment.css"]
        };
    }
    static get properties() {
        return {
            "invalid": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If `true`, the segment is shown red."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "invalid",
                "defaultValue": "false"
            },
            "autoInvalidOff": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If `true`, in Angular reactive forms the control will not be set invalid"
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "auto-invalid-off",
                "defaultValue": "false"
            },
            "disabled": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If `true`, the user cannot interact with the segment."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "disabled",
                "defaultValue": "false"
            },
            "vertical": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If `true`, the segment items are presented vertical as a list."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "vertical",
                "defaultValue": "false"
            },
            "scrollable": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If `true`, and is vertical then the list height is limited and scrollable."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "scrollable",
                "defaultValue": "false"
            },
            "expanded": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If `true`, the element uses the whole width"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "expanded",
                "defaultValue": "false"
            },
            "value": {
                "type": "any",
                "mutable": true,
                "complexType": {
                    "original": "BalProps.BalSegmentValue",
                    "resolved": "boolean | number | string",
                    "references": {
                        "BalProps": {
                            "location": "global",
                            "id": "global::BalProps"
                        }
                    }
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "the value of the segment."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "value"
            }
        };
    }
    static get states() {
        return {
            "focusedValue": {},
            "keyboardMode": {},
            "isVertical": {},
            "isMobile": {},
            "maxWidth": {},
            "ariaForm": {}
        };
    }
    static get events() {
        return [{
                "method": "balFocus",
                "name": "balFocus",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the toggle has focus."
                },
                "complexType": {
                    "original": "BalEvents.BalSegmentFocusDetail",
                    "resolved": "FocusEvent",
                    "references": {
                        "BalEvents": {
                            "location": "global",
                            "id": "global::BalEvents"
                        }
                    }
                }
            }, {
                "method": "balBlur",
                "name": "balBlur",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the component was touched"
                },
                "complexType": {
                    "original": "BalEvents.BalSegmentBlurDetail",
                    "resolved": "FocusEvent",
                    "references": {
                        "BalEvents": {
                            "location": "global",
                            "id": "global::BalEvents"
                        }
                    }
                }
            }, {
                "method": "balChange",
                "name": "balChange",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the value property has changed and any dragging pointer has been released from `bal-segment`.\n\nThis event will not emit when programmatically setting the `value` property."
                },
                "complexType": {
                    "original": "BalEvents.BalSegmentChangeDetail",
                    "resolved": "boolean | number | string",
                    "references": {
                        "BalEvents": {
                            "location": "global",
                            "id": "global::BalEvents"
                        }
                    }
                }
            }, {
                "method": "balSelect",
                "name": "balSelect",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [{
                            "name": "internal",
                            "text": undefined
                        }],
                    "text": "Emitted when the value of the segment changes from user committed actions\nor from externally assigning a value."
                },
                "complexType": {
                    "original": "BalEvents.BalSegmentChangeDetail",
                    "resolved": "boolean | number | string",
                    "references": {
                        "BalEvents": {
                            "location": "global",
                            "id": "global::BalEvents"
                        }
                    }
                }
            }, {
                "method": "balVertical",
                "name": "balVertical",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [{
                            "name": "internal",
                            "text": undefined
                        }],
                    "text": "Emitted when the vertical style changes"
                },
                "complexType": {
                    "original": "BalEvents.BalSegmentVerticalDetail",
                    "resolved": "boolean",
                    "references": {
                        "BalEvents": {
                            "location": "global",
                            "id": "global::BalEvents"
                        }
                    }
                }
            }];
    }
    static get methods() {
        return {
            "setAriaForm": {
                "complexType": {
                    "signature": "(ariaForm: BalAriaForm) => Promise<void>",
                    "parameters": [{
                            "name": "ariaForm",
                            "type": "BalAriaForm",
                            "docs": ""
                        }],
                    "references": {
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        },
                        "BalAriaForm": {
                            "location": "import",
                            "path": "../../utils/form",
                            "id": "src/utils/form.ts::BalAriaForm"
                        }
                    },
                    "return": "Promise<void>"
                },
                "docs": {
                    "text": "",
                    "tags": [{
                            "name": "internal",
                            "text": undefined
                        }]
                }
            }
        };
    }
    static get elementRef() { return "el"; }
    static get watchers() {
        return [{
                "propName": "invalid",
                "methodName": "invalidChanged"
            }, {
                "propName": "disabled",
                "methodName": "disabledChanged"
            }, {
                "propName": "value",
                "methodName": "valueChanged"
            }];
    }
    static get listeners() {
        return [{
                "name": "keydown",
                "method": "listenOnKeyDownOutside",
                "target": "document",
                "capture": false,
                "passive": false
            }, {
                "name": "keydown",
                "method": "listenOnKeyDown",
                "target": undefined,
                "capture": false,
                "passive": false
            }];
    }
}
__decorate([
    Logger('bal-segment'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Function]),
    __metadata("design:returntype", void 0)
], Segment.prototype, "createLogger", null);
__decorate([
    ListenToFocus(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", void 0)
], Segment.prototype, "focusInListener", null);
__decorate([
    ListenToFocus(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", void 0)
], Segment.prototype, "focusOutListener", null);
__decorate([
    ListenToAnimation(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", void 0)
], Segment.prototype, "animationListener", null);
__decorate([
    ListenToVisibility(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", void 0)
], Segment.prototype, "visibilityListener", null);
__decorate([
    ListenToBreakpoints(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", void 0)
], Segment.prototype, "breakpointListener", null);
__decorate([
    ListenToWindowResize(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", void 0)
], Segment.prototype, "windowResizeListener", null);
