import React from 'react'

import { CountryData } from '../types'

export interface IntlTelInputProps {
  /**
   * Container CSS class name.
   * @default 'intl-tel-input'
   */
  containerClassName?: string
  /**
   * Text input CSS class name.
   * @default ''
   */
  inputClassName?: string
  /**
   * It's used as `input` field `name` attribute.
   * @default ''
   */
  fieldName?: string
  /**
   * It's used as `input` field `id` attribute.
   * @default ''
   */
  fieldId?: string
  /**
   * The value of the input field. Useful for making input value controlled from outside the component.
   */
  value?: string
  /**
   * The value used to initialize input. This will only work on uncontrolled component.
   * @default ''
   */
  defaultValue?: string
  /**
   * Countries data can be configured, it defaults to data defined in `AllCountries`.
   * @default AllCountries.getCountries()
   */
  countriesData?: CountryData[] | null
  /**
   * Whether or not to allow the dropdown. If disabled, there is no dropdown arrow, and the selected flag is not clickable.
   * Also we display the selected flag on the right instead because it is just a marker of state.
   * @default true
   */
  allowDropdown?: boolean
  /**
   * If there is just a dial code in the input: remove it on blur, and re-add it on focus.
   * @default true
   */
  autoHideDialCode?: boolean
  /**
   * Add or remove input placeholder with an example number for the selected country.
   * @default true
   */
  autoPlaceholder?: boolean
  /**
   * Change the placeholder generated by autoPlaceholder. Must return a string.
   * @default null
   */
  customPlaceholder?:
    | ((placeholder: string, selectedCountryData: CountryData) => string)
    | null
  /**
   * Don't display the countries you specify. (Array)
   * @default []
   */
  excludeCountries?: string[]
  /**
   * Format the input value during initialisation.
   * @default true
   */
  formatOnInit?: boolean
  /**
   * Display the country dial code next to the selected flag so it's not part of the typed number.
   * Note that this will disable nationalMode because technically we are dealing with international numbers,
   * but with the dial code separated.
   * @default false
   */
  separateDialCode?: boolean
  /**
   * Default country.
   * @default ''
   */
  defaultCountry?: string
  /**
   * GeoIp lookup function.
   * @default null
   */
  geoIpLookup?: (countryCode: string) => void
  /**
   * Don't insert international dial codes.
   * @default true
   */
  nationalMode?: boolean
  /**
   * Number type to use for placeholders.
   * @default 'MOBILE'
   */
  numberType?: string
  /**
   * The function which can catch the "no this default country" exception.
   * @default null
   */
  noCountryDataHandler?: (countryCode: string) => void
  /**
   * Display only these countries.
   * @default []
   */
  onlyCountries?: string[]
  /**
   * The countries at the top of the list. defaults to United States and United Kingdom.
   * @default ['us', 'gb']
   */
  preferredCountries?: string[]
  /**
   * Optional validation callback function. It returns validation status, input box value and selected country data.
   * @default null
   */
  onPhoneNumberChange?: (
    isValid: boolean,
    value: string,
    selectedCountryData: CountryData,
    fullNumber: string,
    extension: string,
  ) => void
  /**
   * Optional validation callback function. It returns validation status, input box value and selected country data.
   * @default null
   */
  onPhoneNumberBlur?: (
    isValid: boolean,
    value: string,
    selectedCountryData: CountryData,
    fullNumber: string,
    extension: string,
    event: React.FocusEvent<HTMLInputElement>,
  ) => void
  /**
   * Optional validation callback function. It returns validation status, input box value and selected country data.
   * @default null
   */
  onPhoneNumberFocus?: (
    isValid: boolean,
    value: string,
    selectedCountryData: CountryData,
    fullNumber: string,
    extension: string,
    event: React.FocusEvent<HTMLInputElement>,
  ) => void
  /**
   * Allow main app to do things when a country is selected.
   * @default null
   */
  onSelectFlag?: (
    currentNumber: string,
    selectedCountryData: CountryData,
    fullNumber: string,
    isValid: boolean,
  ) => void
  /**
   * Disable this component.
   * @default false
   */
  disabled?: boolean
  /**
   * Static placeholder for input controller. When defined it takes priority over autoPlaceholder.
   */
  placeholder?: string
  /**
   * Enable auto focus
   * @default false
   */
  autoFocus?: boolean
  /**
   * Set the value of the autoComplete attribute on the input.
   * For example, set it to phone to tell the browser where to auto complete phone numbers.
   * @default 'off'
   */
  autoComplete?: string
  /**
   * Style object for the wrapper div. Useful for setting 100% width on the wrapper, etc.
   */
  style?: React.CSSProperties
  /**
   * Render fullscreen flag dropdown when mobile useragent is detected.
   * The dropdown element is rendered as a direct child of document.body
   * @default true
   */
  useMobileFullscreenDropdown?: boolean
  /**
   * Pass through arbitrary props to the tel input element.
   * @default {}
   */
  telInputProps?: React.InputHTMLAttributes<HTMLInputElement>
  /**
   * Format the number.
   * @default true
   */
  format?: boolean
  /**
   * Allow main app to do things when flag icon is clicked.
   * @default null
   */
  onFlagClick?: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void
}

