<template>
  <el-input
    v-test-id="'input-phone-number'"
    v-model="model"
    :disabled="disabled">
    <el-select
      slot="prepend"
      v-test-id="'country-selector'"
      v-model="rawRegionCode"
      :disabled="disabled"
      filterable>
      <el-option
        v-for="countryOption in allCountryOptions"
        :key="countryOption.alpha2Code"
        :label="`+${countryOption.callingCode} ${countryOption.alpha3Code}`"
        :value="countryOption.alpha2Code">
      </el-option>
    </el-select>
    <template slot="append">
      <slot name="append"></slot>
    </template>
  </el-input>
</template>

<script>
import { negate, isUndefined, sortBy, isEmpty } from 'lodash';
import countries from 'i18n-iso-countries';
import { formatNumber, parseNumber, getCountryCallingCode, isValidNumber } from 'libphonenumber-js';

const DEFAULT_REGION_CODE = 'US';

export default {
  name: 'ElInputPhoneNumber',
  props: {
    value: { default: '', type: String },
    regionCode: { default: DEFAULT_REGION_CODE, type: String },
    disabled: Boolean
  },
  data() {
    return {
      rawPhoneNumber: '',
      rawRegionCode: this.regionCode,

      lastEmittedValue: ''
    };
  },
  computed: {
    allCountryOptions: function() {
      let countryList = Object.keys(countries.getAlpha2Codes());

      let options = countryList.map(alpha2Code => {
        try {
          const callingCode = getCountryCallingCode(alpha2Code);
          const alpha3Code = countries.alpha2ToAlpha3(alpha2Code);

          return {
            callingCode,
            alpha2Code,
            alpha3Code
          };
        } catch (e) {
          // Ignoring this exception since it results from some countries defined in ISO-3166-2 but
          // not supported by libphonenumber. `getCountryCallingCode` throws an exception in this case,
          // but since there's nothing we can do we'll just ignore it.
        }
      }).filter(negate(isUndefined));

      return sortBy(options, option => parseInt(option.callingCode, 10));
    },

    model: {
      get() {
        return this.rawPhoneNumber;
      },
      set(rawPhoneNumber) {
        // vars, parsing and validation
        const { rawRegionCode } = this;
        let display /* displayed value with formatting applied */, value /* actual value of the v-model in E.164 */, regionCode;
        const phone = parseNumber(rawPhoneNumber, { defaultCountry: rawRegionCode, extended: false });
        const isValid = isValidNumber(rawPhoneNumber, rawRegionCode);

        // populate vars
        if (isEmpty(phone) || !isValid) {
          display = rawPhoneNumber;
          regionCode = rawRegionCode;
          // The entered number is not valid. To avoid validators accepting this as an empty
          // phone number, we'll return the user's input + the prefix
          // If the input is indeed empty, just return as-is
          if (rawPhoneNumber.length) value = '+' + getCountryCallingCode(rawRegionCode) + rawPhoneNumber;
          else value = '';
        } else {
          display = formatNumber(phone, 'National');
          value = formatNumber(phone, 'E.164') + (phone.ext ? `x${phone.ext}` : '');
          regionCode = phone.country;
        }

        // update state
        this.rawPhoneNumber = display;
        this.rawRegionCode = regionCode;
        this.lastEmittedValue = value;
        this.$emit('input', value);
      }
    }
  },
  watch: {
    rawRegionCode(rawRegionCode) {
      this.$emit('update:regionCode', rawRegionCode);
      this.model = this.rawPhoneNumber; // 'hack' to trigger recompute with updated region code
    },

    value: {
      handler: function(val) {
        if (val === this.lastEmittedValue) return;
        const phone = parseNumber(val, { defaultCountry: this.rawRegionCode, extended: false });

        if (isEmpty(phone)) this.model = val;
        else {
          this.rawRegionCode = phone.country;
          this.model = phone.ext ? `${phone.phone}x${phone.ext}` : phone.phone;
        }
      },

      immediate: true
    }
  }
};
</script>
