import React, { Component, useEffect } from 'react';
import { connect } from 'react-redux';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import Mousetrap from 'mousetrap';
import classNames from 'classnames';
import Transformer from './Transformer';
import { DragProvider, ModalEditProvider } from 'components/Screen/EditContext';
import ScreenComponents from './ScreenComponents';
import { flattenToObject, getGroupDimension, componentOperator } from 'utils/component';
import { showContextMenu, hideContextMenu } from '../ContextMenu/models';
import { updatePanel } from 'models/panel';
import {
  selectLayers,
  selectLayerWithScrollIntoView,
  resetSelect,
  undoConfigChange,
  changeComponentConfigActiveKey,
  showComponentEditModal,
  hideComponentEditModal,
} from 'models/create-app';
import { getAssetsUrl } from 'utils/utils';
import { updateComponentConfig, copyComponents, hybridUpdate } from 'models/component';
import styles from './Simulator.less';
import isNumber from 'lodash/isNumber';
import { screenOperator, getTopScreenId } from 'utils/screen';
import { arrayExpand } from 'utils/data-helper';
import {
  PartialStateShare,
  raf,
  isPanel,
  isRefScreen,
  isComponent,
  isGeneralPanel,
  isGroup,
} from 'utils/utils';
import { getId } from 'utils/interaction';
import {
  pressed,
  layerIgnored,
  getCoordinateInBoard,
  contained,
  MouseHover,
  MouseDrag,
  BoxSelection,
} from './Moveable';
import { Utils } from '@easyv/react-gui';
const { reduceConfig, updateArrayConfig, getComponentDimension } = Utils;

function EventBind() {
  useEffect(() => {
    const container = document.getElementById('j-previewarea');

    const onmousemove = raf((e) => {
      PartialStateShare.emit('simulator_mousemove', e);
    });
    const onmouseup = (e) => {
      PartialStateShare.emit('mouse_press', false);
      PartialStateShare.emit('simulator_mouseup', e);
    };
    const onmousedown = (e) => {
      PartialStateShare.emit('mouse_press', true);
      PartialStateShare.emit('simulator_mousedown', e);
    };

    const onKeyUp = (e) => {
      PartialStateShare.emit('simulator_keyup', e);
    };

    const onKeyDown = (e) => {
      PartialStateShare.emit('simulator_keydown', e);
    };

    container?.addEventListener('mousedown', onmousedown);
    container?.addEventListener('mousemove', onmousemove);
    window?.addEventListener('keyup', onKeyUp);
    window?.addEventListener('keydown', onKeyDown);
    // 鼠标出container无法触发事件 所以监听到window上
    window.addEventListener('mouseup', onmouseup);
    return () => {
      container?.removeEventListener('mousedown', onmousedown);
      container?.removeEventListener('mousemove', onmousemove);
      // 鼠标出container无法触发事件 所以监听到window上
      window.removeEventListener('mouseup', onmouseup);
      window?.removeEventListener('keyup', onKeyUp);
      window?.removeEventListener('keydown', onKeyDown);
    };
  }, []);
  return null;
}

interface IProps extends RouteComponentProps {
  panelId: number;
  layers: any;
  componentsById: any;
  layersSelected: any;
  panelsById: any;
  focusSet: any;
  config: any;
  width: number;
  height: number;
  modalEdit: number;
  rulerLines: any;
  scale: number;
  space: boolean;
  focusMode: boolean;
  editId: number | string | null;
  undoConfigChange: () => any;
  hideContextMenu: () => any;
  hybridUpdate: (param) => any;
  selectLayers: (param) => any;
  resetSelect: () => any;
  selectLayerWithScrollIntoView: (param) => any;
  showContextMenu: (param) => any;
  copyComponents: (param) => any;
  changeComponentConfigActiveKey: (param) => any;
  showComponentEditModal: (param) => any;
  hideComponentEditModal: (param) => any;
  handleMouseScroll: (e) => any;
}

class Simulator extends Component<IProps> {
  selectedDom: any[];
  controller: AbortController;
  private _clickPos: any[];
  constructor(props) {
    super(props);
    this.selectedDom = [];
  }
  componentDidMount() {
    this.controller = window.AbortController && new AbortController();

    Mousetrap.bind(['ctrl+z', 'command+z'], () => {
      this.props.undoConfigChange();
    });
  }

