import { DomHandler, ObjectUtils } from 'primevue/utils';

function bind(el, binding) {
    const { onFocusIn, onFocusOut } = binding.value || {};

    el.$_pfocustrap_mutationobserver = new MutationObserver((mutationList) => {
        mutationList.forEach((mutation) => {
            if (mutation.type === 'childList' && !el.contains(document.activeElement)) {
                const findNextFocusableElement = (el) => {
                    const focusableElement = DomHandler.isFocusableElement(el) ? el : DomHandler.getFirstFocusableElement(el);

                    return ObjectUtils.isNotEmpty(focusableElement) ? focusableElement : findNextFocusableElement(el.nextSibling);
                };

                DomHandler.focus(findNextFocusableElement(mutation.nextSibling));
            }
        });
    });

    el.$_pfocustrap_mutationobserver.disconnect();
    el.$_pfocustrap_mutationobserver.observe(el, {
        childList: true
    });

    el.$_pfocustrap_focusinlistener = (event) => onFocusIn && onFocusIn(event);
    el.$_pfocustrap_focusoutlistener = (event) => onFocusOut && onFocusOut(event);

    el.addEventListener('focusin', el.$_pfocustrap_focusinlistener);
    el.addEventListener('focusout', el.$_pfocustrap_focusoutlistener);
}

function unbind(el) {
    el.$_pfocustrap_mutationobserver && el.$_pfocustrap_mutationobserver.disconnect();
    el.$_pfocustrap_focusinlistener && el.removeEventListener('focusin', el.$_pfocustrap_focusinlistener) && (el.$_pfocustrap_focusinlistener = null);
    el.$_pfocustrap_focusoutlistener && el.removeEventListener('focusout', el.$_pfocustrap_focusoutlistener) && (el.$_pfocustrap_focusoutlistener = null);
}

function autoFocus(el, binding) {
    const { autoFocusSelector = '', firstFocusableSelector = '', autoFocus = false } = binding.value || {};
    let focusableElement = DomHandler.getFirstFocusableElement(el, `[autofocus]:not(.p-hidden-focusable)${autoFocusSelector}`);

    autoFocus && !focusableElement && (focusableElement = DomHandler.getFirstFocusableElement(el, `:not(.p-hidden-focusable)${firstFocusableSelector}`));
    DomHandler.focus(focusableElement);
}

function onFirstHiddenElementFocus(event) {
    const { currentTarget, relatedTarget } = event;
    const focusableElement =
        relatedTarget === currentTarget.$_pfocustrap_lasthiddenfocusableelement
            ? DomHandler.getFirstFocusableElement(currentTarget.parentElement, `:not(.p-hidden-focusable)${currentTarget.$_pfocustrap_focusableselector}`)
            : currentTarget.$_pfocustrap_lasthiddenfocusableelement;

    DomHandler.focus(focusableElement);
}

function onLastHiddenElementFocus(event) {
    const { currentTarget, relatedTarget } = event;
    const focusableElement =
        relatedTarget === currentTarget.$_pfocustrap_firsthiddenfocusableelement
            ? DomHandler.getLastFocusableElement(currentTarget.parentElement, `:not(.p-hidden-focusable)${currentTarget.$_pfocustrap_focusableselector}`)
            : currentTarget.$_pfocustrap_firsthiddenfocusableelement;

    DomHandler.focus(focusableElement);
}

function createHiddenFocusableElements(el, binding) {
    const { tabIndex = 0, firstFocusableSelector = '', lastFocusableSelector = '' } = binding.value || {};

    const createFocusableElement = (onFocus) => {
        const element = document.createElement('span');

        element.classList = 'p-hidden-accessible p-hidden-focusable';
        element.tabIndex = tabIndex;
        element.setAttribute('aria-hidden', 'true');
        element.setAttribute('role', 'presentation');
        element.addEventListener('focus', onFocus);

        return element;
    };

    const firstFocusableElement = createFocusableElement(onFirstHiddenElementFocus);
    const lastFocusableElement = createFocusableElement(onLastHiddenElementFocus);

    firstFocusableElement.$_pfocustrap_lasthiddenfocusableelement = lastFocusableElement;
    firstFocusableElement.$_pfocustrap_focusableselector = firstFocusableSelector;

    lastFocusableElement.$_pfocustrap_firsthiddenfocusableelement = firstFocusableElement;
    lastFocusableElement.$_pfocustrap_focusableselector = lastFocusableSelector;

    el.prepend(firstFocusableElement);
    el.append(lastFocusableElement);
}

const FocusTrap = {
    mounted(el, binding) {
        const { disabled } = binding.value || {};

        if (!disabled) {
            createHiddenFocusableElements(el, binding);
            bind(el, binding);
            autoFocus(el, binding);
        }
    },
    updated(el, binding) {
        const { disabled } = binding.value || {};

        disabled && unbind(el);
    },
    unmounted(el) {
        unbind(el);
    }
};

export default FocusTrap;
