import React, { useState, useRef, useEffect, useReducer, ReactNode, ChangeEvent } from 'react';
// import PropTypes from 'prop-types';
import { InputGroup, FormControl, Overlay } from 'react-bootstrap';

import { COUNTRIES } from './data';

import {
  applyExclusionsAndAdditions,
  getInitialList,
  removeEmojiFlag,
  getUpdatedList,
  classNames,
} from './util';

import ICountry from './ICountry';

import OverlayContent from './OverlayContent';

import { DEFAULT_CLASS_PREFIX } from './constants';

import reducer, { INITIAL_STATE } from './reducer';

import {
  init,
  focus,
  blur,
  textChange,
  activeListItemChange,
  countrySelect,
  clear,
} from './actions';

import './style.scss';

export interface CountrySelectProps {
  value: string | ICountry;
  onChange: (countryIdOrCountry: string | ICountry) => void;
  onTextChange: (text: string, changeEvent: ChangeEvent) => void;
  countries?: ICountry[];
  exclusions?: string[];
  additions?: ICountry[];
  valueAs?: 'id' | 'object';
  flags?: boolean;
  flush?: boolean;
  disabled?: boolean;
  placeholder?: ReactNode;
  noMatchesText?: ReactNode;
  size?: 'sm' | 'lg';
  sort?: (c1: ICountry, c2: ICountry) => number; 
  matchNameFromStart?: boolean;
  matchAbbreviations?: boolean;
  countryLabelFormatter?: (country: ICountry) => ReactNode;
  throwInvalidValueError?: boolean;
  listMaxHeight?: number;
  closeOnSelect?: true;
  formControlProps?: any; // TODO
  overlayProps?: any; // TODO
  classPrefix?: string;
  className?: string;
}

const CountrySelect = ({
  value,
  onChange = () => {},
  onTextChange,
  countries = [ ...COUNTRIES ],
  exclusions,
  additions,
  valueAs = 'object',
  flags = true,
  flush = true,
  disabled = false,
  placeholder = 'Type or select country...',
  noMatchesText = 'No matches',
  size,
  sort, // e.g. (c1, c2) => c1.name < c2.name ? -1 : (c1.name > c2.name ? 1 : 0),
  matchNameFromStart = true,
  matchAbbreviations = false,
  countryLabelFormatter = ({ name }) => name,
  throwInvalidValueError = false, 
  listMaxHeight,
  closeOnSelect = true,
  formControlProps = {},
  overlayProps = {},
  classPrefix = DEFAULT_CLASS_PREFIX,
  className,
}: CountrySelectProps) => {

  const inputGroupRef = useRef(null);
  const formControlRef = useRef(null);
  const hasInitRef = useRef(false);
  const [ width, setWidth ] = useState(-1);

  const [ {
    focused,
    inputText,
    list,
    activeListItemIndex,
    combinedCountries,
  }, dispatch ] = useReducer(reducer, INITIAL_STATE);

  const handleFocus = focus(dispatch);
  const handleBlur = blur(dispatch);
  const handleTextChange = textChange(dispatch);
  const handleListActiveItemChange = activeListItemChange(dispatch);
  const handleCountrySelect = countrySelect(dispatch);
  const handleClear = clear(dispatch);

  const getCountryId = (value: ICountry | string): string => (typeof value === 'string' ? value : value.id);

  const selectedCountry = value ? (combinedCountries || []).find(country => country.id === getCountryId(value)) : null;

  if (throwInvalidValueError && value && !selectedCountry)
    throw new Error(`No matching country for value: ${JSON.stringify(value)}`);

  useEffect(() => {

    if (hasInitRef.current) return;

    const combinedCountries = applyExclusionsAndAdditions(countries, exclusions, additions);

    const sorted = getInitialList(combinedCountries, sort);

    init(dispatch)(sorted);

    hasInitRef.current = true;

  }, [ countries, exclusions, additions, sort ]);

  useEffect(() => {

    setWidth(inputGroupRef.current.offsetWidth);

  }, [ inputGroupRef ]);

  const select = listItemIndex => {

    const country = list[listItemIndex];

    handleCountrySelect();
    onChange(valueAs === 'id' ? country.id : country);

  };

  const escape = () => {

    handleClear();
    onChange(null);

  };

  const inputChange = (text, ev) => {

    if (selectedCountry && flags) {

      text = removeEmojiFlag(text);

    }

    const [ updatedList, updatedActiveListItemIndex ]
      = getUpdatedList(text, list, activeListItemIndex, combinedCountries, sort, matchNameFromStart, matchAbbreviations);

    handleTextChange(text, updatedList, updatedActiveListItemIndex);

    if (onTextChange) onTextChange(text, ev);
    if (value) onChange(null);

  };

  const handleKey = ev => {

    if (ev.key === 'ArrowUp') {

      ev.preventDefault();

      const newIndex = activeListItemIndex <= 0 ? list.length - 1 : activeListItemIndex - 1;
      handleListActiveItemChange(newIndex);

    } else if (ev.key === 'ArrowDown') {

      const newIndex = activeListItemIndex >= list.length - 1 ? 0 : activeListItemIndex + 1;
      handleListActiveItemChange(newIndex);

    } else if (ev.key === 'Enter') {

      if (activeListItemIndex >= 0) select(activeListItemIndex)

    } else if (ev.key === 'Escape') {

      escape();

    }

  };
  
  const classes = classNames([
    className,
    classPrefix,
    flush && `${classPrefix}--flush`,
  ]);

  return (
    <div className={classes}>

      <InputGroup
        ref={inputGroupRef}
        className={`${classPrefix}__input-group`}
        size={size}
      >

        { (!flush && flags) && 
          <InputGroup.Prepend>

            <InputGroup.Text
              className={`${classPrefix}__input-group__flag`}
            >

              {selectedCountry ? selectedCountry.flag : ''}
            
            </InputGroup.Text>
            
          </InputGroup.Prepend>
        }

        <FormControl
          ref={formControlRef}
          className={`${classPrefix}__form-control`}
          value={selectedCountry ? `${flush && flags ? selectedCountry.flag + '   ' : ''}${selectedCountry.name}` : inputText}
          onKeyDown={handleKey}
          onChange={ev => inputChange(ev.target.value, ev)}
          onFocus={handleFocus}
          onBlur={handleBlur}
          placeholder={placeholder}
          disabled={disabled}
          spellCheck={false}
          autoComplete='new-value'
          {...formControlProps}
        />

      </InputGroup>

      <Overlay
        target={inputGroupRef.current}
        rootClose
        placement='bottom-start'
        show={focused && (!selectedCountry || !closeOnSelect)} // experimental; not documented
        onHide={() => {}}
        transition
        {...overlayProps}
      >

        {({ placement, arrowProps, show: _show, popper, ...props }) => (

          <div
            {...props}
            style={{
              width: (width > 0) ? `${width}px` : 'calc(100% - 10px)',
              ...props.style,
            }}
          >

            <OverlayContent
              classPrefix={classPrefix}
              list={list}
              activeListItemIndex={activeListItemIndex}
              countryLabelFormatter={countryLabelFormatter}
              flags={flags}
              noMatchesText={noMatchesText}
              maxHeight={listMaxHeight}
              onListItemClick={select}
            />

          </div>

        )}

      </Overlay>
    
    </div>
  );

};

export default CountrySelect;