import React, { useEffect, useImperativeHandle, useRef, useState } from 'react';
import { useClickOutside, useEventListener } from '@devesharp/react-hooks';
import { NormalizeString } from '@devesharp/react-utils';
import * as _ from 'lodash';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { FormStyle } from '../styles';
import { IAutocompleteItem, IPropsAutoCompletePure, IRefAutoCompleteAs } from './AutocompletePure.interface';
import Style from './AutocompletePure.style';

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 function AutocompletePure<P>({
   renderRow = (item: any, value: any, active, disabled) => (
      <RenderRow item={item} active={active} disabled={disabled} />
   ),
   onSelect,
   fetchItems,
   items: itemsDefault,
   setError,
   cleanItemsAfterSelect = false,
   persistOpenAutoCompleteItemsAfterSelect = false,
   NotFoundComponent = NotFoundAutoCompleteComponent,
   as: As,
   ...rest
}: IPropsAutoCompletePure<P>): JSX.Element {
   const refAs = useRef<IRefAutoCompleteAs>(null);
   //
   const autocompleteRef = useRef<any>();
   // Scroll
   const scrollAutoComplete = useRef(null);
   // Resultados
   const [items, setItems] = useState<(IAutocompleteItem & { id: number })[]>([]);
   // Autocomplete aberto/fechado
   const [open, setOpen] = useState(false);
   // Buscando items
   const [searching, setSearching] = useState(false);
   // Quando não for encontrado nada
   const [notFound, setNotFound] = useState(false);
   // Posição da seleção, para saber onde está quando usuário apertar enter
   const [position, setPosition] = useState(0);
   // Tamanho da lista de items, ignorando disable = true
   const [lengthItems, setLengthItems] = useState(0);

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

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

         return;
      }

      $destroy.current.next();

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

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

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

   function closeAutoComplete(): void {
      $destroy.current.next();
      closeAutocomplete.current = setTimeout(() => setOpen(false), 200);
   }

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

   function selectItem(item: IAutocompleteItem & { id: number }): void {
      if (!item.disabled) {
         if (!persistOpenAutoCompleteItemsAfterSelect) setOpen(false);

         refAs.current?.onSelect(item);

         if (onSelect) onSelect(item.name, item);

         if (cleanItemsAfterSelect) setItems([]);
      }
   }

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

   useEventListener('keydown.esc', () => {
      if (open) {
         setOpen(false);
      }
   });

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

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

         // Movimentar scroll junto com o item selecionado

         if (Array.from(scrollAutoComplete.current.getElementsByTagName('li'))[0] as any) {
            const itemSize = (Array.from(
               scrollAutoComplete.current.getElementsByTagName('li'),
            )[0] as any).getBoundingClientRect().height;
            const { height: heightScroll } = scrollAutoComplete.current.getBoundingClientRect();
            const heightScrollTop = scrollAutoComplete.current.scrollTop;

            if (!(heightScrollTop < itemSize * nextPos && heightScrollTop + heightScroll > itemSize * nextPos)) {
               scrollAutoComplete.current.scrollTop = itemSize * (nextPos + 1) - heightScroll;
            }

            setPosition(nextPos);
         }
      }
   });

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

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

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

         // Movimentar scroll junto com o item selecionado
         if (Array.from(scrollAutoComplete.current.getElementsByTagName('li'))[0] as any) {
            const itemSize = (Array.from(
               scrollAutoComplete.current.getElementsByTagName('li'),
            )[0] as any).getBoundingClientRect().height;
            const { height: heightScroll } = scrollAutoComplete.current.getBoundingClientRect();
            const heightScrollTop = scrollAutoComplete.current.scrollTop;

            if (!(heightScrollTop < itemSize * nextPos && heightScrollTop + heightScroll > itemSize * nextPos)) {
               scrollAutoComplete.current.scrollTop = itemSize * nextPos;
            }

            setPosition(nextPos);
         }
      }
   });

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

   useClickOutside(() => {
      closeAutoComplete();
   }, autocompleteRef);

   return (
      <FormStyle ref={autocompleteRef} data-cy="autocomplete" className="autocomplete-container">
         <Style>
            <As
               {...(rest as any)}
               ref={refAs}
               _handleInputChange={handleInputChange}
               _openAutoComplete={openAutoComplete}
               _closeAutoComplete={closeAutoComplete}
               _loading={searching}
            />

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

function RenderRow({ item, disabled, active }): any {
   return <div className={`item-row-autocomplete ${disabled && 'disabled'} ${active && 'active'}`}>{item}</div>;
}

function NotFoundAutoCompleteComponent(): JSX.Element {
   return <>Não encontrado</>;
}
