// @flow
import React from 'react';
import cn from 'classnames';
import { Scrollbars } from 'react-custom-scrollbars';

import './scss/tooltip.scss';
/* eslint-env browser */


/** Props, передаваемые в Tooltip
  * @property {boolean} isVisible - виден или нет компонент
  * @property {string} offset - с какой стороны от родительского элемента открывается подсказка,
  * возможные значения: top, bottom, left, right
  * @property {string} event - событие, при наступлении которого открывается подсказка,
  * возможные значения: hover, click, dblclick
  * @property {boolean} fixed - при выходе подсказки за границы окна она
  * не меняет расположение
  * @property {number} maxHeight - максимальная высота, после которой появляется скролл
  * @property {string} arrowPosition - позация стрелки относительно блока с подсказкой,
  * возможные значения: left, center, right, top, bottom
  * @property {any} content - содержимое подсказки, переданное через свойство
  * @property {any} children - содержимое подсказки, обернутое компонентом (если установлено
  * свойство content, то children не отображается)
  */
type TooltipProps = {
  isVisible: any,
  offset: string,
  event: string,
  fixed: boolean,
  maxHeight: number,
  arrowPosition: string,
  closeButton: boolean,
  title: string,
  content: any,
  children: any,
};

/** State компонента Tooltip
  * @property {boolean} isVisible - виден или нет компонент
  * @property {string} offset - с какой стороны от родительского элемента открывается подсказка,
  * возможные значения: top, bottom, left, right
  * @property {string} arrowPosition - позация стрелки относительно блока с подсказкой,
  * возможные значения: start, mid, end (порядок слева направо и сверху вниз, соответственно)
  * @property {number} callCounter - счетчик вызовов функции расчета позиции, если
  * количество вызовов функции превышает количество вариантов расположения подсказки, то
  * устанавливается изначальное положение (заданное через props)
  */
type TooltipState = {
  isVisible: boolean,
  offset: string,
  arrowPosition: string,
  callCounter: number,
};


/** Props компонента TooltipHeader
  * @property {string} title - заголовок подсказки
  * @property {Function} close - функция закрытия подсказки
  */
type TooltipHeaderProps = {
  title: string,
  close: Function,
};

/** Заголовок окна с подсказкой */
const TooltipHeader = (props: TooltipHeaderProps) => (
  <div className='omni-tooltip__header'>
    <div className='omni-tooltip__header-title'>{props.title}</div>
    {props.close && <div className='omni-tooltip__close-button' onClick={props.close}>&times;</div>}
  </div>
);

/** Компонент Tooltip - подсказка, отображаемая при наведении/клике на элемент. Прикрепляется
* к родительскому элементу.
* У родительского элемента должен быть задан любой position, кроме static. Если не задан или static,
* то position будет насильно установлен в relative !!!
* @example
* <div style='position:relative'>
*   <Tooltip content='Содержимое подсказки' />
* </div>
* или
* <div>
*   <Tooltip>Содержимое подсказки</Tooltip>
* </div>
*/
export default class Tooltip extends React.Component<TooltipProps, TooltipState> {
  static defaultProps = {
    isVisible: false,
    offset: 'top',
    event: 'hover',
    /* maxHeight: 400, */
    arrowPosition: 'mid',
  };

  constructor(props: TooltipProps) {
    super(props);
    this.state = {
      isVisible: props.isVisible,
      offset: props.offset,
      arrowPosition: props.arrowPosition,
      callCounter: 0,
    };
  }

  rootRef: Object

  contentRef: Object

  componentDidMount() {
    this.setParentPosition();
    this.handleOffset();
    this.addListeners(this.props.event);
  }

  componentDidUpdate(prevProps: TooltipProps, prevState: TooltipState) {
    const { offset, arrowPosition } = this.state;
    if (prevState.offset !== offset || prevState.arrowPosition !== arrowPosition) {
      this.handleOffset();
    }
  }

  componentWillUnmount() {
    this.removeListeners(this.props.event);
  }

  // обработчик кликов, вне родительского элемента и подсказки
  handleDocumentClick = (e: Event) => {
    if (this.rootRef.parentNode && !this.rootRef.parentNode.contains(e.target)) {
      this.handleClose();
    }
  }

  addListeners = (event: string) => {
    if (!this.rootRef) {
      return;
    }

    const parent = this.rootRef.parentNode;

    if (event === 'hover') {
      parent.addEventListener('mouseenter', this.handleOpen);
      parent.addEventListener('click', this.handleClick);
      parent.addEventListener('mouseout', this.handleClose);
    } else if (event === 'click') {
      parent.addEventListener('click', this.handleOpen);
      document.addEventListener('mousedown', this.handleDocumentClick);
    } else if (event === 'dblclick') {
      parent.addEventListener('dblclick', this.handleOpen);
      document.addEventListener('mousedown', this.handleDocumentClick);
    }
  }

