import { MIN_LEADING_DIGITS_LENGTH, DIGIT_PLACEHOLDER } from './types';
import { META, getCountryCallingCode, getFormats, detectCallingCode, getCountryByNationalNumber, } from './metadata';
import { isNonAlteringFormat, formatSuits, formatMatchesLeadingDigits, buildTemplate, 
// eslint-disable-next-line spellcheck/spell-checker
cutAndStripNonPairedParens, applyInternationalSeparatorStyle, } from './format-helpers';
// ---------------------------------------------------------------------------
// formatIncompletePhoneNumber
// ---------------------------------------------------------------------------
/**
 * Format a possibly-incomplete phone number for display.
 *
 * Internally uses AsYouType to produce correct progressive formatting.
 *
 * @param {string} number - the raw phone number string to format
 * @param {VegaCountryCode} [country] - optional default country code
 * @returns {string} the formatted incomplete phone number
 * @example formatIncompletePhoneNumber('3023334444', 'US') // '(302) 333-4444'
 * @example formatIncompletePhoneNumber('+13023334444')      // '+1 302 333 4444'
 */
export function formatIncompletePhoneNumber(number, country) {
  if (!number)
    return '';
  const formatter = new AsYouType(country);
  return formatter.input(number);
}
// ---------------------------------------------------------------------------
// AsYouType
// ---------------------------------------------------------------------------
/**
 * Progressive phone number formatter.
 *
 * Internally uses template-based formatting matching libphonenumber-js.
 */
