import React, { useEffect, useImperativeHandle, useRef, useState } from 'react';
import { useEventListener } from '@devesharp/react-hooks';
import { NormalizeString } from '@devesharp/react-utils';
import * as _ from 'lodash';
import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Input } from '../Input';
import { FormStyle } from '../styles';
import { IPropsAutoComplete } from './Autocomplete.interface';
import Style from './Autocomplete.style';
import { AutocompleteCore } from './AutocompleteCore';

function convertArrayFetchToOptions(results): any {
   let i = -1;
   return results.map((el) => {
      if (el.disabled) {
         return {
            id: el.id ?? JSON.stringify(el),
            disabled: true,
            value: el.value ?? el.name,
            // trata objeto com getAsyncLabel ou resgata el (que deve ser uma string)
            name: el.name,
         };
      }

      i += 1;
      return { id: i, value: el.value, name: el.name };
   });
}

export const AutocompletePure = React.forwardRef<any, IPropsAutoComplete>(function AutocompletePure(
   {
      renderInput = (item: any) => <div>{item}</div>,
      name,
      onSelect,
      fetch,
      setError,
      cleanAfterSelect = false,
      NotFoundComponent: NotFoundComponentCustom,
      //
      title,
      prefix,
      suffix,
      textRight,
      textCenter,
      loading,
      mask,
      withoutDotNumber,
      decimals,
      error,
      value,
      onInput,
      onEnter,
      defaultValue,
      options,
      ...rest
   },
   ref,
) {
   const inputProps = {
      title,
      prefix,
      suffix,
      textRight,
      textCenter,
      loading,
      mask,
      withoutDotNumber,
      decimals,
      error,
      value,
      onInput,
      onEnter,
      defaultValue,
   };

   const textInput = useRef(null);
   const [result, setResults] = useState([]);
   const [open, setOpen] = useState(false);
   const [searching, setSearching] = useState(false);
   const [notFound, setNotFound] = useState(false);
   const [position, setPosition] = useState(0);
   const [lengthItems, setLengthItems] = useState(0);

   useImperativeHandle(ref, () => textInput.current, []);

   const debounce = useRef<any>();
   const $destroy = useRef(new Subject());
   const closeAutocomplete = useRef<any>();

   // component not found
   const NotFoundComponent = (NotFoundComponentCustom ?? NotFoundAutoCompleteComponent) as any;

   const onChangeDebounced = async (text: string): Promise<void> => {
      if (options) {
         setPosition(0);
         setLengthItems(options.length);
         setResults(
            options
               .filter((i) => NormalizeString(i.name).includes(NormalizeString(text)))
               .map((el, i) => ({ ...el, id: i })),
         );
         setOpen(true);
         setSearching(false);

         return;
      }

      fetch(text)
         .pipe(takeUntil($destroy.current))
         .subscribe(
            (results) => {
               setPosition(0);
               const items = convertArrayFetchToOptions(results);
               setLengthItems(items.filter((i) => !i.disabled).length);
               setResults(items);
               setOpen(true);
               setNotFound(items.filter((i) => !i.disabled).length === 0);

               setSearching(false);
            },
            (e) => {
               setSearching(false);
               if (setError) setError(e);
            },
         );
   };

   const handleInputChange = (text: string): void => {
      if (debounce.current) debounce.current.cancel();
      setSearching(true);
      debounce.current = _.debounce(onChangeDebounced, 300);
      debounce.current(text);
      if (onInput) onInput(text);
   };

   function closeAutoComplete(): void {
      closeAutocomplete.current = setInterval(() => setOpen(false), 200);
      if (rest.onBlur) rest.onBlur();
   }

   function openAutoComplete(): void {
      clearInterval(closeAutocomplete.current);
      setOpen(true);
      if (rest.onFocus) rest.onFocus();
   }

   function selectItem(e): void {
      if (!e.disabled) {
         if (onSelect) onSelect(e.name, e);

         textInput.current.value = e.name;
         if (onInput) onInput(e.name);

         setResults([]);
         setOpen(false);
         if (cleanAfterSelect) {
            textInput.current.value = '';
            if (onInput) onInput('');
         }
      }
   }

   // ClearInternal
   useEffect(
      () => () => {
         clearInterval(closeAutocomplete.current);
      },
      [],
   );

   useEventListener('keydown.arrowdown', (event) => {
      if (open) {
         let nextPos = position + 1;

         if (nextPos >= lengthItems) {
            nextPos = 0;
         }

         setPosition(nextPos);
      }
   });

   useEventListener('keydown.arrowup', (event) => {
      if (open) {
         let nextPos = position - 1;

         if (nextPos < 0) {
            nextPos = lengthItems - 1;

            if (lengthItems - 1 < 0) {
               nextPos = 0;
            }
         }

         setPosition(nextPos);
      }
   });

   useEventListener('keydown.enter', (event) => {
      const item = result.find((e) => e.id === position);
      if (item) selectItem(item);
   });

   return (
      <FormStyle data-cy={`autocomplete-${name}`} {...rest}>
         <Style>
            <Input
               {...inputProps}
               ref={textInput}
               onInput={handleInputChange}
               onFocus={openAutoComplete}
               onBlur={closeAutoComplete}
               loading={searching}
               data-testid="autocomplete--input"
            />

            {open && (
               <ul className="autocomplete-list" data-testid="autocomplete-list">
                  {result.map((e, i) => (
                     <li
                        key={e.id}
                        onClick={() => selectItem(e)}
                        onMouseOver={() => !e.disabled && setPosition(e.id)}
                        data-testid={`autocomplete-list--row-${i}`}
                        className={`${position === e.id && 'active'} ${!!e.disabled && 'disabled'}`}
                     >
                        {renderInput(e.name, e.value)}
                     </li>
                  ))}
                  {notFound && <NotFoundComponent />}
               </ul>
            )}
         </Style>
      </FormStyle>
   );
});

const NotFoundAutoCompleteComponent = (): any => {
   return <>Não encontrado</>;
};

export const Autocomplete = React.forwardRef<any, IPropsAutoComplete>(function Autocomplete(props, ref) {
   return <AutocompleteCore {...props} as={AutocompletePure} ref={ref} />;
});
