import * as React from 'react';
import {useEffect, useRef, useState} from 'react';
import {ClipItemDoc, Events, Messages} from '../types';
import * as PluginTypes from '@type/pluginTypes';
import useEventListener from '@use-it/event-listener';
import {clamp, inRange} from 'lodash';
import {SearchBar} from './SearchBar';
import SimpleBar from 'simplebar-react';
import 'simplebar/dist/simplebar.min.css';
import {dimensions, isAlphanumeric} from './utils';
import {ClipItem, ClipItemVariants} from './ClipItemRow';
import styled from 'styled-components';
import {GlobalEvents} from '@type/globalEvents';
import {ipcRenderer} from 'electron';

const StyledContainer = styled.div`
  display: flex;
  flex-direction: column;
  height: 100%;
  width: 100%;
`;

const SimpleBarStyles: React.CSSProperties = {
  display: 'flex',
  height: '100%',
  width: '100%',
  overflowY: 'auto',
  scrollBehavior: 'unset',
  flexDirection: 'column',
};

export const ClipboardRenderer = (props: PluginTypes.RenderProps) => {
  const [clipItems, updateClipItems] = useState<ClipItemDoc[]>([]);
  const [selectedIndex, updateSelectedIndex] = useState(0);
  const [imagesDir, updateImagesDir] = useState<string>('');
  const [searchText, updateSearchText] = useState('');
  const {pluginProcess} = props;
  const searchBarRef = useRef<HTMLInputElement>(null);
  const clipsListRef = useRef<HTMLDivElement>(null);

  const resetClips = () => {
    pluginProcess.send(Messages.GetAllClipItems, undefined, (err, clips) => {
      if (!err) {
        updateClipItems([...clips]);
      }
    });
  };

  const hideWindow = () => {
    setImmediate(() => {
      ipcRenderer.send(GlobalEvents.HideWindow);
    });
  };

  const onKeyPress = (event: KeyboardEvent) => {
    const {keyCode} = event;

    /* disable scrolling by arrow keys */
    if ([38, 40].includes(keyCode)) {
      event.preventDefault();
    }

    if (clipsListRef.current) {
      const {clipItemDimensions, searchBarDimensions} = dimensions;

      const clipRowHeight =
        clipItemDimensions.heightPx +
        clipItemDimensions.paddingTopPx +
        clipItemDimensions.paddingBottomPx;

      const searchBarHeight =
        searchBarDimensions.heightPx +
        searchBarDimensions.paddingTopPx +
        searchBarDimensions.paddingBottomPx;

      const viewHeight = clipsListRef.current.offsetHeight - searchBarHeight;
      const itemsVisibleN = Math.floor(viewHeight / clipRowHeight);

      const itemsScrolled = Math.floor(
        clipsListRef.current.scrollTop / clipRowHeight,
      );

      const isItemInViewPort = inRange(
        selectedIndex,
        itemsScrolled,
        itemsVisibleN + itemsScrolled + 1,
      );

      /* up key */
      if (keyCode === 38) {
        if (isItemInViewPort) {
          clipsListRef.current.scrollBy({
            top: -clipRowHeight,
          });
        } else {
          clipsListRef.current.scrollTop = (selectedIndex - 2) * clipRowHeight;
        }

        updateSelectedIndex((prevSelectedIndex) =>
          clamp(prevSelectedIndex - 1, 0, clipItems.length - 1),
        );
      }

      /* down key */
      if (keyCode === 40) {
        if (selectedIndex >= itemsVisibleN - 1 && isItemInViewPort) {
          clipsListRef.current.scrollBy({top: clipRowHeight});
        } else if (clipsListRef.current.scrollTop) {
          clipsListRef.current.scrollTop = selectedIndex * clipRowHeight;
        }

        updateSelectedIndex((prevSelectedIndex) =>
          clamp(prevSelectedIndex + 1, 0, clipItems.length - 1),
        );
      }
    }

    /* escape */
    if (keyCode === 27) {
      if (searchText) {
        resetClips();
      } else {
        hideWindow();
      }

      handleSearchUpdate('');
    }

    /* enter key */
    if (keyCode === 13) {
      handleClipItemSelected(clipItems[selectedIndex]);
    }

    /* key is alphanumeric */
    if (isAlphanumeric(keyCode)) {
      updateSelectedIndex(0);
      searchBarRef.current && searchBarRef.current.focus();
    }
  };

  useEventListener('keydown', onKeyPress);

  useEffect(() => {
    resetClips();

    pluginProcess.on(Events.NewClip, (clip: ClipItemDoc) => {
      updateClipItems((prevClipItems) => [clip, ...prevClipItems]);
    });

    pluginProcess.on(Events.ClipsInitialized, (clips: ClipItemDoc[]) => {
      updateClipItems([...clips]);
    });

    ipcRenderer.on(GlobalEvents.ShowWindow, () => {
      if (process.platform === 'linux') {
        setImmediate(() => {
          searchBarRef.current && searchBarRef.current.blur();
        });
      }
    });

    ipcRenderer.on(GlobalEvents.HideWindow, () => {
      handleSearchUpdate('');
    });

    pluginProcess.send(
      Messages.GetImagesDir,
      undefined,
      (_err: any, _imagesDir: string) => {
        updateImagesDir(_imagesDir);
      },
    );
  }, []);

  const handleClipItemSelected = (item: ClipItemDoc) => {
    pluginProcess.send(Messages.ClipItemSelected, item, (err, res) => {
      if (err) {
        throw err;
      }

      if (res) {
        updateClipItems([...res]);
      }
    });

    handleSearchUpdate('');
    hideWindow();
  };

  const onClickClipItem = (item: ClipItemDoc) => {
    handleClipItemSelected(item);
  };

  const handleSearchUpdate = (text: string) => {
    updateSearchText(text);
    updateSelectedIndex(0);
    clipsListRef.current && (clipsListRef.current.scrollTop = 0);

    if (text === '') {
      searchBarRef.current && searchBarRef.current.blur();
      resetClips();
    } else {
      pluginProcess.send(Messages.SearchClips, text, (err, clips) => {
        if (err) {
          throw err;
        }

        updateClipItems([...clips]);
      });
    }
  };

  const onSearchTextChanged = (
    event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>,
  ) => {
    const query = event.target.value;
    handleSearchUpdate(query);
  };

  const getClipItemVariant = (index: number): ClipItemVariants => {
    if (index === selectedIndex) {
      return 'selected';
    }

    if (index % 2 === 0) {
      return 'dark';
    } else {
      return 'light';
    }
  };

  return (
    <StyledContainer>
      <SimpleBar
        style={SimpleBarStyles}
        scrollableNodeProps={{ref: clipsListRef}}
      >
        {clipItems.map((item, index) => (
          <ClipItem
            key={`${index}_clipItem`}
            clipItem={item}
            imagesDir={imagesDir}
            searchText={searchText}
            variant={getClipItemVariant(index)}
            onClick={() => onClickClipItem(item)}
          />
        ))}
      </SimpleBar>
      <SearchBar
        id="clipboard-searchbar"
        placeholder="search"
        onChange={onSearchTextChanged}
        value={searchText}
        ref={searchBarRef}
      />
    </StyledContainer>
  );
};