export class AsYouType {
  constructor(defaultCountry) {
    this._allDigits = '';
    this._hasPlus = false;
    this._nationalNumber = '';
    this._matchingFormats = [];
    this._populatedPosition = -1;
    /**
     * Stores a complex prefix (e.g. national prefix captured groups) that was
     * stripped from the national number. This prefix is re-prepended when
     * producing output.  Mirrors `complexPrefixBeforeNationalSignificantNumber`
     * in the original library.
     */
    this._complexPrefix = '';
    this._country = defaultCountry;
    if (defaultCountry) {
      this._callingCode = getCountryCallingCode(defaultCountry);
      this.initFormats(defaultCountry);
    }
  }
  /**
   * The currently detected country.
   *
   * @returns {VegaCountryCode | undefined} the detected country code or undefined
   */
  get country() {
    return this._country;
  }
  /**
   * Feed characters into the formatter. Returns the formatted number so far.
   *
   * The original library processes one digit at a time, running narrow-down
   * and format logic after each digit.  This is critical because formats may
   * be eliminated at intermediate digit counts, triggering national-prefix
   * re-extraction that changes the output structure.  We replicate that
   * behavior here by looping over each digit individually.
   *
   * @param {string} text - the raw input characters to process
   * @returns {string} the formatted phone number so far
   */
  input(text) {
    let result = '';
    for (let i = 0; i < text.length; i++) {
      const char = text[i];
      if (char === '+' && this._allDigits.length === 0 && !this._hasPlus) {
        this._hasPlus = true;
        result = '+';
        continue;
      }
      if (char < '0' || char > '9')
        continue;
      this._allDigits += char;
      this.updateState();
      this.tryReExtractNationalPrefix();
      this.narrowDownFormats();
      const formattedNational = this.formatDigit(char);
      if (formattedNational === undefined) {
        result = this.getNonFormattedOutput();
      }
      else {
        result = this.getFullOutput(formattedNational);
      }
    }
    if (!result) {
      if (this._hasPlus && this._allDigits.length === 0) {
        return '+';
      }
      return this.getNonFormattedOutput();
    }
    return result;
  }
  // ------- Private methods -------
  /**
   * Update internal state after a new digit is added.
   * Detects calling code and country for international numbers,
   * or keeps the full digit string as the national number in national mode.
   *
   * @returns {void}
   */
  updateState() {
    if (this._hasPlus) {
      const detected = detectCallingCode(this._allDigits);
      if (detected.callingCode) {
        const prevCallingCode = this._callingCode;
        this._callingCode = detected.callingCode;
        this._nationalNumber = this._allDigits.substring(detected.callingCode.length);
        if (detected.countries && detected.countries.length === 1) {
          const newCountry = detected.countries[0];
          if (newCountry !== this._country) {
            this._country = newCountry;
            this.initFormats(newCountry);
            this.resetFormat();
          }
        }
        else if (detected.countries && detected.countries.length > 1) {
          const matched = getCountryByNationalNumber(detected.countries, this._nationalNumber);
          this._country = matched;
          if (prevCallingCode !== detected.callingCode) {
            this.initFormats(detected.countries[0]);
            this.resetFormat();
          }
        }
      }
      else {
        this._nationalNumber = this._allDigits;
      }
    }
    else {
      // National mode — tryReExtractNationalPrefix handles prefix splitting.
      this._nationalNumber = this._allDigits;
    }
  }
  /**
   * Initialize the list of candidate format rules for a given country.
   * Filters out formats that alter the number or do not suit the current mode.
   *
   * @param {VegaCountryCode} country - the country whose formats to load
   * @returns {void}
   */
  initFormats(country) {
    const formats = getFormats(country);
    if (formats) {
      const international = this._hasPlus;
      this._matchingFormats = formats.filter((formatRule) => isNonAlteringFormat(formatRule[1]) &&
        formatSuits(formatRule, international, /* nationalPrefix */ false));
    }
    else {
      this._matchingFormats = [];
    }
  }
  /**
   * Narrow down matching formats based on the leading digits of the national number.
   * Removes formats that no longer match and resets the chosen format if it was eliminated.
   *
   * @returns {void}
   */
  narrowDownFormats() {
    if (!this._nationalNumber)
      return;
    const leadingDigitsPatternIndex = Math.max(0, this._nationalNumber.length - MIN_LEADING_DIGITS_LENGTH);
    this._matchingFormats = this._matchingFormats.filter((formatRule) => formatMatchesLeadingDigits(formatRule, this._nationalNumber, leadingDigitsPatternIndex));
    if (this._chosenFormat && !this._matchingFormats.includes(this._chosenFormat)) {
      this.resetFormat();
    }
  }
  /**
   * Attempt to format the national number after a new digit is added.
   * Tries complete formatting first, then template-based, then chooses a new format.
   *
   * @param {string} newDigits - the newly added digit characters
   * @returns {string | undefined} the formatted national number or undefined if no format applies
   */
  formatDigit(newDigits) {
    if (!this._nationalNumber)
      return undefined;
    // 1. Try formatting as a complete number first
    const completeFormatted = this.tryFormatComplete();
    if (completeFormatted !== undefined) {
      return completeFormatted;
    }
    // 2. Try using existing template
    if (this._chosenFormat && this._populatedTemplate) {
      const templateResult = this.formatNextDigits(newDigits);
      if (templateResult !== undefined) {
        return templateResult;
      }
      this.resetFormat();
    }
    // 3. Choose a new format and create a template
    return this.chooseFormatAndApply();
  }
  /**
   * Try to format the national number as a complete number using the matching format rules.
   * Only attempts formatting when the number length is a possible length for the country.
   *
   * @returns {string | undefined} the formatted number if a complete match is found, or undefined
   */
  tryFormatComplete() {
    const nationalDigits = this._nationalNumber;
    // Only try complete formatting if the number length is a possible length
    // for the country. This mirrors canFormatCompleteNumber from the original.
    if (this._country) {
      const entry = META.countries[this._country];
      if (entry) {
        const possibleLengths = entry[3];
        if (possibleLengths && possibleLengths.includes(nationalDigits.length)) {
          // length is valid, continue
        }
        else if (possibleLengths) {
          return undefined;
        }
      }
    }
    for (const formatRule of this._matchingFormats) {
      const [patternSrc, replacement] = [
        formatRule[0],
        formatRule[1],
      ];
      const fullPattern = new RegExp('^(?:' + patternSrc + ')$');
      if (fullPattern.test(nationalDigits)) {
        const formatted = nationalDigits.replace(new RegExp(patternSrc), replacement);
        this.resetFormat();
        this._chosenFormat = formatRule;
        const template = buildTemplate(formatRule, nationalDigits.length);
        if (template) {
          const populatedResult = this.populateTemplateFull(template, nationalDigits);
          if (populatedResult) {
            this._populatedTemplate = populatedResult.filled;
            this._populatedPosition = populatedResult.lastPosition;
          }
        }
        return formatted;
      }
    }
    return undefined;
  }
  /**
   * Choose a new format from the remaining candidates and apply it to the national number.
   * Builds a template for each candidate and returns the formatted result on first match.
   *
   * @returns {string | undefined} the formatted national number or undefined if no format matches
   */
  chooseFormatAndApply() {
    for (const formatRule of this._matchingFormats) {
      if (this._chosenFormat === formatRule)
        break;
      if (!isNonAlteringFormat(formatRule[1]))
        continue;
      const template = buildTemplate(formatRule, this._nationalNumber.length);
      if (!template) {
        this._matchingFormats = this._matchingFormats.filter((f) => f !== formatRule);
        continue;
      }
      this._chosenFormat = formatRule;
      this._populatedTemplate = template;
      this._populatedPosition = -1;
      return this.formatNextDigits(this._nationalNumber);
    }
    this.resetFormat();
    return undefined;
  }
  /**
   * Insert digits into the current populated template one at a time.
   * Replaces placeholder characters with the actual digits.
   *
   * @param {string} digits - the digit characters to insert into the template
   * @returns {string | undefined} the partially formatted string or undefined if the template is exhausted
   */
  formatNextDigits(digits) {
    if (!this._populatedTemplate)
      return undefined;
    let template = this._populatedTemplate;
    let position = this._populatedPosition;
    for (const digit of digits) {
      const searchFrom = position + 1;
      const nextIndex = template.indexOf(DIGIT_PLACEHOLDER, searchFrom);
      if (nextIndex === -1) {
        return undefined;
      }
      template = template.substring(0, nextIndex) + digit + template.substring(nextIndex + 1);
      position = nextIndex;
    }
    this._populatedTemplate = template;
    this._populatedPosition = position;
    // eslint-disable-next-line spellcheck/spell-checker
    return cutAndStripNonPairedParens(template, position + 1);
  }
  /**
   * Fill an entire template string with the given digits.
   * Used to synchronize the template state after a complete format match.
   *
   * @param {string} template - the format template containing digit placeholders
   * @param {string} digits - the digit characters to populate the template with
   * @returns {PopulatedTemplateResult | undefined} the filled template and last position, or undefined if the template ran out of placeholders
   */
  populateTemplateFull(template, digits) {
    let result = template;
    let lastPosition = -1;
    for (const digit of digits) {
      const index = result.indexOf(DIGIT_PLACEHOLDER);
      if (index === -1)
        return undefined;
      result = result.substring(0, index) + digit + result.substring(index + 1);
      lastPosition = index;
    }
    return { filled: result, lastPosition: lastPosition };
  }
  /**
   * Reset the chosen format, populated template, and position to their initial values.
   *
   * @returns {void}
   */
  resetFormat() {
    this._chosenFormat = undefined;
    this._populatedTemplate = undefined;
    this._populatedPosition = -1;
  }
  /**
   * Try to extract a national prefix from the current national number.
   * Uses the country's `nationalPrefixForParsing` pattern (metadata index [7]).
   *
   * Called proactively on every digit input, mirroring the original library's
   * `reExtractNationalSignificantNumber`.  When extraction first succeeds
   * (or the extracted prefix changes), formats are re-initialized so that
   * only formats appropriate for the shorter national number remain.
   *
   * @returns {void}
   */
  tryReExtractNationalPrefix() {
    if (!this._country || this._hasPlus)
      return;
    const entry = META.countries[this._country];
    if (!entry)
      return;
    const prefixPattern = entry[7];
    if (!prefixPattern || typeof prefixPattern !== 'string')
      return;
    const allDigits = this._allDigits;
    const match = allDigits.match(new RegExp('^(?:' + prefixPattern + ')'));
    if (!match || match[0].length === 0)
      return;
    const newPrefix = match[0];
    const remaining = allDigits.substring(newPrefix.length);
    // If the prefix hasn't changed, just re-apply the split and return.
    if (this._complexPrefix === newPrefix) {
      this._nationalNumber = remaining;
      return;
    }
    // New or changed prefix extraction — update state and re-init formats.
    this._complexPrefix = newPrefix;
    this._nationalNumber = remaining;
    this.resetFormat();
    if (this._country) {
      this.initFormats(this._country);
    }
  }
  /**
   * Build the full output string by prepending the calling code or complex prefix
   * to the formatted national number.
   *
   * @param {string} formattedNational - the formatted national number portion
   * @returns {string} the complete formatted phone number with prefix
   */
  getFullOutput(formattedNational) {
    if (this._hasPlus && this._callingCode) {
      if (formattedNational) {
        const intlNational = applyInternationalSeparatorStyle(formattedNational);
        return '+' + this._callingCode + ' ' + intlNational;
      }
      return '+' + this._callingCode;
    }
    if (this._complexPrefix) {
      return this._complexPrefix + ' ' + formattedNational;
    }
    return formattedNational;
  }
  /**
   * Produce a fallback output when no format template matches.
   * Returns the raw digits with the plus sign or complex prefix if applicable.
   *
   * @returns {string} the non-formatted output string
   */
  getNonFormattedOutput() {
    if (this._hasPlus) {
      return '+' + this._allDigits;
    }
    if (this._complexPrefix && this._nationalNumber) {
      return this._complexPrefix + ' ' + this._nationalNumber;
    }
    return this._allDigits;
  }
}