  removeListeners = (event: string) => {
    if (!this.rootRef) {
      return;
    }
    const parent = this.rootRef.parentNode;
    if (event === 'hover') {
      parent.removeEventListener('mouseenter', this.handleOpen);
      parent.removeEventListener('mouseout', this.handleClose);
    } else if (event === 'click') {
      parent.removeEventListener('click', this.handleOpen);
      document.removeEventListener('mousedown', this.handleDocumentClick);
    } else if (event === 'dblclick') {
      parent.removeEventListener('dblclick', this.handleOpen);
      document.removeEventListener('mousedown', this.handleDocumentClick);
    }
  }

  // обработчик кликов по подсказке и родительскому элементу
  handleClick = (e: MouseEvent) => {
    const parent = this.rootRef.parentNode;
    if (parent && parent.contains(e.target)) {
      parent.removeEventListener('mouseout', this.handleClose);
      document.addEventListener('mousedown', this.handleClick);
    }
    if (parent && !parent.contains(e.target)) {
      this.handleClose();
      parent.addEventListener('mouseout', this.handleClose);
      document.removeEventListener('mousedown', this.handleClick);
    }
  }

  handleOpen = () => {
    if (!this.state.isVisible) {
      const { offset, arrowPosition } = this.props;
      this.setState({ offset, arrowPosition });
      this.handleOffset();
      this.setState({ isVisible: true });
    }
  }

  handleClose = () => {
    this.setState({
      isVisible: false,
      callCounter: 0,
    });
  }

  setParentPosition = () => {
    if (!this.rootRef) {
      return;
    }

    const parent = this.rootRef.parentNode;
    if (!parent.style.position || parent.style.position === 'static') {
      parent.style.position = 'relative';
    }
  }

