import { useState, useEffect, useMemo } from 'react';
import { useIntl } from 'react-intl';

import { Size, SizeLarge, SizeMedium, SizeSmall } from '../common';
import { useInputAttributes } from '../inputs/contexts';
import { SelectInput, SelectInputOptionContent, SelectInputProps } from '../inputs/SelectInput';

import messages from './PhoneNumberInput.messages';
import countries from './data/countries';
import {
  explodeNumberModel,
  isValidPhoneNumber,
  cleanNumber,
  setDefaultPrefix,
  sortArrayByProperty,
  groupCountriesByPrefix,
  excludeCountries,
  findCountryByPrefix,
} from './utils';
import { PhoneNumber } from './utils/explodeNumberModel';

const ALLOWED_PHONE_CHARS = /^$|^[\d-\s]+$/;

export interface PhoneNumberInputProps {
  id?: string;
  'aria-labelledby'?: string;
  required?: boolean;
  disabled?: boolean;
  initialValue?: string;
  onChange: (value: string | null, prefix: string) => void;
  onFocus?: React.FocusEventHandler<HTMLInputElement>;
  onBlur?: () => void;
  countryCode?: string;
  searchPlaceholder?: string;
  size?: SizeSmall | SizeMedium | SizeLarge;
  placeholder?: string;
  selectProps?: Partial<SelectInputProps<string | null>>;
  /** List of iso3 codes of countries to remove from the list */
  disabledCountries?: readonly string[];
}

const defaultSelectProps = {} satisfies PhoneNumberInputProps['selectProps'];
const defaultDisabledCountries = [] satisfies PhoneNumberInputProps['disabledCountries'];

const PhoneNumberInput = ({
  id,
  'aria-labelledby': ariaLabelledByProp,
  required,
  disabled,
  initialValue,
  onChange,
  onFocus,
  onBlur,
  countryCode,
  searchPlaceholder = 'Prefix',
  size = Size.MEDIUM,
  placeholder,
  selectProps = defaultSelectProps,
  disabledCountries = defaultDisabledCountries,
}: PhoneNumberInputProps) => {
  const inputAttributes = useInputAttributes({ nonLabelable: true });
  const ariaLabelledBy = ariaLabelledByProp ?? inputAttributes['aria-labelledby'];

  const { locale, formatMessage } = useIntl();

  const [internalValue, setInternalValue] = useState<PhoneNumber>(() => {
    const cleanValue = initialValue ? cleanNumber(initialValue) : null;

    if (!cleanValue || !isValidPhoneNumber(cleanValue)) {
      return {
        prefix: setDefaultPrefix(locale, countryCode),
        suffix: '',
      };
    }

    return explodeNumberModel(cleanValue);
  });
  const [broadcastedValue, setBroadcastedValue] = useState<PhoneNumber | null>(null);

  const [suffixDirty, setSuffixDirty] = useState(false);
  useEffect(() => {
    if (internalValue.suffix) {
      setSuffixDirty(true);
    }
  }, [internalValue.suffix]);

  const countriesByPrefix = useMemo(
    () =>
      groupCountriesByPrefix(
        sortArrayByProperty(excludeCountries(countries, disabledCountries), 'iso3'),
      ),
    [disabledCountries],
  );

  const onSuffixChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
    const suffix = event.target.value;
    if (ALLOWED_PHONE_CHARS.test(suffix)) {
      setInternalValue((prev) => ({ ...prev, suffix }));
    }
  };

  const onPaste: React.ClipboardEventHandler<HTMLInputElement> = (event) => {
    if (!event.nativeEvent.clipboardData) {
      return;
    }

    const pastedValue = (event.nativeEvent.clipboardData.getData('text/plain') || '').replace(
      /(\s|-)+/g,
      '',
    );
    const pastedNumber = explodeNumberModel(pastedValue);

    if (
      pastedNumber.prefix != null &&
      countriesByPrefix.has(pastedNumber.prefix) &&
      ALLOWED_PHONE_CHARS.test(pastedNumber.suffix)
    ) {
      setInternalValue(pastedNumber);
    }
  };

  useEffect(() => {
    if (broadcastedValue === null) {
      setBroadcastedValue(internalValue);
      return;
    }

    const internalPhoneNumber = `${internalValue.prefix ?? ''}${internalValue.suffix}`;
    const broadcastedPhoneNumber = `${broadcastedValue.prefix ?? ''}${broadcastedValue.suffix}`;

    if (internalPhoneNumber === broadcastedPhoneNumber) {
      return;
    }

    const newValue = isValidPhoneNumber(internalPhoneNumber)
      ? cleanNumber(internalPhoneNumber)
      : null;

    onChange(
      newValue,
      internalValue.prefix ?? '', // TODO: Allow `null` in public API
    );
    setBroadcastedValue(internalValue);
  }, [onChange, broadcastedValue, internalValue]);

  return (
    <div
      role="group"
      {...inputAttributes}
      aria-labelledby={ariaLabelledBy}
      className="tw-telephone"
    >
      <div className="tw-telephone__country-select">
        <SelectInput
          placeholder={formatMessage(messages.selectInputPlaceholder)}
          items={[...countriesByPrefix].map(([prefix, countries]) => ({
            type: 'option',
            value: prefix,
            filterMatchers: [
              prefix,
              ...countries.map((country) => country.name),
              ...countries.map((country) => country.iso3),
            ],
          }))}
          value={internalValue.prefix}
          renderValue={(prefix, withinTrigger) => (
            <SelectInputOptionContent
              title={prefix}
              note={
                withinTrigger
                  ? undefined
                  : countriesByPrefix
                      .get(prefix)
                      ?.map((country) => country.iso3)
                      .join(', ')
              }
            />
          )}
          filterable
          filterPlaceholder={searchPlaceholder}
          disabled={disabled}
          size={size}
          onChange={(prefix) => {
            const country = prefix != null ? findCountryByPrefix(prefix) : null;
            setInternalValue((prev) => ({ ...prev, prefix, format: country?.phoneFormat }));
          }}
          onClose={() => {
            if (suffixDirty) {
              onBlur?.();
            }
          }}
          {...selectProps}
        />
      </div>
      <div className="tw-telephone__number-input">
        <div className={`input-group input-group-${size}`}>
          <input
            id={id}
            autoComplete="tel-national"
            name="phoneNumber"
            inputMode="numeric"
            value={internalValue.suffix}
            className="form-control"
            disabled={disabled}
            required={required}
            placeholder={placeholder}
            onChange={onSuffixChange}
            onPaste={onPaste}
            onFocus={onFocus}
            onBlur={() => onBlur?.()}
          />
        </div>
      </div>
    </div>
  );
};

export default PhoneNumberInput;