export interface IntlTelInputState {
  showDropdown: boolean
  highlightedCountry: number
  value: string
  disabled: boolean
  readonly: boolean
  offsetTop: number
  outerHeight: number
  placeholder: string
  title: string
  countryCode: string
  dialCode: string
  cursorPosition: any
}

export default class IntlTelInput extends React.Component<
  IntlTelInputProps,
  IntlTelInputState
> {
  //#region Properties
  wrapperClass: {
    [key: string]: boolean
  }

  defaultCountry?: string

  autoCountry: string

  tempCountry: string

  startedLoadingAutoCountry: boolean

  dropdownContainer?: React.ElementType | ''

  isOpening: boolean

  isMobile: boolean

  preferredCountries: CountryData[]

  countries: CountryData[]

  countryCodes: {
    [key: string]: string[]
  }

  windowLoaded: boolean

  query: string

  selectedCountryData?: CountryData

  // prop copies
  autoHideDialCode: boolean

  nationalMode: boolean

  allowDropdown: boolean

  // refs
  /**
   * `<div>` HTML element of the `FlagDropDown` React component.
   */
  flagDropDown: HTMLDivElement | null

  /**
   * `<input>` HTML element of the `TelInput` React component.
   */
  tel: HTMLInputElement | null

  // NOTE:
  // The underscore.deferred package doesn't have known type definitions.
  // The closest counterpart is jquery's Deferred object, which it claims to derive itself from.
  // These two are equivalent if you log it in console:
  //
  // underscore.deferred
  // var deferred = new _.Deferred()
  //
  // jquery
  // var deferred = $.Deferred()
  deferreds: JQuery.Deferred<any, any, any>[]

  autoCountryDeferred: JQuery.Deferred<any, any, any>

  utilsScriptDeferred: JQuery.Deferred<any, any, any>
  //#endregion

  //#region Methods
  /**
   * Updates flag when value of defaultCountry props change
   */
  updateFlagOnDefaultCountryChange(countryCode?: string): void

  getTempCountry(countryCode?: string): CountryData['iso2'] | 'auto'

  /**
   * set the input value and update the flag
   */
  setNumber(number: string, preventFocus?: boolean): void

  setFlagDropdownRef(ref: HTMLDivElement | null): void

  setTelRef(ref: HTMLInputElement | null): void

  /**
   * select the given flag, update the placeholder and the active list item
   *
   * Note: called from setInitialState, updateFlagFromNumber, selectListItem, setCountry, updateFlagOnDefaultCountryChange
   */
  setFlag(countryCode?: string, isInit?: boolean): void

  /**
   * get the extension from the current number
   */
  getExtension(number?: string): string

  /**
   * format the number to the given format
   */
  getNumber(number?: string, format?: string): string

  /**
   * get the input val, adding the dial code if separateDialCode is enabled
   */
  getFullNumber(number?: string): string

  /**
   * try and extract a valid international dial code from a full telephone number
   */
  getDialCode(number: string): string

  /**
   * check if the given number contains an unknown area code from
   */
  isUnknownNanp(number?: string, dialCode?: string): boolean

  /**
   * add a country code to countryCodes
   */
  addCountryCode(
    countryCodes: {
      [key: string]: string[]
    },
    iso2: string,
    dialCode: string,
    priority?: number,
  ): {
    [key: string]: string[]
  }

  processAllCountries(): void

  /**
   * process the countryCodes map
   */
  processCountryCodes(): void

  /**
   * process preferred countries - iterate through the preferences,
   * fetching the country data for each one
   */
  processPreferredCountries(): void

  /**
   * set the initial state of the input value and the selected flag
   */
  setInitialState(): void

  initRequests(): void

  loadCountryFromLocalStorage(): string

  loadAutoCountry(): void

  cap(number?: string): string | undefined

  removeEmptyDialCode(): void

  /**
   * highlight the next/prev item in the list (and ensure it is visible)
   */
  handleUpDownKey(key?: number): void

  /**
   * select the currently highlighted item
   */
  handleEnterKey(): void

  /**
   * find the first list item whose name starts with the query string
   */
  searchForCountry(query: string): void

  formatNumber(number?: string): string

  /**
   * update the input's value to the given val (format first if possible)
   */
  updateValFromNumber(
    number?: string,
    doFormat?: boolean,
    doNotify?: boolean,
  ): void

  /**
   * check if need to select a new flag based on the given number
   */
  updateFlagFromNumber(number?: string, isInit?: boolean): void

  /**
   * filter the given countries using the process function
   */
  filterCountries(
    countryArray: string[],
    processFunc: (iso2: string) => void,
  ): void

  /**
   * prepare all of the country data, including onlyCountries and preferredCountries options
   */
  processCountryData(): void

  handleOnBlur(event: React.FocusEvent<HTMLInputElement>): void

  handleOnFocus(event: React.FocusEvent<HTMLInputElement>): void

  bindDocumentClick(): void

  unbindDocumentClick(): void

  clickSelectedFlag(event: React.MouseEvent<HTMLDivElement, MouseEvent>): void

  /**
   * update the input placeholder to an
   * example number from the currently selected country
   */
  updatePlaceholder(props?: IntlTelInputProps): void

  toggleDropdown(status?: boolean): void

  /**
   * check if an element is visible within it's container, else scroll until it is
   */
  scrollTo(element: Element, middle?: boolean): void

  /**
   * replace any existing dial code with the new one
   *
   * Note: called from _setFlag
   */
  updateDialCode(newDialCode?: string, hasSelectedListItem?: boolean): string

  generateMarkup(): void

  handleSelectedFlagKeydown(event: React.KeyboardEvent<HTMLDivElement>): void

  /**
   * validate the input val - assumes the global function isValidNumber (from libphonenumber)
   */
  isValidNumber(number?: string): boolean

  formatFullNumber(number?: string): string

  notifyPhoneNumberChange(number?: string): void

  /**
   * remove the dial code if separateDialCode is enabled
   */
  beforeSetNumber(
    number?: string,
    props?: IntlTelInputProps,
  ): string | undefined

  handleWindowScroll(): void

  handleDocumentKeyDown(event: KeyboardEvent): void

  handleDocumentClick(event: MouseEvent): void

  /**
   * Either notify phoneNumber changed if component is controlled
   */
  handleInputChange(event: React.FocusEvent<HTMLInputElement>): void

  changeHighlightCountry(showDropdown: boolean, selectedIndex: number): void

  loadUtils(): void

  /**
   * this is called when the geoip call returns
   */
  autoCountryLoaded(): void
  //#endregion
}
