import { useContext, useRef, useMemo, useCallback } from 'react';
import { action, computed } from 'mobx';
import { observer } from 'mobx-react';
import { EventListener } from '../types';
import ElementContext from '../utils/ElementContext';

export const SELECTION_EVENT = 'selection';
export const SELECTION_STATE = 'selectedIds';

export type SelectionEventListener = EventListener<[string[]]>;

interface SelectionHandlerState {
  [SELECTION_STATE]?: string[];
}

export type OnSelect = (e: React.MouseEvent) => void;

interface Options {
  multiSelect?: boolean;
  controlled?: boolean;
  raiseOnSelect?: boolean;
}

export const useSelection = ({ multiSelect, controlled, raiseOnSelect = true }: Options = {}): [boolean, OnSelect] => {
  const element = useContext(ElementContext);
  const elementRef = useRef(element);
  elementRef.current = element;

  const selected = useMemo(
    () =>
      computed(() => {
        const { selectedIds } = element.getController().getState<SelectionHandlerState>();
        return !!selectedIds && selectedIds.includes(element.getId());
      }),
    [element]
  );

  const onSelect = useCallback(
    (e: React.MouseEvent): void => {
      const actionFn = action((e: React.MouseEvent): void => {
        e.stopPropagation();
        const id = elementRef.current.getId();
        const state = elementRef.current.getController().getState<SelectionHandlerState>();
        const idx = state.selectedIds ? state.selectedIds.indexOf(id) : -1;
        let selectedIds: string[];
        let raise = false;
        if (multiSelect && (e.ctrlKey || e.metaKey)) {
          if (!state.selectedIds) {
            raise = true;
            selectedIds = [id];
          } else {
            selectedIds = [...state.selectedIds];
            if (idx === -1) {
              raise = true;
              selectedIds.push(id);
            } else {
              selectedIds.splice(idx, 1);
            }
          }
        } else if (idx === -1 || multiSelect) {
          raise = true;
          selectedIds = [id];
        } else {
          selectedIds = [];
        }
        if (!controlled) {
          state.selectedIds = selectedIds;
        }
        elementRef.current.getController().fireEvent(SELECTION_EVENT, selectedIds);
        if (raiseOnSelect && raise) {
          elementRef.current.raise();
        }
      });
      actionFn(e);
    },
    [multiSelect, controlled, raiseOnSelect]
  );
  return [selected.get(), onSelect];
};

export interface WithSelectionProps {
  selected?: boolean;
  onSelect?: OnSelect;
}

export const withSelection =
  (options?: Options) =>
  <P extends WithSelectionProps>(WrappedComponent: React.ComponentType<P>) => {
    const Component: React.FunctionComponent<Omit<P, keyof WithSelectionProps>> = (props) => {
      const [selected, onSelect] = useSelection(options);
      return <WrappedComponent {...(props as any)} selected={selected} onSelect={onSelect} />;
    };
    Component.displayName = `withSelection(${WrappedComponent.displayName || WrappedComponent.name})`;
    return observer(Component);
  };
