// #region Imports
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;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var TourElement_1;
import { HTMLElementEventEmitter } from '@breadstone/mosaik-elements';
import { Emit } from '../../../../events/Decorators/EmitDecorator';
import { Themeable } from '../../../Behaviors/Themeable';
import { Attribute } from '../../../Decorators/AttributeDecorator';
import { Component } from '../../../Decorators/ComponentDecorator';
import { Property } from '../../../Decorators/PropertyDecorator';
import { QueryChild } from '../../../Decorators/QueryChildDecorator';
import { Placement } from '../../../Types/Placement';
import { BackdropElement } from '../../Primitives/Backdrop/BackdropElement';
import { OverlayElement } from '../Abstracts/OverlayElement';
import { tourElementCosmopolitanStyle } from './Themes/TourElement.Cosmopolitan';
import { tourElementJoyStyle } from './Themes/TourElement.Joy';
import { tourElementMemphisStyle } from './Themes/TourElement.Memphis';
import { tourElementTemplate } from './TourElementTemplate';
import { addTourRegistryController } from './TourRegistryController';
import { TourStepElement } from './TourStepElement';
/**
 * Tour - Guides users through a sequence of steps anchored to UI elements.
 *
 * @description
 * Manages an ordered collection of tour steps and overlays step tooltips onto
 * the corresponding `mosaik-tour-anchor` elements. The `tour-id` must match
 * the `tour-id` used on the associated `mosaik-tour-anchor` elements.
 * The `steps` array must be set via JavaScript.
 *
 * @name Tour
 * @element mosaik-tour
 * @category Overlays
 *
 * @csspart backdrop - The backdrop part.
 * @csspart tooltip - The tooltip part.
 *
 * @cssprop {String} --tour-font-family - The font family CSS custom property.
 * @cssprop {String} --tour-font-letter-spacing - The font letter spacing CSS custom property.
 * @cssprop {String} --tour-font-line-height - The font line height CSS custom property.
 * @cssprop {String} --tour-font-size - The font size CSS custom property.
 * @cssprop {String} --tour-font-text-decoration - The font text decoration CSS custom property.
 * @cssprop {String} --tour-font-text-transform - The font text transform CSS custom property.
 * @cssprop {String} --tour-font-weight - The font weight CSS custom property.
 * @cssprop {String} --tour-gap - The gap CSS custom property.
 * @cssprop {String} --tour-overlay-zindex - The overlay zindex CSS custom property.
 * @cssprop {String} --tour-padding-bottom - The padding bottom CSS custom property.
 * @cssprop {String} --tour-padding-left - The padding left CSS custom property.
 * @cssprop {String} --tour-padding-right - The padding right CSS custom property.
 * @cssprop {String} --tour-padding-top - The padding top CSS custom property.
 * @cssprop {String} --tour-shadow - The shadow CSS custom property.
 * @cssprop {String} --tour-shadow-blur - The shadow blur CSS custom property.
 * @cssprop {String} --tour-shadow-color - The shadow color CSS custom property.
 * @cssprop {String} --tour-shadow-offset-x - The shadow offset x CSS custom property.
 * @cssprop {String} --tour-shadow-offset-y - The shadow offset y CSS custom property.
 * @cssprop {String} --tour-shadow-spread - The shadow spread CSS custom property.
 * @cssprop {String} --tour-tooltip-max-width - The tooltip max width CSS custom property.
 * @cssprop {String} --tour-transition-duration - The transition duration CSS custom property.
 * @cssprop {String} --tour-transition-mode - The transition mode CSS custom property.
 * @cssprop {String} --tour-transition-property - The transition property CSS custom property.
 * @cssprop {String} --tour-translate - The translate CSS custom property.
 *
 * @dependency mosaik-backdrop - The Backdrop element.
 * @dependency mosaik-tour-step - The Tour Step element.
 *
 * @example
 * Basic tour identified by tour-id:
 * ```html
 * <mosaik-tour tour-id="onboarding"></mosaik-tour>
 * ```
 *
 * @public
 */
