<template>
  <Teleport :to="appendTo" :disabled="!appendTo">
    <div
      ref="layer"
      class="mc-layer"
      :class="{ 'mc-layer--ltr': position === 'left' }"
      role="dialog"
      :aria-labelledby="layerTitleId"
      :aria-modal="open"
      :aria-hidden="!open"
      v-bind="$attrs"
      @keyup.esc="closeLayer()"
    >
      <div class="mc-layer__dialog" :class="classObjectDialog" role="document">
        <div class="mc-layer__header">
          <slot name="header">
            <MIcon v-if="headerIcon" :name="headerIcon" class="mc-layer__icon" />
            <component
              :is="layerTitleTag"
              :id="layerTitleId"
              class="mc-layer__title"
            >
              {{ layerTitle }}
            </component>
            <button
              v-if="!disableCloseIcon"
              ref="close"
              class="mc-layer__close"
              type="button"
              @click="closeLayer()"
            >
              <span class="mc-layer__close-text">
                {{ closeButtonText }}
              </span>
            </button>
          </slot>
        </div>
        <div ref="body" class="mc-layer__body">
          <div ref="content" class="mc-layer__content">
            <!-- @slot Use this slot to insert the main content of the Layer -->
            <slot />
          </div>
        </div>
        <div v-if="$slots.footer" class="mc-layer__footer">
          <!--
            @slot Use this slot to insert the footer content of the Layer. A footer is mandatory!
            - [See the footer rules](https://mozaic.adeo.cloud/components/layers/#layer-footer)
          -->
          <slot name="footer" />
        </div>
      </div>
    </div>
    <div
      class="mc-layer-overlay"
      :class="{
        'is-visible': open && !hideOverlay,
        'is-not-visible': open && hideOverlay,
      }"
      @click="closeOnOverlay && closeLayer()"
    />
  </Teleport>
</template>

<script>
import MIcon from '../icon/MIcon.vue';

/**
 * > In modern layouts, a layer is a way to give further actions or display to the user specific content without breaking the user flow, like login or cart overview.
 *
 * The `MLayer` component is the **Vue.js** implementation of the **Layer** component of Mozaic Design System.<br/>
 * The full specification of this component is available in [the associated documentation](https://mozaic.adeo.cloud/components/layers/).
 */
