// @flow
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { AUTONUMERIC_CONFIG_BY_CURRENCY_CODE } from './currency-input.constants';
import { TextInput } from '../text-input';
import type { CurrencyInputProps } from './currency-input.proptypes';

let AutoNumeric;
let loadLibPromise;
const lazyLoadAutonumeric = () => {
  if (!loadLibPromise) {
    loadLibPromise = import('autonumeric').then((lib) => {
      // `lib.default` seems to be undefined when linking
      // micro-apps together in development. Instead, the `lib`
      // itself contains the module.
      AutoNumeric = lib.default || lib;
      return AutoNumeric;
    });
  }

  return loadLibPromise;
};

const getAutonumericConfig = (currency) =>
  AUTONUMERIC_CONFIG_BY_CURRENCY_CODE[currency];

const format = (value, currency, decimalPlaces) =>
  AutoNumeric
    ? AutoNumeric.format(value, {
        ...getAutonumericConfig(currency),
        decimalPlaces,
      })
    : String(value);

export const CurrencyInput = ({
  onRef,
  onChange,
  placeholder,
  value,
  currency,
  spacing,
  grow,
  shrink,
  basis,
  className,
  'data-qa': dataQa,
  'data-cy': dataCy,
  id,
  decimalPlaces = 2,
  ...rest
}: CurrencyInputProps) => {
  const inputRef = useRef<?HTMLInputElement>();
  const previousValue = useRef(value); // we keep track of the previous value as we need it in our update logic (useEffect doesn't keep track of this)
  const [amount, updateAmount] = useState<?string>(
    value != null ? String(value) : undefined,
  );
  const [
    autoNumericInstance,
    setAutoNumericInstance,
  ] = useState<?typeof AutoNumeric>();

  useEffect(() => {
    if (
      autoNumericInstance &&
      Number(autoNumericInstance.settings.decimalPlaces) !== decimalPlaces
    ) {
      autoNumericInstance.options.decimalPlaces(decimalPlaces);
    }
  }, [decimalPlaces, autoNumericInstance]);

  useEffect(() => {
    if (value == null) {
      if (inputRef.current) inputRef.current.value = '';
      if (autoNumericInstance) autoNumericInstance.set('');
      updateAmount(value);
      return;
    }
    if (autoNumericInstance) {
      // Sometimes when component is re-rendered it will lose its formatting.
      // This check is to ensure we re-apply autonumeric styling if we've lost
      // our formatting.
      const { rawValue } = autoNumericInstance;
      const formattedValue = autoNumericInstance.getFormatted();
      const isFormatted = rawValue !== formattedValue;

      if (isFormatted && previousValue.current === value) return;

      const valueHasChanged = Number(autoNumericInstance.rawValue) !== value;
      if (isFormatted && !valueHasChanged) return;

      autoNumericInstance.set(value);
      updateAmount(format(value, currency, decimalPlaces));
    }
  }, [
    value,
    updateAmount,
    currency,
    previousValue,
    autoNumericInstance,
    decimalPlaces,
  ]);

  const handleRef = useCallback(
    (ref: HTMLInputElement) => {
      if (!inputRef.current) {
        inputRef.current = ref;
      } else {
        return;
      }

      const { current: inputElement } = inputRef;

      if (inputElement && inputElement instanceof HTMLInputElement) {
        lazyLoadAutonumeric().then(() => {
          setAutoNumericInstance(
            new AutoNumeric(ref, {
              ...getAutonumericConfig(currency),
              decimalPlaces,
            }),
          );

          if (placeholder !== null && placeholder !== undefined) {
            inputElement.placeholder = format(
              placeholder,
              currency,
              decimalPlaces,
            );
          }

          if (value !== null && value !== undefined) {
            inputElement.value = format(value, currency, decimalPlaces);
          }
        });
      }
    },
    [
      setAutoNumericInstance,
      currency,
      inputRef,
      placeholder,
      value,
      decimalPlaces,
    ],
  );

  const handleChange = useCallback(
    (newValue: string, event: SyntheticInputEvent<HTMLInputElement>) => {
      const { current: inputElement } = inputRef;
      if (!autoNumericInstance || !inputElement) {
        event.preventDefault();
        return;
      }

      previousValue.current = value; // set the previous value here before we update anything
      onChange(Number(autoNumericInstance.rawValue), event);
      updateAmount(event.target.value);

      if (inputElement) {
        event.target.setSelectionRange(
          inputElement.selectionStart,
          inputElement.selectionEnd,
        );
      }
    },
    [autoNumericInstance, inputRef, onChange, updateAmount, value],
  );

  return (
    <TextInput
      {...{ spacing, grow, shrink, basis }}
      data-cy={dataCy}
      data-qa={dataQa}
      id={id}
      // the spread prop is first so value and onChange are redefined
      {...rest}
      // Coerce to string for underlying implementation.
      value={amount != null ? String(amount) : undefined}
      placeholder={placeholder != null ? `${placeholder}` : undefined}
      className={className}
      onRef={handleRef}
      onChange={handleChange}
      inputMode="decimal"
    />
  );
};

CurrencyInput.displayName = 'CurrencyInput';