  componentDidUpdate(nextProps) {
    // 切换屏幕时清空hover状态
    if (nextProps.panelId !== this.props.panelId) {
      PartialStateShare.emit('mouse_over_layer', null);
    }
  }

  componentWillUnmount() {
    Mousetrap.unbind(['ctrl+z', 'command+z']);
    this.controller && this.controller.abort();

    // 离开屏幕时清理状态
    const { hideContextMenu } = this.props;
    hideContextMenu();
  }

  getTransformerDimension = () => {
    const { layers, componentsById, layersSelected, panelsById, focusSet } = this.props;

    const dimensions: any = [];
    arrayExpand(
      layers,
      (layer) => layer.components,
      (layer) => {
        if (!~layersSelected.indexOf(layer.id)) return;
        if (layerIgnored(layer, { ignoreLocked: false })) return;
        if (!!focusSet) {
          focusSet.has(layer.id) &&
            dimensions.push(componentOperator.getLayerScale({ layer, componentsById, panelsById }));
          return;
        }
        dimensions.push(componentOperator.getLayerScale({ layer, componentsById, panelsById }));
      },
    );

    if (!dimensions.length) return {};
    return getGroupDimension(dimensions);
  };
  onResizeStart = () => {
    const { layersSelected } = this.props;

    const selectedDom = layersSelected.map((d) => {
      const dom = document.getElementById(`rnd_${d}`);
      return {
        id: d,
        dom,
        defaultUi: {
          width: parseInt(dom?.style.width || '0'),
          height: parseInt(dom?.style.height || '0'),
          top: parseInt(dom?.style.top || '0'),
          left: parseInt(dom?.style.left || '0'),
        },
      };
    });

    this.selectedDom = selectedDom;
  };
  onTransformerDragEnd = ({ deltaX, deltaY }) => {
    this.selectedDom = [];
    this.onDragEnd({ offsetX: deltaX, offsetY: deltaY, moveLayers: this.props.layersSelected });
  };
  onResize = ({ originX, originY, x, y, scaleWidth, scaleHeight }) => {
    this.selectedDom.map(({ dom, defaultUi }) => {
      const { left, top, width, height } = defaultUi;
      const newWidth = Math.round(width * scaleWidth);
      const newHeight = Math.round(height * scaleHeight);
      const newLeft = Math.round(x + (left - originX) * scaleWidth);
      const newTop = Math.round(y + (top - originY) * scaleHeight);
      dom.style.width = `${newWidth}px`;
      dom.style.height = `${newHeight}px`;
      dom.style.left = `${newLeft}px`;
      dom.style.top = `${newTop}px`;
    });
  };

  onResizeStop = ({ originX, originY, x, y, scaleWidth, scaleHeight }) => {
    this.selectedDom = [];

    const { layers, componentsById, layersSelected, panelsById } = this.props;

    const componentsStates: any = [];
    const panelStates: any = [];

    screenOperator.getComponentsFromSelectedLayers(layersSelected, layers).forEach((id) => {
      if (isComponent(id)) {
        const component = componentsById[id];
        if (!component) {
          return;
        }

        const { width, height, top, left } = getComponentDimension(component.config);
        const newWidth = Math.round((width || 1) * scaleWidth);
        const newHeight = Math.round((height || 1) * scaleHeight);
        const newLeft = Math.round(x + (left - originX) * scaleWidth);
        const newTop = Math.round(y + (top - originY) * scaleHeight);
        const updates = [
          { path: ['chart', 'dimension', 'chartPosition', 'left'], value: newLeft },
          { path: ['chart', 'dimension', 'chartPosition', 'top'], value: newTop },
          { path: ['chart', 'dimension', 'chartDimension', 'width'], value: newWidth },
          { path: ['chart', 'dimension', 'chartDimension', 'height'], value: newHeight },
        ];

        const newConfig = updateArrayConfig(component.config, updates);

        componentsStates.push({
          componentId: id,
          updateData: {
            config: newConfig,
          },
        });
      } else if (isGeneralPanel(id)) {
        const { id: panelId, config } = panelsById[getId(id) as number];
        panelStates.push({
          id: panelId,
          updateData: {
            config: {
              left: Math.round(x + (config.left - originX) * scaleWidth),
              top: Math.round(y + (config.top - originY) * scaleHeight),
              width: Math.round(config.width * scaleWidth),
              height: Math.round(config.height * scaleHeight),
            },
          },
        });
      }
    });

    this.props.hybridUpdate({ component: componentsStates, panel: panelStates });
  };

