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

import { Size, SizeLarge, SizeMedium, SizeSmall } from '../common';
import { useFieldLabelRef, useInputAttributes } from '../inputs/contexts';
import { useInputPaddings } from '../inputs/InputGroup';
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';
import { Input } from '../inputs/Input';

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;
  /** @default 'Prefix' */
  searchPlaceholder?: string;
  /** @default 'md' */
  size?: SizeSmall | SizeMedium | SizeLarge;
  placeholder?: string;
  /** @default {} */
  selectProps?: Partial<SelectInputProps<string | null>>;
  /**
   * List of iso3 codes of countries to remove from the list
   * @default []
   */
  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 countryCodeSelectRef = useRef<HTMLButtonElement>(null);
  const phoneNumberInputRef = useRef<HTMLInputElement>(null);
  const inputAttributes = useInputAttributes({ nonLabelable: true });
  const { paddingInlineStart } = useInputPaddings();
  const hasInputGroupAddonStart = paddingInlineStart != null;
  const fieldLabelRef = useFieldLabelRef();
  const ariaLabelledBy = ariaLabelledByProp ?? inputAttributes['aria-labelledby'];

  const { locale, formatMessage } = useIntl();

  const [randomId] = useState(() => Math.random().toString(36).slice(2, 8));

  const createId = (customID: string | undefined, backup: string): string => {
    if (customID) {
      return customID + (backup ? `-${backup}` : '');
    }
    return `${backup}-${randomId}`;
  };

  // Link the first non-disabled input to the the Field label, if present
  const ids = {
    countryCode: {
      label: createId(id, 'country-code-label'),
      select: createId(id, 'country-code-select'),
    },
    phoneNumber: {
      label: createId(id, 'phone-number-label'),
      input: createId(id, id ? '' : 'phone-number-input'),
    },
  };

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

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

    return explodeNumberModel(cleanValue);
  });
  const broadcastedValueRef = useRef<PhoneNumber>(internalValue);

  const [suffixDirty, setSuffixDirty] = useState(false);

  useEffect(() => {
    if (!suffixDirty && internalValue.suffix) {
      // eslint-disable-next-line react-hooks/set-state-in-effect -- Tracking when suffix becomes dirty
      setSuffixDirty(true);
    }
  }, [internalValue.suffix, suffixDirty]);

  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(() => {
    const internalPhoneNumber = `${internalValue.prefix ?? ''}${internalValue.suffix}`;
    const broadcastedPhoneNumber = `${broadcastedValueRef.current.prefix ?? ''}${broadcastedValueRef.current.suffix}`;

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

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

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

  useEffect(() => {
    const labelRef = fieldLabelRef?.current;

    if (labelRef) {
      const handleLabelClick = () => {
        if (!selectProps.disabled) {
          countryCodeSelectRef.current?.click();
        } else {
          phoneNumberInputRef.current?.focus();
        }
      };

      labelRef.addEventListener('click', handleLabelClick);

      return () => {
        labelRef?.removeEventListener('click', handleLabelClick);
      };
    }
  }, [fieldLabelRef, selectProps.disabled]);

  return (
    <fieldset {...inputAttributes} aria-labelledby={ariaLabelledBy} className="tw-telephone">
      <label className="sr-only" id={ids.countryCode.label} htmlFor={ids.countryCode.select}>
        {formatMessage(messages.countryCodeLabel)}
      </label>
      <div
        className={`tw-telephone__country-select${
          hasInputGroupAddonStart
            ? ' tw-telephone__country-select--with-input-group-addon-start'
            : ''
        }`}
      >
        <SelectInput
          triggerRef={countryCodeSelectRef}
          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}
          id={ids.countryCode.select}
          UNSAFE_triggerButtonProps={{
            id: ids.countryCode.select,
            'aria-labelledby': ids.countryCode.label,
            'aria-describedby': undefined,
            'aria-invalid': undefined,
          }}
          onChange={(prefix) => {
            const country = prefix != null ? findCountryByPrefix(prefix) : null;
            setInternalValue((prev) => ({ ...prev, prefix, format: country?.phoneFormat }));
          }}
          onClose={() => {
            if (suffixDirty) {
              onBlur?.();
            }
          }}
          {...selectProps}
        />
      </div>
      <label className="sr-only" id={ids.phoneNumber.label} htmlFor={ids.phoneNumber.input}>
        {formatMessage(messages.phoneNumberLabel)}
      </label>
      <div className="tw-telephone__number-input">
        <div className={`input-group input-group-${size} ${disabled ? 'disabled' : ''}`}>
          <Input
            ref={phoneNumberInputRef}
            id={ids.phoneNumber.input}
            autoComplete="tel-national"
            name="phoneNumber"
            inputMode="numeric"
            value={internalValue.suffix}
            disabled={disabled}
            required={required}
            placeholder={placeholder}
            aria-labelledby={ids.phoneNumber.label}
            onChange={onSuffixChange}
            onPaste={onPaste}
            onFocus={onFocus}
            onBlur={() => onBlur?.()}
          />
        </div>
      </div>
    </fieldset>
  );
};

export default PhoneNumberInput;
