<template>
  <Teleport :to="appendTo" :disabled="!appendTo">
    <div
      ref="modal"
      class="mc-modal"
      tabindex="-1"
      role="dialog"
      :aria-labelledby="modalTitleId"
      :aria-hidden="!open"
      v-bind="$attrs"
      @keyup.esc="closeModal()"
    >
      <div
        class="mc-modal__dialog"
        :class="{ 'is-open': open }"
        role="document"
      >
        <div class="mc-modal__header">
          <component
            :is="modalTitleTag"
            :id="modalTitleId"
            class="mc-modal__title"
          >
            {{ modalTitle }}
          </component>
          <button
            v-if="displayCloseButton"
            ref="close"
            class="mc-modal__close"
            type="button"
            @click="closeModal()"
          >
            <span class="mc-modal__close-text">
              {{ closeButtonText }}
            </span>
          </button>
        </div>
        <div ref="body" class="mc-modal__body">
          <article ref="content" class="mc-modal__content">
            <component :is="titleTag" v-if="title" class="mc-modal__heading">
              {{ title }}
            </component>
            <div class="mt-body-m">
              <!-- @slot Use this slot to insert the main content of the Modal -->
              <slot />
            </div>
          </article>
        </div>
        <div v-if="$slots.footer" class="mc-modal__footer">
          <!--
          @slot Use this slot to insert the footer content of the Modal. A footer is mandatory!
          - [See the footer rules](https://mozaic.adeo.cloud/components/modals/#modal-footer)
        -->
          <slot name="footer" />
        </div>
      </div>
    </div>
    <KeepAlive>
      <div
        class="mc-modal-overlay"
        :class="{ 'is-visible': open }"
        tabindex="-1"
        role="dialog"
      />
    </KeepAlive>
  </Teleport>
</template>

<script>
/**
 * > A modal is a dialog window allowing you to focus the user's attention on a specific task, a piece of information or a mandatory action. It must be used for single action only and must have a call to action button in the bottom.
 *
 * The `MModal` component is the **Vue.js** implementation of the **Modal** component of Mozaic Design System.<br/>
 * The full specification of this component is available in [the associated documentation](https://mozaic.adeo.cloud/components/modals/).
 */
export default {
  name: 'MModal',
  props: {
    /**
     * Allows to make the Modal visible or hide it. Can be used with `v-model`.
     */
    open: {
      type: Boolean,
      default: false,
    },
    /**
     * The Modal's title. _(Title visible in the header. Mandatory for accessibility reasons)_
     */
    modalTitle: {
      type: String,
      required: true,
    },
    /**
     * Allows to define the HTML tag to be used for the Modal's title
     * @values h2, h3, h4, h5, h6
     */
    modalTitleTag: {
      type: String,
      default: 'h2',
      validator: (value) =>
        ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(value),
    },
    /**
     * Allows to define the id for the Modal's title
     */
    modalTitleId: {
      type: String,
      default: 'modal_title',
    },
    /**
     * The title of the content of the Modal
     */
    title: {
      type: String,
      default: null,
    },
    /**
     * Allows to define the HTML tag to be used for the title of the content of the Modal
     */
    titleTag: {
      type: String,
      default: 'h3',
    },
    /**
     * Allows to specify the text of the Modal's close button
     */
    closeButtonText: {
      type: String,
      default: 'Close',
    },
    /**
     * Allows to show or hide the close button
     */
    displayCloseButton: {
      type: Boolean,
      default: true,
    },
    /**
     * A query selector that defines the target element to which to append the content of the Teleport
     */
    appendTo: {
      type: [String, Boolean],
      default: 'body',
    },
    /**
     * Allows to lock the general scroll of the page
     */
    lockScroll: {
      type: Boolean,
      default: true,
    },
  },
  emits: ['update:open', 'modal-opened', 'modal-closed'],
  data() {
    return {
      lastFocusedElement: null,
    };
  },
  watch: {
    open(isOpen) {
      this.$nextTick(() => {
        if (isOpen) {
          this.openModal();
        } else {
          if (this.lastFocusedElement) {
            this.lastFocusedElement.focus();
          }
          /**
           * Triggered when the modal is close
           */
          this.$emit('modal-closed');
        }

        this.lockScroll && this.handleLockScroll(isOpen);
      });
    },
  },
  mounted() {
    if (this.open) {
      this.$nextTick(() => {
        this.openModal();
      });
    }

    this.lockScroll && this.handleLockScroll(this.open);
  },
  unmounted() {
    this.lockScroll && this.handleLockScroll(false);
  },
  methods: {
    openModal: function () {
      this.trapFocus();
      this.setFocusOnModal();
      this.setOverflow();
      /**
       * Triggered when the modal is open
       */
      this.$emit('modal-opened');
    },
    trapFocus: function () {
      const modal = document.querySelector('.mc-modal');
      const focusableElements = modal.querySelectorAll(
        'a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])',
      );

      const firstFocusableElement = focusableElements[0]; // get first element to be focused inside modal
      const lastFocusableElement =
        focusableElements[focusableElements.length - 1];

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

        if (!isTabPressed) {
          return;
        }

        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();
          }
        }
      });
    },
    closeModal() {
      /**
       * Emits the state value of the "open" prop
       */
      this.$emit('update:open', false);
    },
    setOverflow: function () {
      const modal = this.$refs.modal;
      const bodyHeight = this.$refs.body.clientHeight;
      const contentHeight = this.$refs.content.clientHeight;

      if (contentHeight > bodyHeight) {
        modal.classList.add('mc-modal--overflow');
      }
    },
    setFocusOnModal() {
      const modal = this.$refs.modal;

      this.lastFocusedElement = document.activeElement;

      modal.focus();
    },
    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>
@import 'settings-tools/all-settings';
@import 'typography/t.bodys';
@import 'components/c.modal';
@import 'components/c.button';
</style>

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