  onMouseDown = (e) => {
    this._clickPos = [e.clientX, e.clientY];
  };

  onBordClick = (event) => {
    const {
      history,
      panelsById,
      componentsById,
      resetSelect,
      layers,
      layersSelected,
      hideContextMenu,
      selectLayers,
      selectLayerWithScrollIntoView,
    } = this.props;

    hideContextMenu();

    // 根据鼠标位置移动来区分点击和拖拽
    if (
      Math.abs(this._clickPos[0] - event.clientX) > 1 ||
      Math.abs(this._clickPos[1] - event.clientY) > 1
    )
      return;

    const ctrlPressed = pressed({ e: event, keys: ['ctrl'] });
    const clickedId = (PartialStateShare.get('mouse_over_layer') || {}).hover;

    if (!clickedId) {
      resetSelect();
      return;
    }

    // 单击
    if (event.nativeEvent.detail === 1) {
      // 追加选择
      if (event.shiftKey) {
        if (!~layersSelected.indexOf(clickedId)) {
          selectLayers(layersSelected.concat(clickedId));
        }
        return;
      }
      selectLayerWithScrollIntoView(clickedId);
    } else {
      // 双击
      // 按下command+双击无效
      if (ctrlPressed) return;
      const [pathname] = window.location.pathname.split('/panel_');
      const isTerminal = /\/app\/edit\/[0-9]+\/[0-9]+/.test(pathname);

      if (~layersSelected.indexOf(clickedId)) {
        // 动态面板双击进入
        if (isPanel(clickedId)) {
          const panel = panelsById[getId(clickedId) as number];
          if (panel) {
            const { states = [] } = panel;
            const stateId = states[0];
            const topScreenId = getTopScreenId();
            const panelUrl = isTerminal
              ? `${pathname.replace(BASENAME, '')}/${clickedId}/${stateId}`
              : `/create/${topScreenId}/${clickedId}/${stateId}`;
            this.props.resetSelect();
            history.push(panelUrl);
            // 清除hover状态
            this.setState({ hoverLayers: [] });
          }
          return;
        }

        // 引用面板则跳转至项目编辑
        if (isRefScreen(clickedId)) {
          const panel = panelsById[getId(clickedId) as number];
          if (panel) {
            const { states } = panel;

            if (states[0]) {
              const from = JSON.parse(sessionStorage.getItem('linkFrom') || '[]');
              sessionStorage.setItem(
                'linkFrom',
                JSON.stringify(from.concat(history.location.pathname)),
              );

              history.push(`/create/${states[0]}`);
              // 清除hover状态
              this.setState({ hoverLayers: [] });
            }
          }
          return;
        }

        // 如果是分组 双击选中里层
        if (isGroup(clickedId)) {
          const group = screenOperator.findLayerById(clickedId, layers) as any;
          const { x, y } = getCoordinateInBoard({ x: event.clientX, y: event.clientY });
          for (let i = 0; i < group.components.length; i++) {
            const layer = group.components[i];
            if (layerIgnored(layer)) {
              continue;
            }
            if (
              contained(
                { left: x, top: y, width: 0, height: 0 },
                componentOperator.getLayerScale({ layer, componentsById, panelsById }),
              )
            ) {
              selectLayerWithScrollIntoView(layer.id);
            }
          }

          return;
        }

        selectLayerWithScrollIntoView(clickedId);
      }
    }
  };

  getDisabled = () => {
    const { layers, layersSelected } = this.props;
    const layersById = flattenToObject(layers);
    return layersSelected.every((d) => layersById[d] && layersById[d].lock);
  };

  getDimensionLock = () => {
    const { layersSelected, componentsById, layers, panelsById } = this.props;
    const layersById = flattenToObject(layers);
    return layersSelected.some((d) => {
      if (isComponent(d)) {
        const component = componentsById[d];
        return component && getComponentDimension(component.config).lock;
      } else if (isGroup(d)) {
        return layersById[d] && layersById[d].dimensionLock;
      } else if (isGeneralPanel(d)) {
        const panel = panelsById[getId(d) as number];
        return panel && panel.config.lock;
      }
    });
  };

  handleBoardContextMenu = (e) => {
    e.preventDefault();
    const { showContextMenu } = this.props;

    const stayLayer = (PartialStateShare.get('mouse_over_layer') || {}).hover;
    // 右击空白处
    if (!stayLayer) {
      showContextMenu({
        type: 'screen',
        event: e,
      });
      return;
    }

    showContextMenu({
      type: 'screen',
      event: e,
      id: stayLayer,
    });
  };