  // обработчик отступа от родительского элемента
  handleOffset = () => {
    if (!this.contentRef || this.props.fixed) {
      return;
    }

    const {
      left,
      right,
      bottom,
      top,
    } = this.contentRef.getBoundingClientRect();
    const { offset, arrowPosition } = this.state;
    const clientHeight = document.documentElement ? document.documentElement.clientHeight : 0;
    const clientWidth = document.documentElement ? document.documentElement.clientWidth : 0;
    // console.log(this.contentRef.getBoundingClientRect());

    /* все возможные положения подсказки и варианты перехода в новое положение, если
    не помещается на экран */
    const tooltipPositions = [
      {
        position: 'top_mid',
        check: () => offset === 'top' && arrowPosition === 'mid',
        getPosition: () => {
          if (top < 0) {
            return { offset: 'bottom', arrowPosition: 'mid' };
          }
          if (left < 0) {
            return { offset: 'top', arrowPosition: 'start' };
          }
          if (right > clientWidth) {
            return { offset: 'top', arrowPosition: 'end' };
          }
          return { offset: 'top', arrowPosition: 'mid' };
        },
      },
      {
        position: 'top_start',
        check: () => offset === 'top' && arrowPosition === 'start',
        getPosition: () => {
          if (top < 0) {
            return { offset: 'bottom', arrowPosition: 'start' };
          }
          if (left < 0) {
            return { offset: 'right', arrowPosition: 'end' };
          }
          if (right > clientWidth) {
            return { offset: 'top', arrowPosition: 'end' };
          }
          return { offset: 'top', arrowPosition: 'start' };
        },
      },
      {
        position: 'top_end',
        check: () => offset === 'top' && arrowPosition === 'end',
        getPosition: () => {
          if (top < 0) {
            return { offset: 'bottom', arrowPosition: 'mid' };
          }
          if (left < 0) {
            return { offset: 'top', arrowPosition: 'start' };
          }
          if (right > clientWidth) {
            return { offset: 'left', arrowPosition: 'end' };
          }
          return { offset: 'top', arrowPosition: 'end' };
        },
      },
      {
        position: 'right_mid',
        check: () => offset === 'right' && arrowPosition === 'mid',
        getPosition: () => {
          if (top < 0) {
            return { offset: 'right', arrowPosition: 'start' };
          }
          if (bottom > clientHeight) {
            return { offset: 'right', arrowPosition: 'end' };
          }
          if (right > clientWidth) {
            return { offset: 'left', arrowPosition: 'mid' };
          }
          return { offset: 'right', arrowPosition: 'mid' };
        },
      },
      {
        position: 'right_start',
        check: () => offset === 'right' && arrowPosition === 'start',
        getPosition: () => {
          if (top < 0) {
            return { offset: 'bottom', arrowPosition: 'end' };
          }
          if (bottom > clientHeight) {
            return { offset: 'right', arrowPosition: 'mid' };
          }
          if (right > clientWidth) {
            return { offset: 'left', arrowPosition: 'start' };
          }
          return { offset: 'right', arrowPosition: 'start' };
        },
      },
      {
        position: 'right_end',
        check: () => offset === 'right' && arrowPosition === 'end',
        getPosition: () => {
          if (top < 0) {
            return { offset: 'right', arrowPosition: 'mid' };
          }
          if (bottom > clientHeight) {
            return { offset: 'top', arrowPosition: 'end' };
          }
          if (right > clientWidth) {
            return { offset: 'left', arrowPosition: 'end' };
          }
          return { offset: 'right', arrowPosition: 'end' };
        },
      },
      {
        position: 'bottom_mid',
        check: () => offset === 'bottom' && arrowPosition === 'mid',
        getPosition: () => {
          if (bottom > clientHeight) {
            return { offset: 'top', arrowPosition: 'mid' };
          }
          if (left < 0) {
            return { offset: 'bottom', arrowPosition: 'start' };
          }
          if (right > clientWidth) {
            return { offset: 'bottom', arrowPosition: 'end' };
          }
          return { offset: 'bottom', arrowPosition: 'mid' };
        },
      },
      {
        position: 'bottom_start',
        check: () => offset === 'bottom' && arrowPosition === 'start',
        getPosition: () => {
          if (bottom > clientHeight) {
            return { offset: 'top', arrowPosition: 'start' };
          }
          if (left < 0) {
            return { offset: 'right', arrowPosition: 'start' };
          }
          if (right > clientWidth) {
            return { offset: 'bottom', arrowPosition: 'mid' };
          }
          return { offset: 'bottom', arrowPosition: 'start' };
        },
      },
      {
        position: 'bottom_end',
        check: () => offset === 'bottom' && arrowPosition === 'end',
        getPosition: () => {
          if (bottom > clientHeight) {
            return { offset: 'top', arrowPosition: 'end' };
          }
          if (left < 0) {
            return { offset: 'bottom', arrowPosition: 'mid' };
          }
          if (right > clientWidth) {
            return { offset: 'left', arrowPosition: 'start' };
          }
          return { offset: 'bottom', arrowPosition: 'end' };
        },
      },
      {
        position: 'left_mid',
        check: () => offset === 'left' && arrowPosition === 'mid',
        getPosition: () => {
          if (top < 0) {
            return { offset: 'left', arrowPosition: 'start' };
          }
          if (bottom > clientHeight) {
            return { offset: 'left', arrowPosition: 'end' };
          }
          if (left < 0) {
            return { offset: 'right', arrowPosition: 'mid' };
          }
          return { offset: 'left', arrowPosition: 'mid' };
        },
      },
      {
        position: 'left_start',
        check: () => offset === 'left' && arrowPosition === 'start',
        getPosition: () => {
          if (top < 0) {
            return { offset: 'bottom', arrowPosition: 'start' };
          }
          if (bottom > clientHeight) {
            return { offset: 'left', arrowPosition: 'mid' };
          }
          if (left < 0) {
            return { offset: 'right', arrowPosition: 'start' };
          }
          return { offset: 'left', arrowPosition: 'start' };
        },
      },
      {
        position: 'left_end',
        check: () => offset === 'left' && arrowPosition === 'end',
        getPosition: () => {
          if (top < 0) {
            return { offset: 'left', arrowPosition: 'mid' };
          }
          if (left < 0) {
            return { offset: 'right', arrowPosition: 'end' };
          }
          if (bottom > clientHeight) {
            return { offset: 'top', arrowPosition: 'start' };
          }
          return { offset: 'left', arrowPosition: 'end' };
        },
      },
    ];
    const defaultPosition = { offset: this.props.offset, arrowPosition: this.props.arrowPosition };
    const current = tooltipPositions.find(({ check }) => check());
    const position = current ? current.getPosition() : defaultPosition;
    // console.log(position);
    // console.log(this.state.callCounter);
    if (this.state.callCounter <= tooltipPositions.length) {
      this.setState({ ...position, callCounter: this.state.callCounter + 1 });
    } else {
      this.setState({ offset: this.props.offset, arrowPosition: this.props.arrowPosition });
    }
  }

  render() {
    const {
      isVisible,
      offset,
      arrowPosition,
    } = this.state;
    const { closeButton, title } = this.props;
    const close = closeButton ? this.handleClose : false;

    const content = this.props.content ? this.props.content : this.props.children;
    const arrowClasses = cn({
      [`omni-tooltip__arrow_${offset}`]: true,
    });
    const contentClasses = cn({
      'omni-tooltip__content': true,
      [`omni-tooltip__content_${offset}_${arrowPosition}`]: true,
    });
    const tooltipClasses = cn({
      'omni-ui-general': true,
      'omni-tooltip': true,
      'omni-tooltip_visible': isVisible,
      [`omni-tooltip_${offset}`]: true,
    });
    return (
      <div
        className={tooltipClasses}
        // eslint-disable-next-line no-return-assign
        ref={(el) => this.rootRef = el}
      >
        <div className={arrowClasses}>
          <div
            className={contentClasses}
            // eslint-disable-next-line no-return-assign
            ref={(el) => this.contentRef = el}
          >
            {(title || closeButton) && <TooltipHeader title={title} close={close}/>}
            <div className='omni-tooltip__body'>
              {/* <Scrollbars
                autoHeight
                autoHeightMax={this.props.maxHeight}
              > */}
                {content}
              {/* </Scrollbars> */}
            </div>
          </div>
        </div>
      </div>
    );
  }
}
