import compare from './tools/semver-compare.js';
import isObject from './helpers/isObject.js';
import isCallingCode from './helpers/isCallingCode.js';

// Added "possibleLengths" and renamed
// "country_phone_code_to_countries" to "country_calling_codes".
const V2 = '1.0.18';

// Added "idd_prefix" and "default_idd_prefix".
const V3 = '1.2.0';

// Moved `001` country code to "nonGeographic" section of metadata.
const V4 = '1.7.35';
const DEFAULT_EXT_PREFIX = ' ext. ';

/**
 * See: https://gitlab.com/catamphetamine/libphonenumber-js/blob/master/METADATA.md
 */
export default class Metadata {
  /**
   * @param {object} metadata — Metadata JSON.
   */
  constructor(metadata) {
    validateMetadata(metadata);
    this.metadata = metadata;
    setVersion.call(this, metadata);
  }
  getCountries() {
    return Object.keys(this.metadata.countries).filter(_ => _ !== '001');
  }
  getCountryMetadata(countryCode) {
    return this.metadata.countries[countryCode];
  }
  nonGeographic() {
    if (this.v1 || this.v2 || this.v3) return;
    // `nonGeographical` was a typo.
    // It's present in metadata generated from `1.7.35` to `1.7.37`.
    // The test case could be found by searching for "nonGeographical".
    return this.metadata.nonGeographic || this.metadata.nonGeographical;
  }
  hasCountry(country) {
    return this.getCountryMetadata(country) !== undefined;
  }
  hasCallingCode(callingCode) {
    if (this.getCountryCodesForCallingCode(callingCode)) {
      return true;
    }
    if (this.nonGeographic()) {
      if (this.nonGeographic()[callingCode]) {
        return true;
      }
    } else {
      // A hacky workaround for old custom metadata (generated before V4).
      const countryCodes = this.countryCallingCodes()[callingCode];
      if (countryCodes && countryCodes.length === 1 && countryCodes[0] === '001') {
        return true;
      }
    }
  }
  isNonGeographicCallingCode(callingCode) {
    if (this.nonGeographic()) {
      return this.nonGeographic()[callingCode] ? true : false;
    } else {
      return this.getCountryCodesForCallingCode(callingCode) ? false : true;
    }
  }

  // Deprecated.
  country(countryCode) {
    return this.selectNumberingPlan(countryCode);
  }

  /**
   * Selects a telephone numbering plan based on either a country code or a calling code.
   * @param {string} argument — Country code or calling code.
   * @param {string} legacyArgumentCallingCode — Legacy argument: calling code, if the first argument is not passed.
   */
  selectNumberingPlan(argument, legacyArgumentCallingCode) {
    let countryCode;
    let callingCode;
    if (argument) {
      if (isCallingCode(argument)) {
        callingCode = argument;
      } else {
        countryCode = argument;
      }
    }
    // Previously, the signature of this method was: `.selectNumberingPlan(country, callingCode)`.
    // And if `country` argument wasn't passed, it looked into the second argument `callingCode`.
    // Now it's preferred to only pass either `country` or `callingCode` as the first argument.
    if (legacyArgumentCallingCode) {
      callingCode = legacyArgumentCallingCode;
    }

    // Select a telephone numbering plan.
    if (countryCode && countryCode !== '001') {
      const countryMetadata = this.getCountryMetadata(countryCode);
      if (!countryMetadata) {
        throw new Error(`Unknown country: ${countryCode}`);
      }
      this.numberingPlan = new NumberingPlan(countryMetadata, this);
    } else if (callingCode) {
      if (!this.hasCallingCode(callingCode)) {
        throw new Error(`Unknown calling code: ${callingCode}`);
      }
      this.numberingPlan = new NumberingPlan(this.getNumberingPlanMetadata(callingCode), this);
    } else {
      this.numberingPlan = undefined;
    }
    return this;
  }
  getCountryCodesForCallingCode(callingCode) {
    const countryCodes = this.countryCallingCodes()[callingCode];
    if (countryCodes) {
      // Metadata before V4 included "non-geographic entity" calling codes
      // inside `country_calling_codes` (for example, `"881":["001"]`).
      // Now the semantics of `country_calling_codes` has changed:
      // it's specifically for "countries" now.
      // Older versions of custom metadata will simply skip parsing
      // "non-geographic entity" phone numbers with new versions
      // of this library: it's not considered a bug,
      // because such numbers are extremely rare,
      // and developers extremely rarely use custom metadata.
      if (countryCodes.length === 1 && countryCodes[0].length === 3) {
        return;
      }
      return countryCodes;
    }
  }
  getCountryCodeForCallingCode(callingCode) {
    const countryCodes = this.getCountryCodesForCallingCode(callingCode);
    if (countryCodes) {
      return countryCodes[0];
    }
  }
  getNumberingPlanMetadata(callingCode) {
    // If a calling code is specified, choose the telephone numbering plan
    // of the "default" country for this calling code.
    // For example, if calling code `1` is passed,
    // the telephone numbering plan of `US` will be selected.
    const countryCode = this.getCountryCodeForCallingCode(callingCode);
    if (countryCode) {
      return this.getCountryMetadata(countryCode);
    }
    if (this.nonGeographic()) {
      const metadata = this.nonGeographic()[callingCode];
      if (metadata) {
        return metadata;
      }
    } else {
      // A hacky workaround for old custom metadata (generated before V4).
      // In that metadata, there was no concept of "non-geographic" metadata
      // so metadata for `001` country code was stored along with other countries.
      // The test case can be found by searching for:
      // "should work around `nonGeographic` metadata not existing".
      const countryCodes = this.countryCallingCodes()[callingCode];
      if (countryCodes && countryCodes.length === 1 && countryCodes[0] === '001') {
        return this.metadata.countries['001'];
      }
    }
  }