let TourElement = class TourElement extends Themeable(OverlayElement) {
    static { TourElement_1 = this; }
    // #region Fields
    static POSITION_OFFSET = 12;
    static VIEWPORT_PADDING = 8;
    _registryController;
    _started;
    _ended;
    _stepChanged;
    _boundHandleViewportChange;
    _steps;
    _currentStepIndex;
    _tourId;
    _tooltipStyles;
    _positionFrameId;
    // #endregion
    // #region Ctor
    /**
     * @public
     */
    constructor() {
        super();
        this._steps = new Array();
        this._currentStepIndex = -1;
        this._tourId = 'default';
        this._tooltipStyles = {
            top: '0px',
            left: '0px',
            transform: 'translate(0, 0)'
        };
        this._positionFrameId = null;
        this._started = new HTMLElementEventEmitter(this, 'started');
        this._ended = new HTMLElementEventEmitter(this, 'ended');
        this._stepChanged = new HTMLElementEventEmitter(this, 'stepChanged');
        this._boundHandleViewportChange = () => this.schedulePositionUpdate();
        this._registryController = addTourRegistryController(this, {
            getTourId: () => this.tourId
        });
        this.hasBackdrop = true;
        this.pressEscapeToClose = true;
        this.clickOutsideToClose = true;
    }
    // #endregion
    // #region Properties
    /**
     * Returns the `is` property.
     *
     * @public
     * @static
     * @readonly
     */
    static get is() {
        return 'mosaik-tour';
    }
    /**
     * Gets or sets the `tourId` property.
     *
     * @public
     * @attr
     */
    get tourId() {
        return this._tourId;
    }
    set tourId(value) {
        if (this._tourId !== value) {
            this._tourId = value || 'default';
            this.requestUpdate('tourId');
            this.schedulePositionUpdate();
        }
    }
    /**
     * Gets or sets the tour steps.
     *
     * @public
     */
    get steps() {
        return this._steps;
    }
    set steps(value) {
        if (this._steps !== value) {
            this._steps = value ?? new Array();
            this._currentStepIndex = -1;
            this.requestUpdate('steps');
            this.requestUpdate();
            this.emitStepChanged(null, this.currentStep, this._currentStepIndex);
            this.schedulePositionUpdate();
        }
    }
    /**
     * Gets the current step.
     *
     * @public
     * @readonly
     */
    get currentStep() {
        if (this._currentStepIndex < 0 || this._currentStepIndex >= this._steps.length) {
            return null;
        }
        return this._steps[this._currentStepIndex] ?? null;
    }
    /**
     * Indicates whether the current step is the first step.
     *
     * @public
     * @readonly
     */
    get isFirstStep() {
        return this._currentStepIndex <= 0;
    }
    /**
     * Indicates whether the current step is the last step.
     *
     * @public
     * @readonly
     */
    get isLastStep() {
        return this._currentStepIndex >= this._steps.length - 1;
    }
    /**
     * Gets the tooltip styles used for positioning.
     *
     * @public
     * @readonly
     */
    get tooltipStyles() {
        return this._tooltipStyles;
    }
    /**
     * Fired when the tour is started.
     *
     * @public
     * @readonly
     * @eventProperty
     */
    get started() {
        return this._started;
    }
    /**
     * Fired when the tour is ended.
     *
     * @public
     * @readonly
     * @eventProperty
     */
    get ended() {
        return this._ended;
    }
    /**
     * Fired when the tour step changes.
     *
     * @public
     * @readonly
     * @eventProperty
     */
    get stepChanged() {
        return this._stepChanged;
    }
    // #endregion
    // #region Methods
    /**
     * Starts the tour from the first step.
     *
     * @public
     */
    start() {
        if (this._steps.length === 0) {
            return;
        }
        this._currentStepIndex = 0;
        this.requestUpdate();
        this.emitStepChanged(null, this.currentStep, this._currentStepIndex);
        this.started.emit({});
        void this.open();
        this.schedulePositionUpdate();
    }
    /**
     * Moves to the next step in the tour.
     *
     * @public
     */
    next() {
        if (this._currentStepIndex < this._steps.length - 1) {
            this.setCurrentStepIndex(this._currentStepIndex + 1);
        }
        else {
            this.finish();
        }
    }
    /**
     * Moves to the previous step in the tour.
     *
     * @public
     */
    previous() {
        if (this._currentStepIndex > 0) {
            this.setCurrentStepIndex(this._currentStepIndex - 1);
        }
    }
    /**
     * Ends the tour and closes the overlay.
     *
     * @public
     */
    finish() {
        if (!this.isOpen) {
            return;
        }
        const previousStep = this.currentStep;
        this._currentStepIndex = -1;
        this.requestUpdate();
        this.emitStepChanged(previousStep, null, this._currentStepIndex);
        this.ended.emit({});
        void this.close();
    }
    /**
     * Skips the current tour.
     *
     * @public
     */
    skip() {
        this.finish();
    }
    /**
     * Shows the specified step by its identifier.
     *
     * @public
     */
    showStep(stepId) {
        const index = this._steps.findIndex((step) => step.id === stepId);
        if (index >= 0) {
            this.setCurrentStepIndex(index);
            void this.open();
        }
    }
    /**
     * @public
     * @override
     */
    disconnectedCallback() {
        this.removeViewportListeners();
        super.disconnectedCallback();
    }
    /**
     * Handles backdrop clicks.
     *
     * @public
     */
    onBackdropClicked(event) {
        if (!this.clickOutsideToClose) {
            return;
        }
        event.stopPropagation();
        void this.finish();
    }
    /**
     * @protected
     * @override
     */
    onIsOpenChanged(previousValue, newValue) {
        super.onIsOpenChanged(previousValue, newValue);
        if (newValue) {
            this.addViewportListeners();
            this.schedulePositionUpdate();
        }
        else {
            this.removeViewportListeners();
        }
    }
    /**
     * @private
     */
    _tooltipElement;
    /**
     * Updates the current step index and emits events.
     *
     * @private
     */
    setCurrentStepIndex(nextIndex) {
        const previousStep = this.currentStep;
        if (this._steps.length === 0) {
            this._currentStepIndex = -1;
        }
        else {
            this._currentStepIndex = Math.min(Math.max(nextIndex, 0), this._steps.length - 1);
        }
        const currentStep = this.currentStep;
        this.requestUpdate();
        this.emitStepChanged(previousStep, currentStep, this._currentStepIndex);
        this.schedulePositionUpdate();
    }
    /**
     * Emits the step changed event.
     *
     * @private
     */
    emitStepChanged(previousStep, currentStep, stepIndex) {
        this._stepChanged.emit({
            previousStep,
            currentStep,
            stepIndex
        });
    }
    /**
     * Adds listeners needed to keep the tooltip positioned.
     *
     * @private
     */
    addViewportListeners() {
        window.addEventListener('resize', this._boundHandleViewportChange);
        window.addEventListener('scroll', this._boundHandleViewportChange, true);
    }
    /**
     * Removes listeners used for tooltip positioning.
     *
     * @private
     */
    removeViewportListeners() {
        window.removeEventListener('resize', this._boundHandleViewportChange);
        window.removeEventListener('scroll', this._boundHandleViewportChange, true);
    }
    /**
     * Schedules a tooltip position update.
     *
     * @private
     */
    schedulePositionUpdate() {
        if (this._positionFrameId !== null) {
            cancelAnimationFrame(this._positionFrameId);
        }
        this._positionFrameId = requestAnimationFrame(() => {
            this.updateTooltipPosition();
            this._positionFrameId = null;
        });
    }
    /**
     * Updates the tooltip position based on the current step.
     *
     * @private
     */
    updateTooltipPosition() {
        const step = this.currentStep;
        const tooltipElement = this._tooltipElement;
        if (!step || !tooltipElement) {
            return;
        }
        const anchorElement = this._registryController.resolve(step.anchorId);
        if (!anchorElement) {
            return;
        }
        const anchorRect = anchorElement.getBoundingClientRect();
        const tooltipRect = tooltipElement.getBoundingClientRect();
        const position = this.resolveTooltipPosition(anchorRect, tooltipRect, step.placement);
        this._tooltipStyles = {
            top: `${position.top}px`,
            left: `${position.left}px`,
            transform: 'translate(0, 0)'
        };
        this.requestUpdate('tooltipStyles');
    }
    /**
     * Resolves the tooltip position for a given placement.
     *
     * @private
     */
    resolveTooltipPosition(anchorRect, tooltipRect, placement) {
        let top = 0;
        let left = 0;
        switch (placement) {
            case Placement.Top:
                top = anchorRect.top - tooltipRect.height - TourElement_1.POSITION_OFFSET;
                left = anchorRect.left + (anchorRect.width - tooltipRect.width) / 2;
                break;
            case Placement.TopStart:
                top = anchorRect.top - tooltipRect.height - TourElement_1.POSITION_OFFSET;
                left = anchorRect.left;
                break;
            case Placement.TopEnd:
                top = anchorRect.top - tooltipRect.height - TourElement_1.POSITION_OFFSET;
                left = anchorRect.right - tooltipRect.width;
                break;
            case Placement.Bottom:
                top = anchorRect.bottom + TourElement_1.POSITION_OFFSET;
                left = anchorRect.left + (anchorRect.width - tooltipRect.width) / 2;
                break;
            case Placement.BottomStart:
                top = anchorRect.bottom + TourElement_1.POSITION_OFFSET;
                left = anchorRect.left;
                break;
            case Placement.BottomEnd:
                top = anchorRect.bottom + TourElement_1.POSITION_OFFSET;
                left = anchorRect.right - tooltipRect.width;
                break;
            case Placement.Left:
                top = anchorRect.top + (anchorRect.height - tooltipRect.height) / 2;
                left = anchorRect.left - tooltipRect.width - TourElement_1.POSITION_OFFSET;
                break;
            case Placement.LeftStart:
                top = anchorRect.top;
                left = anchorRect.left - tooltipRect.width - TourElement_1.POSITION_OFFSET;
                break;
            case Placement.LeftEnd:
                top = anchorRect.bottom - tooltipRect.height;
                left = anchorRect.left - tooltipRect.width - TourElement_1.POSITION_OFFSET;
                break;
            case Placement.Right:
                top = anchorRect.top + (anchorRect.height - tooltipRect.height) / 2;
                left = anchorRect.right + TourElement_1.POSITION_OFFSET;
                break;
            case Placement.RightStart:
                top = anchorRect.top;
                left = anchorRect.right + TourElement_1.POSITION_OFFSET;
                break;
            case Placement.RightEnd:
                top = anchorRect.bottom - tooltipRect.height;
                left = anchorRect.right + TourElement_1.POSITION_OFFSET;
                break;
            default:
                top = anchorRect.bottom + TourElement_1.POSITION_OFFSET;
                left = anchorRect.left + (anchorRect.width - tooltipRect.width) / 2;
                break;
        }
        const maxLeft = window.innerWidth - tooltipRect.width - TourElement_1.VIEWPORT_PADDING;
        const maxTop = window.innerHeight - tooltipRect.height - TourElement_1.VIEWPORT_PADDING;
        return {
            left: Math.min(Math.max(left, TourElement_1.VIEWPORT_PADDING), maxLeft),
            top: Math.min(Math.max(top, TourElement_1.VIEWPORT_PADDING), maxTop)
        };
    }
};
__decorate([
    Attribute({ type: String }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], TourElement.prototype, "tourId", null);
__decorate([
    Property({ type: Array }),
    __metadata("design:type", Array),
    __metadata("design:paramtypes", [Array])
], TourElement.prototype, "steps", null);
__decorate([
    Emit({ eventName: 'started' }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [])
], TourElement.prototype, "started", null);
__decorate([
    Emit({ eventName: 'ended' }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [])
], TourElement.prototype, "ended", null);
__decorate([
    Emit({ eventName: 'stepChanged' }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [])
], TourElement.prototype, "stepChanged", null);
__decorate([
    QueryChild('[part="tooltip"]'),
    __metadata("design:type", Object)
], TourElement.prototype, "_tooltipElement", void 0);
TourElement = TourElement_1 = __decorate([
    Component({
        selector: 'mosaik-tour',
        template: tourElementTemplate,
        themes: {
            joy: tourElementJoyStyle,
            memphis: tourElementMemphisStyle,
            cosmopolitan: tourElementCosmopolitanStyle
        },
        imports: [
            BackdropElement,
            TourStepElement
        ]
    }),
    __metadata("design:paramtypes", [])
], TourElement);
export { TourElement };
//# sourceMappingURL=TourElement.js.map