<template>
  <div class="mc-phone-number" :class="classObject" :style="styleObject">
    <div
      v-if="isCountriesListOpened"
      class="mc-phone-number__overlay"
      @click="toggleCountriesList"
    />
    <div
      class="mc-phone-number__dropdown"
      tabindex="0"
      @keydown="initKeyboardNav"
    >
      <button
        id="dropdown_country"
        type="button"
        aria-haspopup="listbox"
        aria-labelledby="dropdown_country"
        class="mc-phone-number__button"
        :class="{ 'is-valid': isValid, 'is-invalid': isInvalid }"
        :aria-expanded="isCountriesListOpened"
        tabindex="-1"
        @click="toggleCountriesList"
      >
        <CountryFlag
          v-if="!withoutFlags"
          class="flag"
          :country="selectedCountry.shortName"
          size="small"
        />
        <span class="mc-phone-number__indicator">
          {{ selectedCountry.isd }}
        </span>
      </button>
      <ul
        v-show="isCountriesListOpened"
        id="phone_number_list"
        ref="countriesList"
        tabindex="-1"
        role="listbox"
        aria-labelledby="phone_number_list"
        class="mc-phone-number__list"
        :aria-activedescendant="selectedCountry.shortName"
      >
        <li
          v-for="country in countries"
          :key="country.shortName"
          role="option"
          class="mc-phone-number__item"
          :class="{
            'mc-phone-number__item--focused':
              country.shortName === selectedCountry.shortName,
          }"
          :aria-selected="country.shortName === selectedCountry.shortName"
          @click="onChangeCountry(country)"
        >
          <CountryFlag
            v-if="!withoutFlags"
            class="flag"
            :country="country.shortName"
            size="small"
          />
          <span class="mc-phone-number__country">{{ country.name }}</span>
          &nbsp;
          <span>{{ country.isd }}</span>
        </li>
      </ul>
    </div>
    <input
      id="smallField"
      type="tel"
      class="mc-phone-number__input mc-text-input mc-text-input--m"
      :class="{ 'is-valid': isValid, 'is-invalid': isInvalid }"
      :aria-invalid="isInvalid"
      name="phone-number-input"
      :value="phoneNumber"
      :placeholder="placeholder"
      :maxlength="maxlength"
      v-bind="$attrs"
      spellcheck="false"
      @blur="onChangePhoneNumber"
      @focus="(event) => (formatOnBlur ? cleanFormat(event) : null)"
      @keyup="onKeyPressPhoneNumber"
      @keydown.backspace="
        (event) => (formatOnBlur ? null : backspaceFunction(event))
      "
    />
  </div>
</template>

<script>
import CountryFlag from 'vue-country-flag-next';
import {
  parsePhoneNumber,
  formatIncompletePhoneNumber,
  AsYouType,
} from 'libphonenumber-js';
import { getExampleNumber } from 'libphonenumber-js/max';
import examples from 'libphonenumber-js/examples.mobile.json';

/**
 * > Phone number input enables the user to input its phone number with an international country prefix in a form element.
 *
 * The `MPhoneNumber` component is the **Vue.js** implementation of the **Phone number input** component of Mozaic Design System.<br/>
 * The full specification of this component is available in [the associated documentation](https://mozaic.adeo.cloud/components/form/phone-number-input/).
 */