  onSelection = raf(({ x, y, width, height }) => {
    const { layers, componentsById, panelsById, selectLayers } = this.props;
    const isOverlap = (item) => {
      const a =
        item.left + item.width < x ||
        item.left > x + width ||
        item.top + item.height < y ||
        item.top > y + height;
      return !a;
    };
    const blocks: any = [];
    layers.forEach((item) => {
      if (layerIgnored(item)) return;
      if (isComponent(item.id)) {
        isOverlap(getComponentDimension(componentsById[item.id].config)) && blocks.push(item.id);
      } else if (isGeneralPanel(item.id)) {
        isOverlap(panelsById[getId(item.id) as number].config) && blocks.push(item.id);
      } else if (isGroup(item.id)) {
        const list: any = [];
        arrayExpand(
          item.components,
          (item) => item.components,
          (item) => {
            if (isComponent(item.id)) {
              list.push(getComponentDimension(componentsById[item.id].config));
            } else if (isRefScreen(item.id)) {
              list.push(panelsById[getId(item.id) as number].config);
            }
          },
        );
        isOverlap(getGroupDimension(list)) && blocks.push(item.id);
      }
    });
    selectLayers(blocks);
  });

  onDragEnd = ({
    altPress,
    changeLayer,
    moveLayers,
    offsetX,
    offsetY,
  }: {
    altPress?: boolean;
    changeLayer?: any;
    moveLayers: any;
    offsetX: number;
    offsetY: number;
  }) => {
    if (changeLayer) {
      this.props.selectLayers(changeLayer);
    }
    if (altPress) {
      this.props.copyComponents({ offsetX, offsetY });
    } else {
      const { layers, componentsById, panelsById } = this.props;

      const componentStates: any = [];
      const panelStates: any = [];
      screenOperator.getComponentsFromSelectedLayers(moveLayers, layers).forEach((id) => {
        if (isNumber(id)) {
          const component = componentsById[id];
          const dimension = getComponentDimension(component.config);
          const newLeft = dimension.left + offsetX;
          const newTop = dimension.top + offsetY;
          const config = updateArrayConfig(component.config, [
            { path: ['chart', 'dimension', 'chartPosition', 'left'], value: newLeft },
            { path: ['chart', 'dimension', 'chartPosition', 'top'], value: newTop },
          ]);

          componentStates.push({
            componentId: id,
            updateData: {
              config,
            },
          });
        } else if (isGeneralPanel(id)) {
          const { id: panelId, config } = panelsById[getId(id) as number];
          panelStates.push({
            id: panelId,
            updateData: {
              config: {
                left: config.left + offsetX,
                top: config.top + offsetY,
              },
            },
          });
        }
      });

      this.props.hybridUpdate({ component: componentStates, panel: panelStates });
    }
  };

