import { forwardAttrs } from '@descope-ui/common/components-helpers';
import { createBaseInputClass } from '../../../../baseClasses/createBaseInputClass';
import { getComponentName } from '../../../../helpers/componentHelpers';
import { getCountryByCodeId, matchingParenthesis } from '../../helpers';
import parsePhoneNumberFromString, { AsYouType } from 'libphonenumber-js/min';

export const componentName = getComponentName('phone-field-internal-input-box');

const observedAttributes = [
  'disabled',
  'size',
  'readonly',
  'phone-input-placeholder',
  'name',
  'maxlength',
  'autocomplete',
  'label-type',
];
const mapAttrs = {
  'phone-input-placeholder': 'placeholder',
};

const BaseInputClass = createBaseInputClass({ componentName, baseSelector: 'div' });

class PhoneFieldInternal extends BaseInputClass {
  static get observedAttributes() {
    return [].concat(BaseInputClass.observedAttributes || [], observedAttributes);
  }

  #ayt;

  constructor() {
    super();

    this.innerHTML = `
      <div>
        <descope-text-field tabindex="1" type="tel" bordered="false"></descope-text-field>
      </div>
      `;

    this.textField = this.querySelector('descope-text-field');
  }

  // notice: this function is exposed in parent component
  get phoneNumberInputEle() {
    return this.textField.shadowRoot.querySelector('input');
  }

  get defaultDialCode() {
    return getCountryByCodeId(this.getAttribute('default-code'));
  }

  get defaultCode() {
    return this.getAttribute('default-code');
  }

  get allowAlphanumericInput() {
    return this.getAttribute('allow-alphanumeric-input') === 'true';
  }

  get minLength() {
    return parseInt(this.getAttribute('minlength'), 10) || 0;
  }

  get maxLength() {
    return parseInt(this.getAttribute('maxlength'), 10) || 50;
  }

  get restrictCountries() {
    return this.getAttribute('restrict-countries')?.split(',').filter(Boolean) || [];
  }

  get isFormatValue() {
    return this.getAttribute('format-value') === 'true';
  }

  get isStrictValidation() {
    return this.getAttribute('strict-validation') === 'true';
  }

  get value() {
    if (!this.textField.value) return '';

    if (!this.isStrictValidation) {
      return this.#nonParsedValue();
    }

    const parsedVal = this.#parseWithCountryCode();

    if (parsedVal?.country && parsedVal?.countryCallingCode && parsedVal?.nationalNumber) {
      return `+${[parsedVal?.countryCallingCode, parsedVal?.nationalNumber].join('-')}`;
    }

    // if failed to parse or to find country code return text field value
    return this.textField.value;
  }

  set value(val) {
    this.textField.value = val;
  }

  init() {
    this.addEventListener('focus', (e) => {
      // We want to ignore focus events we are dispatching
      if (e.isTrusted) this.textField.focus();
    });

    super.init?.();

    this.textField.addEventListener('input', this.#onInput.bind(this));
    this.handleFocusEventsDispatching([this.textField]);

    forwardAttrs(this.textField, this, { includeAttrs: ['has-value'] });
  }

  getValidity() {
    const validPhonePattern = /^\+?\d{1,4}-?(?:\d-?){1,15}$/;
    const stripValue = this.#sanitizeVal(this.textField.value || '');

    if (this.isRequired && !this.textField.value) {
      return { valueMissing: true };
    }

    if (this.textField.value) {
      if (stripValue.length < this.minLength) {
        return { tooShort: true };
      }

      if (
        // has `strict-validation` and not properly parsed
        (this.isStrictValidation && this.textField.value && !this.#isValidParsedValue()) ||
        // if no `strict-validation` then conform with naive pattern
        (!this.isStrictValidation && this.textField.value && !validPhonePattern.test(this.value))
      ) {
        return { patternMismatch: true };
      }
    }

    return {};
  }

  setSelectionRange(...args) {
    this.textField.setSelectionRange(...args);
  }

  attributeChangedCallback(attrName, oldValue, newValue) {
    super.attributeChangedCallback(attrName, oldValue, newValue);

    if (oldValue !== newValue && observedAttributes.includes(attrName)) {
      const attr = mapAttrs[attrName] || attrName;
      this.textField.setAttribute(attr, newValue);
    }
  }

  #onInput(e) {
    let sanitizedInput = this.#sanitizeInput(e.target.value);

    if (this.isFormatValue && this.#canFormat(sanitizedInput)) {
      sanitizedInput = this.#formatPhoneNumber(sanitizedInput);
    }

    e.target.value = sanitizedInput;
  }

  #nonParsedValue() {
    if (!this.defaultDialCode) {
      return this.textField.value;
    }

    const nationalNumber = this.#trimDuplicateCountryCode(this.textField.value);
    const sanitizedVal = this.#sanitizeVal(nationalNumber);

    return [this.defaultDialCode, sanitizedVal].join('-');
  }