export default {
  name: 'MPhoneNumber',
  components: {
    CountryFlag,
  },
  props: {
    /**
     * An array of objects to define the countries items
     * @type {[{ isd: '+33', name: 'France', shortName: 'FR' }]}
     */
    countries: {
      type: Array,
      default: () => [
        {
          isd: '+33',
          name: 'France',
          shortName: 'FR',
        },
        {
          isd: '+39',
          name: 'Italia',
          shortName: 'IT',
        },
      ],
    },
    /**
     * The value of the component.
     */
    value: {
      type: [String, Number],
      default: '',
    },
    /**
     * Add class is-valid to inputs
     */
    isValid: {
      type: Boolean,
      default: false,
    },
    /**
     * The input will be shown as invalid
     */
    isInvalid: {
      type: Boolean,
      default: false,
    },
    /**
     * The value of the `maxlength` attribute of the input element
     */
    maxlength: {
      type: Number,
      default: 25,
    },
    /**
     * Allows to display or not the flags
     */
    withoutFlags: {
      type: Boolean,
      default: false,
    },
    /**
     * Format the number only after the onBlur event
     */
    formatOnBlur: {
      type: Boolean,
      default: false,
    },
    /**
     * Allows you to pass the z-index.
     */
    zIndex: {
      type: Number,
    },
  },
  emits: [
    'input',
    'blur',
    'change-country',
    'open-countries-list',
    'close-countries-list',
  ],
  data: () => ({
    isCountriesListOpened: false,
    selectedCountry: undefined,
    phoneNumber: undefined,
    searchCountryBuffer: '',
    searchCountryTimer: null,
  }),
  computed: {
    classObject() {
      return {
        'mc-phone-number--focused': this.isCountriesListOpened,
      };
    },
    hasValue() {
      return this.value !== '';
    },
    placeholder() {
      return getExampleNumber(
        this.selectedCountry.shortName,
        examples,
      ).formatNational();
    },
    styleObject() {
      return {
        '--phone-number-dropdown-z-index': this.zIndex,
      };
    },
  },
  watch: {
    value() {
      this.init();
    },
    selectedCountry() {
      this.scrollToSelectedCountry();
    },
  },
  created() {
    this.init();
    if (!this.hasValue) {
      this.selectedCountry = this.countries[0];
    }
  },
  methods: {
    init() {
      if (this.hasValue) {
        try {
          const parsedValue = parsePhoneNumber(
            this.value,
            this.countries[0].shortName,
          );
          this.phoneNumber = parsedValue.formatNational();
          this.selectedCountry =
            this.countries.find(
              (country) => country.shortName === parsedValue.country,
            ) || this.selectedCountry;
        } catch (error) {
          this.phoneNumber = this.value;
        } finally {
          this.selectedCountry =
            this.selectedCountry === undefined
              ? this.countries[0]
              : this.selectedCountry;
        }
      } else {
        this.phoneNumber = '';
      }
    },
    initKeyboardNav(event) {
      if (event.keyCode >= 65 && event.keyCode <= 90) {
        // Letters A-Z
        clearTimeout(this.searchCountryTimer);
        this.searchCountryBuffer += event.key.toLowerCase();
        this.searchCountryBuffer = this.searchCountryBuffer.slice(-3); // max 3 caractères
        const foundCountry = this.countries.find((country) =>
          country.name.toLowerCase().startsWith(this.searchCountryBuffer),
        );
        if (foundCountry) {
          this.selectedCountry = foundCountry;
        }
        this.searchCountryTimer = setTimeout(() => {
          this.searchCountryBuffer = '';
        }, 500);
        return;
      }
      switch (event.keyCode) {
        case 40: // ARROW DOWN
        case 38: {
          // ARROW UP
          event.preventDefault();

          if (!this.isCountriesListOpened) {
            this.openCountriesList();
          }

          const tmpIndex = this.countries.findIndex(
            (country) => country.shortName === this.selectedCountry.shortName,
          );
          let index = event.keyCode === 40 ? tmpIndex + 1 : tmpIndex - 1;

          if (index === -1 || index >= this.countries.length) {
            index = index === -1 ? this.countries.length - 1 : 0;
          }

          this.selectedCountry = this.countries[index];
          break;
        }
        case 13: // ENTER
          event.preventDefault();
          this.isCountriesListOpened
            ? this.onChangeCountry(this.selectedCountry)
            : this.openCountriesList();
          break;
        case 9: //TAB
          this.isCountriesListOpened &&
            this.onChangeCountry(this.selectedCountry);
          break;
        case 27: // ESC
          this.closeCountriesList();
          break;
      }
    },
    scrollToSelectedCountry() {
      this.$nextTick(() => {
        const list = this.$refs.countriesList;
        const item = list?.querySelector(
          '.mc-phone-number__item[aria-selected="true"]',
        );
        if (item && list) {
          const listRect = list.getBoundingClientRect();
          const itemRect = item.getBoundingClientRect();
          list.scrollTop += itemRect.top - listRect.top;
        }
      });
    },
    backspaceFunction(event) {
      const caretPosition = event.target.selectionStart;
      const char = event.target.value.charAt(event.target.selectionStart - 1);
      if (char === ' ' || char === ')' || char === '-') {
        event.target.value =
          event.target.value.slice(0, event.target.selectionStart - 1) +
          event.target.value.slice(event.target.selectionStart);
        event.target.selectionStart = caretPosition - 1;
        event.target.selectionEnd = caretPosition - 1;
      }
    },

    onKeyPressPhoneNumber(event) {
      if (this.formatOnBlur) {
        const phoneNumber = event ? event.target.value : this.phoneNumber;
        try {
          /**
           * Triggered when entering the field
           */
          this.$emit(
            'input',
            parsePhoneNumber(
              phoneNumber,
              this.selectedCountry.shortName,
            ).formatInternational(),
          );
        } catch (error) {
          /**
           * Triggered when entering the field
           */
          this.$emit(
            'input',
            formatIncompletePhoneNumber(
              phoneNumber,
              this.selectedCountry.shortName,
            ),
          );
        }
      } else {
        let caretPosition = event.target.selectionStart;
        if (
          (event.keyCode >= 48 && event.keyCode <= 57) ||
          (event.keyCode >= 96 && event.keyCode <= 105)
        ) {
          const asYouType = new AsYouType(this.selectedCountry.shortName);

          this.$nextTick(() => {
            const charBefore = event.target.value.charAt(caretPosition - 1);
            const charAfter = event.target.value.charAt(caretPosition + 1);
            caretPosition =
              charBefore === ' ' || charBefore === '-'
                ? caretPosition + 1
                : caretPosition;
            caretPosition =
              charAfter === '-' || charAfter === ')'
                ? caretPosition + 2
                : caretPosition;
            event.target.setSelectionRange(caretPosition, caretPosition);
          });
          try {
            /**
             * Triggered when entering the field
             */
            this.$emit('input', asYouType.input(event.target.value));
          } catch (error) {
            /**
             * Triggered when entering the field
             */
            this.$emit(
              'input',
              formatIncompletePhoneNumber(
                event.target.value,
                this.selectedCountry.shortName,
              ),
            );
          }
        }
      }
    },
    onChangePhoneNumber(event) {
      const phoneNumber = event ? event.target.value : this.phoneNumber;
      try {
        const parsedPhoneNumber = parsePhoneNumber(
          phoneNumber,
          this.selectedCountry.shortName,
        );
        /**
         * Triggered by the onBlur event
         */
        this.$emit('blur', parsedPhoneNumber.formatInternational());

        if (phoneNumber) {
          this.phoneNumber = parsedPhoneNumber.formatNational();
        }
      } catch (error) {
        const incompletePhoneNumber = formatIncompletePhoneNumber(
          phoneNumber,
          this.selectedCountry.shortName,
        );
        /**
         * Triggered by the onBlur event
         */
        this.$emit('blur', incompletePhoneNumber);

        this.phoneNumber = incompletePhoneNumber;
      }

      if (event && event.target) {
        event.target.value = this.phoneNumber;
      }
    },
    onChangeCountry(country) {
      this.selectedCountry = country;
      this.closeCountriesList();
      /**
       * Triggered when a new country is selected
       */
      this.$emit('change-country', country);
      if (this.hasValue) {
        this.onChangePhoneNumber();
      }
    },
    toggleCountriesList() {
      this.$refs.countriesList.offsetParent
        ? this.onChangeCountry(this.selectedCountry)
        : this.openCountriesList();
    },
    openCountriesList() {
      /**
       * Triggered when the country list opens
       */
      this.$emit('open-countries-list');
      this.isCountriesListOpened = true;
      this.scrollToSelectedCountry();
    },
    closeCountriesList() {
      /**
       * Triggered when the country list closes
       */
      this.$emit('close-countries-list');
      this.isCountriesListOpened = false;
    },
    cleanFormat(event) {
      this.phoneNumber = formatIncompletePhoneNumber(event.target.value);
    },
  },
};
</script>

<style scoped lang="scss">
@import 'settings-tools/all-settings';
@import 'components/c.text-input';
@import 'components/c.phone-number';

.mc-phone-number {
  span.flag {
    /* stylelint-disable */
    background-repeat: no-repeat;
    transform: scale(0.46153846153);
    // For information:
    // The value 0.46153846153 is obtained by the calculation 24/52
    // 24 being the width in pixel desired by the design of the component
    // 52 being the width in pixel defined by the country-flag component
    /* stylelint-enable */
  }

  button.is-invalid {
    border-color: $color-input-invalid-border;

    &:hover,
    &.is-hover {
      border-color: $color-input-invalid-hover-border;
    }
  }

  button.is-valid {
    border-color: $color-input-valid-border;

    &:hover,
    &.is-hover {
      border-color: $color-input-valid-hover-border;
    }
  }

  &__dropdown {
    z-index: var(--phone-number-dropdown-z-index, 20);
  }
}
</style>
