import * as R from 'ramda';
import * as React from 'react';
import { generateHandlers } from './utils';
import { events, EventEmitTypes } from '../utils/events';
import { emitComponent } from '../eventListener';
import { ComponentEventListenerTypes } from './types';
import { setComponentNode, deleteComponentNode } from '../componentNodeMap';
import { getComponentId } from '../utils/common';

const componentIntersectionObserver = new window.IntersectionObserver(entries => {
  entries.forEach(({ target, isIntersecting }) => {
    const key = parseInt(target.getAttribute('data-key'), 10);
    events.emit(EventEmitTypes.COMPONENT_INTERSECTING_CHANGE, isIntersecting, key);
  });
});

const LONG_PRESS_TIMEOUT = 1000;
const DOUBLE_CLICK_TIMEOUT = 200;
const INTERSECTION_TIMEOUT = 500;

export class ComponentActionHandler extends React.PureComponent {
  handlerParams = {};

  handlers = {};

  containerRef = null;

  longPressTimer = null;

  preventClick = false;

  doubleClickTimer = null;

  intersectionTimer = null;

  constructor(props) {
    super(props);

    this.handlerParams = { global: props.global, meta: props.meta };
    this.updateHandlersWithParams(props.instance.actions);
  }

  componentDidMount() {
    this.observeIntersection();

    events.on(EventEmitTypes.COMPONENT_INTERSECTING_CHANGE, this.onComponentIntersectionChange);

    if (this.handlers.onInit) {
      this.handlers.onInit();
    }
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.global !== this.props.global || nextProps.meta !== this.props.meta) {
      this.updateHandlerParams(nextProps.meta, nextProps.global);
    }

    if (nextProps.instance.actions !== this.props.instance.actions) {
      this.updateHandlersWithParams(nextProps.instance.actions);
    }
  }

  componentWillUnmount() {
    this.unobserveIntersection();

    events.cancel(EventEmitTypes.COMPONENT_INTERSECTING_CHANGE, this.onComponentIntersectionChange);

    deleteComponentNode(this.props.instance.key);

    if (this.longPressTimer) {
      window.clearTimeout(this.longPressTimer);
    }
  }

  observeIntersection = () => componentIntersectionObserver.observe(this.containerRef);

  unobserveIntersection = () => componentIntersectionObserver.unobserve(this.containerRef);

  onComponentIntersectionChange = (isIntersecting, key) => {
    if (key !== this.props.instance.key) {
      return;
    }

    if (this.intersectionTimer) {
      clearTimeout(this.intersectionTimer);
      this.intersectionTimer = null;
    }

    const callback = isIntersecting ? this.onComponentEnterView : this.onComponentLeaveView;
    this.intersectionTimer = window.setTimeout(() => {
      callback();
      clearTimeout(this.intersectionTimer);
      this.intersectionTimer = null;
    }, INTERSECTION_TIMEOUT);
  };

  onComponentEnterView = () => {
    if (this.handlers.onEnterView) {
      this.handlers.onEnterView();
    }
  };

  onComponentLeaveView = () => {
    if (this.handlers.onLeaveView) {
      this.handlers.onLeaveView();
    }
  };

  onComponentPress = () => {
    if (!this.handlers.onLongPress) {
      return;
    }

    this.longPressTimer = window.setTimeout(this.handlers.onLongPress, LONG_PRESS_TIMEOUT);
  };

  onComponentPressRelease = () => {
    if (this.longPressTimer) {
      window.clearTimeout(this.longPressTimer);
    }
    this.longPressTimer = null;
  };

  onSingleClick = () => {
    this.doubleClickTimer = window.setTimeout(() => {
      if (!this.preventClick) {
        try {
          this.handlers.onClick();
        } catch (e) {
          console.error('Exec onClick action error:', e);
        }
      }
    }, DOUBLE_CLICK_TIMEOUT);
  };

  onDoubleClick = () => {
    if (this.doubleClickTimer) {
      clearTimeout(this.doubleClickTimer);
      this.doubleClickTimer = null;
    }

    this.preventClick = true;
    try {
      this.handlers.onDoubleClick();
    } catch (e) {
      console.error('Exec onClick action error:', e);
    }

    setTimeout(() => (this.preventClick = false), DOUBLE_CLICK_TIMEOUT);
  };

  emitComponentEvent = type => {
    const {
      props: { instance },
      handlerParams: { global, meta },
    } = this;
    return emitComponent(type, instance, meta, global);
  };

  withEmitComponentEvent = (type, handler) => () => {
    this.emitComponentEvent(type);
    if (handler) {
      return handler(this.handlerParams);
    }
  };

  updateHandlerParams = (meta, global) => {
    this.handlerParams.meta = meta;
    this.handlerParams.global = global;
  };

  updateHandlersWithParams = actions => {
    const {
      onInit,
      onClick,
      onMouseEnter,
      onMouseLeave,
      onDoubleClick,
      onLongPress,
      onEnterView,
      onLeaveView,
    } = generateHandlers(actions);
    const { withEmitComponentEvent, handlers } = this;

    handlers.onInit = withEmitComponentEvent(ComponentEventListenerTypes.INIT, onInit);
    handlers.onClick = withEmitComponentEvent(ComponentEventListenerTypes.CLICK, onClick);
    handlers.onMouseEnter = withEmitComponentEvent(
      ComponentEventListenerTypes.MOUSE_ENTER,
      onMouseEnter
    );
    handlers.onMouseLeave = withEmitComponentEvent(
      ComponentEventListenerTypes.MOUSE_LEAVE,
      onMouseLeave
    );
    handlers.onDoubleClick = withEmitComponentEvent(
      ComponentEventListenerTypes.DOUBLE_CLICK,
      onDoubleClick
    );
    handlers.onLongPress = withEmitComponentEvent(
      ComponentEventListenerTypes.LONG_PRESS,
      onLongPress
    );
    handlers.onEnterView = withEmitComponentEvent(
      ComponentEventListenerTypes.ENTER_VIEW,
      onEnterView
    );
    handlers.onLeaveView = withEmitComponentEvent(
      ComponentEventListenerTypes.LEAVE_VIEW,
      onLeaveView
    );
  };

  setNode = node => {
    this.containerRef = node;
    setComponentNode(this.props.instance.key, node);
  };

  render() {
    const { children, ...rest } = this.props;
    delete rest.instance;
    delete rest.meta;
    delete rest.global;

    const { onMouseLeave, onMouseEnter } = this.handlers;

    return (
      <div
        className="__impage-component-actions-proxy"
        id={getComponentId(this.props.instance.key)}
        style={this.props.instance.wrapperStyle}
        ref={this.setNode}
        data-key={this.props.instance.key}
        onClick={this.onSingleClick}
        onDoubleClick={this.onDoubleClick}
        onMouseEnter={onMouseEnter}
        onMouseLeave={onMouseLeave}
        onTouchStart={this.onComponentPress}
        onTouchEnd={this.onComponentPressRelease}
        onMouseDown={this.onComponentPress}
        onMouseUp={this.onComponentPressRelease}
      >
        {R.isEmpty(rest)
          ? children
          : React.Children.map(children, child => React.cloneElement(child, rest))}
      </div>
    );
  }
}
