/*!
 * Custom elements for Nanopore-Digital Web applications
 */
//
// A positioning utility for popovers that handles show/hide/transitionEnd events with simple callbacks.
//
// Powered by Popper.js.
//
// NOTE:
//
// - The popover MUST have at least one property that transitions, otherwise transitionEnd won't fire and the popover
//   won't be hidden. If transitions are delegated to a child element, set the `transitionElement` property accordingly.
//
// - When the popover is shown, it's assigned `PopoverOptions.visibleClass`. You can use this class to provide different
//   transitions for show/hide.
//
// - Popper uses `translate3d` to position elements, so adding a transition to the `transform` property may have an
//   undesired effect when the element is shown and when its placement changes.
//
import { createPopper } from "@popperjs/core";
export default class Popover {
    anchor;
    isVisible;
    popover;
    popper;
    options;
    constructor(anchor, popover, options) {
        this.handleTransitionEnd = this.handleTransitionEnd.bind(this);
        this.anchor = anchor;
        this.popover = popover;
        this.options = {
            skidding: 0,
            distance: 0,
            placement: 'bottom-start',
            strategy: 'absolute',
            transitionElement: this.popover,
            visibleClass: 'popover-visible',
            onAfterShow: () => { },
            onAfterHide: () => { },
            onTransitionEnd: () => { },
            ...options,
        };
        // this.isVisible = false;
        this.popover.hidden = true;
        this.popover.classList.remove(this.options.visibleClass);
    }
    handleTransitionEnd(event) {
        const target = event.target;
        // Make sure the transition event originates from from the correct element, and not one that has bubbled up
        if (target === this.options.transitionElement) {
            // This is called before the element is hidden so users can do things like reset scroll. It will fire once for
            // every transition property. Use `event.propertyName` to determine which property has finished transitioning.
            this.options.onTransitionEnd.call(this, event);
            // Make sure we only do this once, since transitionend will fire for every transition
            if (this.isVisible === false && !this.popover.hidden) {
                this.popover.hidden = true;
                this.popover.classList.remove(this.options.visibleClass);
                this.options.onAfterHide.call(this);
                this.popover.removeEventListener('transitionend', this.handleTransitionEnd);
            }
            else {
                this.options.onAfterShow.call(this);
                this.popover.removeEventListener('transitionend', this.handleTransitionEnd);
            }
        }
    }
    destroy() {
        if (this.popper) {
            this.popper.destroy();
            this.popper = null;
        }
    }
    show() {
        this.isVisible = true;
        this.popover.hidden = false;
        // eslint-disable-next-line @typescript-eslint/no-unused-expressions
        this.popover.clientWidth; // force reflow
        this.options.transitionElement.addEventListener('transitionend', this.handleTransitionEnd, { once: true });
        requestAnimationFrame(() => this.popover.classList.add(this.options.visibleClass));
        if (this.popper) {
            this.popper.destroy();
        }
        const safePlacement = this.options.placement.includes('center')
            ? 'top'
            : this.options.placement;
        this.popper = createPopper(this.anchor, this.popover, {
            placement: safePlacement,
            strategy: this.options.strategy,
            modifiers: [
                {
                    name: 'sameWidth',
                    enabled: true,
                    fn: ({ state }) => {
                        if (this.options.sameWidth === undefined ||
                            !!this.options.sameWidth) {
                            state.styles.popper.minWidth = `${state.rects.reference.width}px`;
                        }
                    },
                    phase: 'beforeWrite',
                    requires: ['computeStyles'],
                },
                {
                    name: 'flip',
                    options: {
                        boundary: 'viewport',
                    },
                },
                {
                    name: 'offset',
                    options: {
                        padding: 5,
                        offset: ({ reference }) => {
                            if (this.options.placement !== 'center') {
                                return [this.options.skidding, this.options.distance];
                            }
                            return [this.options.skidding, (reference.height + 1) * -1];
                        },
                    },
                },
            ],
        });
        // Reposition the menu after it appears in case a modifier kicked in
        requestAnimationFrame(() => {
            this.popper?.update().catch((e) => {
                console.error(e);
            });
        });
    }
    hide() {
        // Apply the hidden styles and wait for the transition before hiding completely
        this.isVisible = false;
        this.options.transitionElement.addEventListener('transitionend', this.handleTransitionEnd, { once: true });
        requestAnimationFrame(() => {
            this.popover.classList.remove(this.options.visibleClass);
        });
    }
    setOptions(options) {
        this.options = Object.assign(this.options, options);
        if (this.isVisible)
            this.popover.classList.add(this.options.visibleClass);
        else
            this.popover.classList.remove(this.options.visibleClass);
        const safePlacement = this.options.placement.includes('center')
            ? 'top'
            : this.options.placement;
        // Update popper options
        if (this.popper) {
            this.popper
                .setOptions({
                placement: safePlacement,
                strategy: this.options.strategy,
            })
                .catch((e) => {
                console.error(e);
            });
            requestAnimationFrame(() => {
                this.popper?.update().catch((e) => {
                    console.error(e);
                });
            });
        }
    }
}