  // Deprecated.
  countryCallingCode() {
    return this.numberingPlan.callingCode();
  }

  // Deprecated.
  IDDPrefix() {
    return this.numberingPlan.IDDPrefix();
  }

  // Deprecated.
  defaultIDDPrefix() {
    return this.numberingPlan.defaultIDDPrefix();
  }

  // Deprecated.
  nationalNumberPattern() {
    return this.numberingPlan.nationalNumberPattern();
  }

  // Deprecated.
  possibleLengths() {
    return this.numberingPlan.possibleLengths();
  }

  // Deprecated.
  formats() {
    return this.numberingPlan.formats();
  }

  // Deprecated.
  nationalPrefixForParsing() {
    return this.numberingPlan.nationalPrefixForParsing();
  }

  // Deprecated.
  nationalPrefixTransformRule() {
    return this.numberingPlan.nationalPrefixTransformRule();
  }

  // Deprecated.
  leadingDigits() {
    return this.numberingPlan.leadingDigits();
  }

  // Deprecated.
  hasTypes() {
    return this.numberingPlan.hasTypes();
  }

  // Deprecated.
  type(type) {
    return this.numberingPlan.type(type);
  }

  // Deprecated.
  ext() {
    return this.numberingPlan.ext();
  }
  countryCallingCodes() {
    if (this.v1) return this.metadata.country_phone_code_to_countries;
    return this.metadata.country_calling_codes;
  }

  // Deprecated.
  chooseCountryByCountryCallingCode(callingCode) {
    return this.selectNumberingPlan(callingCode);
  }
  hasSelectedNumberingPlan() {
    return this.numberingPlan !== undefined;
  }
}
class NumberingPlan {
  constructor(metadata, globalMetadataObject) {
    this.globalMetadataObject = globalMetadataObject;
    this.metadata = metadata;
    setVersion.call(this, globalMetadataObject.metadata);
  }
  callingCode() {
    return this.metadata[0];
  }

  // When multiple countries share the same calling code,
  // all phone number formatting rules are included in the metadata
  // of the "default" country for that calling code.
  // Any other countries' metdata doesn't include any formatting rules.
  // Google developers said that such storage architecture was chosen for performance reasons.
  //
  // For example, for NANPA region ("North American Numbering Plan Administration",
  // which includes USA, Canada, Cayman Islands, Bahamas, etc) all formatting rules
  // are contained in the metadata of `US` country.
  //
  // This is not public API.
  //
  _getDefaultCountryMetadataForThisCallingCode() {
    return this.globalMetadataObject.getNumberingPlanMetadata(this.callingCode());
  }

  // Deprecated.
  getDefaultCountryMetadataForRegion() {
    return this._getDefaultCountryMetadataForThisCallingCode();
  }

  // Is always present.
  IDDPrefix() {
    if (this.v1 || this.v2) return;
    return this.metadata[1];
  }

  // Is only present when a country supports multiple IDD prefixes.
  defaultIDDPrefix() {
    if (this.v1 || this.v2) return;
    return this.metadata[12];
  }
  nationalNumberPattern() {
    if (this.v1 || this.v2) return this.metadata[1];
    return this.metadata[2];
  }

  // "possible length" data is always present in Google's metadata.
  possibleLengths() {
    if (this.v1) return;
    return this.metadata[this.v2 ? 2 : 3];
  }
  _getFormats(metadata) {
    return metadata[this.v1 ? 2 : this.v2 ? 3 : 4];
  }