export default {
  name: 'MLayer',
  components: {
    MIcon,
  },
  props: {
    /**
     * Allows to make the Layer visible or hide it. Can be used with `v-model`.
     */
    open: {
      type: Boolean,
      default: false,
    },
    /**
     * The Layer's title. _(Title visible in the header. Mandatory for accessibility reasons)_
     */
    layerTitle: {
      type: String,
      required: true,
    },
    /**
     * Allows to define the HTML tag to be used for the Layer's title
     * @values h1, h2, h3, h4, h5, h6
     */
    layerTitleTag: {
      type: String,
      default: 'h2',
      validator: (value) =>
        ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(value),
    },
    /**
     * Allows to define the id for the Layer's title
     */
    layerTitleId: {
      type: String,
      default: 'layerTitle',
    },
    /**
     * Allows to display an icon before the Layer's title
     * @see [Available icons](https://mozaic.adeo.cloud/foundations/icons/)
     */
    headerIcon: {
      type: String,
      default: null,
    },
    /**
     * The text content of the Layer's close button
     */
    closeButtonText: {
      type: String,
      default: 'Close',
    },
    /**
     * Enables or disables the "extended" variation
     * @see [Related documentation](https://mozaic.adeo.cloud/components/layers/code/#extended-layer)
     */
    extended: {
      type: Boolean,
      default: false,
    },
    /**
     * Allows to activate or not the closing of the layer when clicking on the overlay
     */
    closeOnOverlay: {
      type: Boolean,
      default: false,
    },
    /**
     * Enables or disables the "edge" variation
     * @see [Related documentation](https://mozaic.adeo.cloud/components/layers/code/#body-with-scrolling-content)
     */
    scrollbarEdge: {
      type: Boolean,
      default: false,
    },
    /**
     * Define the opening position of the layer
     */
    position: {
      type: String,
      default: 'right',
      validator: (value) => ['right', 'left'].includes(value),
    },
    /**
     * Allows to lock the general scroll of the page
     */
    lockScroll: {
      type: Boolean,
      default: true,
    },
    /**
     * Disable header close icon
     */
    disableCloseIcon: {
      type: Boolean,
      default: false,
    },
    /**
     * A query selector that defines the target element to which to append the content of the Teleport
     */
    appendTo: {
      type: String,
      default: undefined,
    },
    /**
     * Top position for opened layer
     */
    dialogTop: {
      type: String,
      default: '0',
    },
    /**
     * Left position for opened layer
     */
    dialogLeft: {
      type: String,
      default: '0',
    },
    /**
     * Right position for opened layer
     */
    dialogRight: {
      type: String,
      default: '0',
    },
    /**
     * Bottom position for opened layer
     */
    dialogBottom: {
      type: String,
      default: '0',
    },
    /**
     * Hide background when layer is opened
     */
    hideOverlay: {
      type: Boolean,
      default: false,
    },
    /**
     * Allow auto focus on open
     */
    allowAutoFocus: {
      type: Boolean,
      default: true,
    },
  },
  emits: ['update:open', 'layer-opened', 'layer-closed'],
  data() {
    return {
      lastFocusedElement: null,
    };
  },
  computed: {
    classObjectDialog() {
      return {
        'is-open': this.open,
        'mc-layer__dialog--extend': this.extended,
      };
    },
  },
  watch: {
    open: {
      handler(isOpen) {
        this.$nextTick(() => {
          if (isOpen) {
            this.openLayer();
          } else {
            if (this.lastFocusedElement) {
              this.lastFocusedElement.focus();
            } else {
              document.body.focus();
            }
            document.body.classList.remove('mc-layer-open');
            /**
             * Triggered when the layer is close
             */
            this.$emit('layer-closed');
          }
          this.lockScroll && this.handleLockScroll(isOpen);
        });
      },
      immediate: true,
    },
  },
  mounted() {
    this.$nextTick(() => {
      this.setOverflow();
    });
  },
  unmounted() {
    this.lockScroll && this.handleLockScroll(false);
  },
  methods: {
    openLayer: function () {
      this.$refs.layer.addEventListener(
        'transitionend',
        this.onOpeningTransitionEnd,
      );
      document.body.classList.add('mc-layer-open');
      /**
       * Triggered when the layer is open
       */
      this.$emit('layer-opened');
    },
    onOpeningTransitionEnd() {
      this.lastFocusedElement = document.activeElement;
      if (this.allowAutoFocus) {
        this.trapFocus();
      }
      this.$refs.layer.removeEventListener(
        'transitionend',
        this.onOpeningTransitionEnd,
      );
    },
    closeLayer: function () {
      /**
       * Emits the state value of the "open" prop
       */
      this.$emit('update:open', false);
    },
    trapFocus: function () {
      const layer = this.$refs.layer;

      if (this.disableCloseIcon) {
        this.$refs.content.focus();
      } else {
        this.$refs.close.focus();
      }

      document.addEventListener('keydown', function (e) {
        const isTabPressed = e.key === 'Tab' || e.keyCode === 9;

        if (!isTabPressed) {
          return;
        }

        const focusableElements = layer.querySelectorAll(
          'a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])',
        );
        const firstFocusableElement = focusableElements[0]; // get first element to be focused inside layer
        let lastFocusableElementIndex = focusableElements.length - 1;
        let lastFocusableElement = focusableElements[lastFocusableElementIndex];

        // if last focusable element is disabled then find the last focusable element which is not disabled
        while (lastFocusableElement.disabled && lastFocusableElementIndex > 0) {
          lastFocusableElementIndex -= 1;
          lastFocusableElement = focusableElements[lastFocusableElementIndex];
        }

        if (e.shiftKey) {
          // if shift key pressed for shift + tab combination
          if (document.activeElement === firstFocusableElement) {
            lastFocusableElement.focus(); // add focus for the last focusable element
            e.preventDefault();
          }
        } else {
          // if tab key is pressed
          if (document.activeElement === lastFocusableElement) {
            // if focused has reached to last focusable element then focus first focusable element after pressing tab
            firstFocusableElement.focus(); // add focus for the first focusable element
            e.preventDefault();
          }
        }
      });
    },
    setOverflow: function () {
      const layer = this.$refs.layer;
      const body = this.$refs.body;
      const content = this.$refs.content;
      const overflowingClass = !this.scrollbarEdge
        ? 'mc-layer__body--overflowing'
        : 'mc-layer__body--overflowing-scroll-edge';

      const ro = new ResizeObserver((entries) => {
        for (let entry of entries) {
          const bodyHeight =
            layer.querySelector('.mc-layer__body').clientHeight;
          const contentHeight = entry.target.scrollHeight;

          if (contentHeight > bodyHeight) {
            body.classList.add(overflowingClass);
          } else {
            body.classList.remove(overflowingClass);
          }
        }
      });

      ro.observe(content);
    },
    handleLockScroll(open) {
      if (open) {
        document.body.parentNode.classList.add('mu-prevent-body-scroll');
        document.body.classList.add('mu-prevent-body-scroll');
      } else {
        document.body.parentNode.classList.remove('mu-prevent-body-scroll');
        document.body.classList.remove('mu-prevent-body-scroll');
      }
    },
  },
};
</script>

<style lang="scss" scoped>
$dialog-top: v-bind(dialogTop);
$dialog-left: v-bind(dialogLeft);
$dialog-right: v-bind(dialogRight);
$dialog-bottom: v-bind(dialogBottom);

@import 'settings-tools/all-settings';
@import 'components/c.layer';
@import 'components/c.button';
</style>

<style lang="scss">
@import 'utilities/u.scroll';
</style>