  #parseWithCountryCode() {
    if (this.defaultDialCode) {
      return parsePhoneNumberFromString(
        [this.defaultDialCode, this.#sanitizeVal(this.textField.value)].filter(Boolean).join('')
      );
    }

    // if default-code or not parsed - try to extract country code from value
    return parsePhoneNumberFromString(this.textField.value);
  }

  #sanitizeVal(val) {
    return val.replace(/\D/g, '');
  }

  #trimDuplicateCountryCode(val) {
    if (this.textField.value?.[0] === '+') {
      const dialCodePrefixPattern = new RegExp(`^\\${this.defaultDialCode}`);
      const trimmed = val.replace(dialCodePrefixPattern, '');
      return trimmed;
    }
    return val;
  }

  #isValidParsedValue() {
    const parsed = parsePhoneNumberFromString(this.value);
    return (
      !!parsed && // parsed successfully (not undefined)
      !!parsed.isValid?.() && // Parsed object is valid
      !!parsed.country && // Parsed object with a country code
      !!this.#isAllowedCountry(parsed.country) && // Parsed with allowed country code
      (this.defaultCode ? this.defaultCode === parsed.country : true) // In case default country code is set validate parsed country matches it
    );
  }

  #isAllowedCountry(countryCode) {
    if (!this.restrictCountries.length) {
      return true;
    }

    return this.restrictCountries.includes(countryCode);
  }

  #sanitizeInput(val) {
    val = val
      .replace(/^-+/, '') // dash as first char
      .replace(/(?!^)\+/g, '') // multiple plus symbols
      .replace('--', '-') // consecutive dashes
      .replace('+-', '+'); // dash following plus symbol

    if (!this.allowAlphanumericInput) {
      const telDigitsRegExp = /^[+\d-\(\)]+$/;
      val = val
        .split('')
        .filter((char) => telDigitsRegExp.test(char))
        .join('');
    }

    return val;
  }

  #formatPhoneNumber(phoneNumber = '') {
    // Get country code from `default-code or` from phone number
    const countryCode = this.defaultCode || this.#getCountryCodeFromValue(phoneNumber);

    // Skip formatting if no country code is available
    if (!countryCode) {
      return phoneNumber;
    }

    // Update AsYouType country code if needed
    if (!this.#ayt || this.#ayt.country !== countryCode) {
      this.#ayt = new AsYouType(countryCode);
    }

    // We need to reset AsYouType instance before setting new input
    this.#ayt.reset();

    // Set AsYouType input
    const formattedVal = this.#ayt.input(phoneNumber) || phoneNumber;

    return formattedVal;
  }

  #getCountryCodeFromValue(val) {
    const parsed = parsePhoneNumberFromString(val);
    return parsed?.country || '';
  }

  #canFormat(val) {
    if (!matchingParenthesis(val)) return false;
    return true;
  }
}

export default PhoneFieldInternal;