  // For countries of the same region (e.g. NANPA)
  // formats are all stored in the "main" country for that region.
  // E.g. "RU" and "KZ", "US" and "CA".
  formats() {
    const formats = this._getFormats(this.metadata) || this._getFormats(this._getDefaultCountryMetadataForThisCallingCode()) || [];
    return formats.map(_ => new Format(_, this));
  }
  nationalPrefix() {
    return this.metadata[this.v1 ? 3 : this.v2 ? 4 : 5];
  }
  _getNationalPrefixFormattingRule(metadata) {
    return metadata[this.v1 ? 4 : this.v2 ? 5 : 6];
  }

  // For countries of the same region (e.g. NANPA)
  // national prefix formatting rule is stored in the "main" country for that region.
  // E.g. "RU" and "KZ", "US" and "CA".
  nationalPrefixFormattingRule() {
    return this._getNationalPrefixFormattingRule(this.metadata) || this._getNationalPrefixFormattingRule(this._getDefaultCountryMetadataForThisCallingCode());
  }
  _nationalPrefixForParsing() {
    return this.metadata[this.v1 ? 5 : this.v2 ? 6 : 7];
  }
  nationalPrefixForParsing() {
    // If `national_prefix_for_parsing` is not set explicitly,
    // then infer it from `national_prefix` (if any)
    return this._nationalPrefixForParsing() || this.nationalPrefix();
  }
  nationalPrefixTransformRule() {
    return this.metadata[this.v1 ? 6 : this.v2 ? 7 : 8];
  }
  _getNationalPrefixIsOptionalWhenFormatting() {
    return !!this.metadata[this.v1 ? 7 : this.v2 ? 8 : 9];
  }

  // For countries of the same region (e.g. NANPA)
  // "national prefix is optional when formatting" flag is
  // stored in the "main" country for that region.
  // E.g. "RU" and "KZ", "US" and "CA".
  nationalPrefixIsOptionalWhenFormattingInNationalFormat() {
    return this._getNationalPrefixIsOptionalWhenFormatting(this.metadata) || this._getNationalPrefixIsOptionalWhenFormatting(this._getDefaultCountryMetadataForThisCallingCode());
  }
  leadingDigits() {
    return this.metadata[this.v1 ? 8 : this.v2 ? 9 : 10];
  }
  types() {
    return this.metadata[this.v1 ? 9 : this.v2 ? 10 : 11];
  }
  hasTypes() {
    // Versions 1.2.0 - 1.2.4: can be `[]`.
    /* istanbul ignore next */
    if (this.types() && this.types().length === 0) {
      return false;
    }
    // Versions <= 1.2.4: can be `undefined`.
    // Version >= 1.2.5: can be `0`.
    return !!this.types();
  }
  type(type) {
    if (this.hasTypes() && getType(this.types(), type)) {
      return new Type(getType(this.types(), type), this);
    }
  }
  ext() {
    if (this.v1 || this.v2) return DEFAULT_EXT_PREFIX;
    return this.metadata[13] || DEFAULT_EXT_PREFIX;
  }
}
class Format {
  constructor(format, metadata) {
    this._format = format;
    this.metadata = metadata;
  }
  pattern() {
    return this._format[0];
  }
  format() {
    return this._format[1];
  }
  leadingDigitsPatterns() {
    return this._format[2] || [];
  }
  nationalPrefixFormattingRule() {
    return this._format[3] || this.metadata.nationalPrefixFormattingRule();
  }
  nationalPrefixIsOptionalWhenFormattingInNationalFormat() {
    return !!this._format[4] || this.metadata.nationalPrefixIsOptionalWhenFormattingInNationalFormat();
  }
  nationalPrefixIsMandatoryWhenFormattingInNationalFormat() {
    // National prefix is omitted if there's no national prefix formatting rule
    // set for this country, or when the national prefix formatting rule
    // contains no national prefix itself, or when this rule is set but
    // national prefix is optional for this phone number format
    // (and it is not enforced explicitly)
    return this.usesNationalPrefix() && !this.nationalPrefixIsOptionalWhenFormattingInNationalFormat();
  }

  // Checks whether national prefix formatting rule contains national prefix.
  usesNationalPrefix() {
    return this.nationalPrefixFormattingRule() &&
    // Check that national prefix formatting rule is not a "dummy" one.
    !FIRST_GROUP_ONLY_PREFIX_PATTERN.test(this.nationalPrefixFormattingRule())
    // In compressed metadata, `this.nationalPrefixFormattingRule()` is `0`
    // when `national_prefix_formatting_rule` is not present.
    // So, `true` or `false` are returned explicitly here, so that
    // `0` number isn't returned.
    ? true : false;
  }
  internationalFormat() {
    return this._format[5] || this.format();
  }
}