  render() {
    const {
      config = [],
      panelId,
      scale,
      space,
      layersSelected,
      width,
      height,
      layers,
      componentsById,
      panelsById,
      modalEdit,
      rulerLines,
      focusMode,
      editId,
    } = this.props;
    const {
      style = {},
      component = {},
      background: stateBackground,
      backgroundColor: stateBackgroundColor,
      useBackground: stateUseBackground,
    } = reduceConfig(config);
    const useBackground = panelId ? stateUseBackground : style.useBackground;
    const background = panelId ? stateBackground : style.background;
    const backgroundColor = panelId ? stateBackgroundColor : style.backgroundColor;

    const backgroundImage =
      useBackground == null || useBackground
        ? background && background !== 'placeholder'
          ? `url(${getAssetsUrl(background)})`
          : undefined
        : undefined;
    const simulatorStyle: React.CSSProperties = {
      width: width + 'px',
      height: height + 'px',
      backgroundImage,
      backgroundColor,
      transform: `scale(${scale})`,
    };
    // const disabled = this.getDisabled();
    const dimensionLock = this.getDimensionLock();

    let transformerDimension: any = {};
    if (
      layersSelected.length > 1 ||
      (layersSelected.length === 1 && !screenOperator.findLayerById(layersSelected[0], layers).lock)
    ) {
      transformerDimension = this.getTransformerDimension();
    }

    PartialStateShare.emit('simulator_scale', scale);

    return (
      <DragProvider
        scale={scale}
        changeComponentConfigActiveKey={this.props.changeComponentConfigActiveKey}
        showComponentEditModal={this.props.showComponentEditModal}
        hideComponentEditModal={this.props.hideComponentEditModal}>
        <ModalEditProvider
          modalEdit={modalEdit}
          focusMode={focusMode}
          canvasWidth={width}
          canvasHeight={height}
          selectedlayers={layersSelected}
          layers={layers}
          components={componentsById}
          panels={panelsById}
          rulerLines={rulerLines}>
          <div
            className={styles.preview}
            id="j-previewarea"
            onMouseDown={this.onMouseDown}
            onClick={this.onBordClick}
            onContextMenu={this.handleBoardContextMenu}
            style={{ overflow: modalEdit ? 'hidden' : 'auto' }}>
            <div
              id="m-simulator-wrap"
              onMouseMove={this.props.handleMouseScroll}
              style={{
                position: 'absolute',
                width: 100000,
                height: 100000,
                overflow: modalEdit ? 'hidden' : 'auto',
                cursor: `${space ? 'grab' : ''}`,
              }}>
              <div
                id="m-simulator-container"
                style={{
                  width: width * scale || 0,
                  height: height * scale || 0,
                  display: width * scale ? 'unset' : 'none',
                }}
                className={classNames(styles.container, { space: space })}>
                {!modalEdit && (
                  <div
                    style={{
                      position: 'absolute',
                      right: 0,
                      bottom: -24,
                      color: '#a9afb8',
                      whiteSpace: 'nowrap',
                    }}>
                    <span style={{ marginRight: 10 }}>按住空格可拖动画布</span>
                    {`${width}x${height} ${Math.round(scale * 100)}%`}
                  </div>
                )}

                <div id="m-simulator" className={styles.simulator} style={simulatorStyle}>
                  <ScreenComponents />
                  {!modalEdit && (
                    <Transformer
                      // key={transformerDimension.left + '_' + transformerDimension.top}
                      width={transformerDimension.width}
                      height={transformerDimension.height}
                      top={transformerDimension.top}
                      left={transformerDimension.left}
                      scale={scale}
                      grid={component.grid}
                      lockAspectRatio={dimensionLock}
                      onDragStop={this.onTransformerDragEnd}
                      onResizeStart={this.onResizeStart}
                      onResize={this.onResize}
                      onResizeStop={this.onResizeStop}
                    />
                  )}
                </div>

                <div
                  id="support_lines"
                  style={{
                    position: 'absolute',
                    left: 0,
                    top: 0,
                    zIndex: 4,
                    pointerEvents: 'none',
                  }}></div>
              </div>
              <MouseHover />
              {!modalEdit && !editId && <MouseDrag onDragEnd={this.onDragEnd} />}
              {!modalEdit && <BoxSelection onChange={this.onSelection} />}
              {!modalEdit && <EventBind />}
            </div>
          </div>
        </ModalEditProvider>
      </DragProvider>
    );
  }
}

function mapStateToProps({ createApp, screensById, panelsById, componentsById }) {
  const {
    id,
    scale,
    layersSelected,
    componentsChildSelected,
    focusLayers,
    modalEdit,
    focusMode,
    editId,
  } = createApp;
  const screen = screensById[id];
  let layers = [];
  let config;
  let panelId;
  let focusSet;
  if (screen) {
    layers = screen.layers;
    focusSet = screenOperator.computeFocusLayers(layers, focusLayers);
    if (focusSet) {
      const filtered: any = [];
      arrayExpand(
        layers,
        (layer) => layer.components,
        (layer) => {
          focusSet.has(layer.id) && filtered.push(layer);
        },
      );
      layers = filtered;
    }
    config = screen.config;
    panelId = screen.panelId;
  }
  return {
    screenId: id,
    config,
    panelId,
    scale,
    layers,
    layersSelected,
    componentsChildSelected,
    componentsById,
    panelsById,
    focusSet,
    modalEdit,
    focusMode,
    editId,
  };
}

const mapDispatchToProps = {
  undoConfigChange,
  updateComponentConfig,
  selectLayers,
  selectLayerWithScrollIntoView,
  resetSelect,
  showContextMenu,
  hideContextMenu,
  updatePanel,
  copyComponents,
  hybridUpdate,
  changeComponentConfigActiveKey: changeComponentConfigActiveKey,
  showComponentEditModal,
  hideComponentEditModal,
};

export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Simulator));