/**
 * A pattern that is used to determine if the national prefix formatting rule
 * has the first group only, i.e., does not start with the national prefix.
 * Note that the pattern explicitly allows for unbalanced parentheses.
 */
const FIRST_GROUP_ONLY_PREFIX_PATTERN = /^\(?\$1\)?$/;
class Type {
  constructor(type, metadata) {
    this.type = type;
    this.metadata = metadata;
  }
  pattern() {
    if (this.metadata.v1) return this.type;
    return this.type[0];
  }
  possibleLengths() {
    if (this.metadata.v1) return;
    return this.type[1] || this.metadata.possibleLengths();
  }
}
function getType(types, type) {
  switch (type) {
    case 'FIXED_LINE':
      return types[0];
    case 'MOBILE':
      return types[1];
    case 'TOLL_FREE':
      return types[2];
    case 'PREMIUM_RATE':
      return types[3];
    case 'PERSONAL_NUMBER':
      return types[4];
    case 'VOICEMAIL':
      return types[5];
    case 'UAN':
      return types[6];
    case 'PAGER':
      return types[7];
    case 'VOIP':
      return types[8];
    case 'SHARED_COST':
      return types[9];
  }
}
export function validateMetadata(metadata) {
  if (!metadata) {
    throw new Error('[libphonenumber-js] `metadata` argument not passed. Check your arguments.');
  }

  // `country_phone_code_to_countries` was renamed to `country_calling_codes` in `1.0.18`.
  // For that reason, it's not used in this detection algorithm.
  // Instead, it detects by `countries: {}` property existence.
  if (!isObject(metadata) || !isObject(metadata.countries)) {
    throw new Error(`[libphonenumber-js] \`metadata\` argument was passed but it's not a valid metadata. Must be an object having \`.countries\` child object property. Got ${isObject(metadata) ? 'an object of shape: { ' + Object.keys(metadata).join(', ') + ' }' : 'a ' + typeOf(metadata) + ': ' + metadata}.`);
  }
}

// Babel transforms `typeof` into some "branches"
// so istanbul will show this as "branch not covered".
/* istanbul ignore next */
const typeOf = _ => typeof _;

/**
 * Returns extension prefix for a country.
 * @param  {string} country
 * @param  {object} metadata
 * @return {string?}
 * @example
 * // Returns " ext. "
 * getExtPrefix("US")
 */
export function getExtPrefix(country, metadataJson) {
  const metadata = new Metadata(metadataJson);
  if (metadata.hasCountry(country)) {
    return metadata.selectNumberingPlan(country).ext();
  }
  return DEFAULT_EXT_PREFIX;
}

/**
 * Returns "country calling code" for a country.
 * Throws an error if the country doesn't exist or isn't supported by this library.
 * @param  {string} country
 * @param  {object} metadataJson
 * @return {string}
 * @example
 * // Returns "44"
 * getCountryCallingCode("GB")
 */
export function getCountryCallingCode(country, metadataJson) {
  const metadata = new Metadata(metadataJson);
  if (metadata.hasCountry(country)) {
    return metadata.selectNumberingPlan(country).countryCallingCode();
  }
  throw new Error(`Unknown country: ${country}`);
}

/**
 * Tells if a country code is supported.
 * @param  {string} country
 * @param  {object} metadataJson
 * @return {boolean}
 * @example
 * // Returns `true`
 * isSupportedCountry("GB")
 */
export function isSupportedCountry(country, metadataJson) {
  // const metadata = new Metadata(metadataJson)
  // return metadata.hasCountry(country)
  return metadataJson.countries.hasOwnProperty(country);
}

/**
 * Sets `v1`/`v2`/`v3`/`v4` properties on `this` object.
 * @param {object} metadata — Metadata JSON
 */
function setVersion(metadata) {
  const {
    version
  } = metadata;
  if (typeof version === 'number') {
    this.v1 = version === 1;
    this.v2 = version === 2;
    this.v3 = version === 3;
    this.v4 = version === 4;
  } else {
    if (!version) {
      this.v1 = true;
    } else if (compare(version, V3) === -1) {
      this.v2 = true;
    } else if (compare(version, V4) === -1) {
      this.v3 = true;
    } else {
      this.v4 = true;
    }
  }
}
//# sourceMappingURL=metadata.js.map