import { getComponentDimension, getGroupDimension } from './component';
import cloneDeep from 'lodash/cloneDeep';
import { deepMap, isComponent, isGroup, isPanel, isRefScreen, isGeneralPanel } from './utils';
import { getId } from './interaction';

export function toObject(layers, path = [], groupPath = []) {
  return layers.reduce((all, item, index) => {
    const currentPath = path.concat(index);
    return {
      ...all,
      [item.id]: {
        ...item,
        path: currentPath,
        groupPath,
      },
      ...(item.components ? toObject(item.components, currentPath, groupPath.concat(item.id)) : {}),
    };
  }, {});
}

export function merge({ layers1, layers2, insertId, insertAfter }) {
  return layers1.reduce((all, item) => {
    if (item.id === insertId) {
      if (insertAfter) {
        return all.concat(item).concat(layers2);
      } else {
        return all.concat(layers2).concat(item);
      }
    } else if (item.components) {
      return all.concat({
        ...item,
        components: merge({ layers1: item.components, layers2, insertId, insertAfter }),
      });
    } else {
      return all.concat(item);
    }
  }, []);
}

export function sort({ layers, fromGroupId, fromIndex, toGroupId, toIndex, selected }) {
  // 按顺序找出选中的layers，并将原来的layers中，选中的置为null  N
  const [unSelectedLayers, selectedLayers] = separate(layers, selected);
  // splice出移动的那个元素  N
  unSelectedLayers === exclude(unSelectedLayers, fromGroupId, fromIndex);
  // splice插入选中的layers N
  const result = insert({
    layers: unSelectedLayers,
    selected: selectedLayers,
    groupId: toGroupId,
    index: toIndex,
  });
  // 过滤所有null的  N
  return compact(result);
}

export function separate(layers, selected) {
  const selectedLayers: any = [];
  function _separate(layers) {
    return layers.map((d) => {
      if (selected.indexOf(d.id) > -1) {
        selectedLayers.push(d);
        return null;
      } else if (d.components) {
        return {
          ...d,
          components: _separate(d.components),
        };
      } else {
        return d;
      }
    });
  }
  const excludeSelectedLayers = _separate(layers);
  return [excludeSelectedLayers, selectedLayers];
}

export function exclude(layers, groupId, index) {
  if (groupId === 'top') {
    layers.splice(index, 1);
    return layers;
  } else {
    return layers.reduce((all, item) => {
      if (item && item.id === groupId) {
        item.components.splice(index, 1);
        return all.concat(item);
      } else if (item && item.components) {
        return all.concat({
          ...item,
          components: exclude(item.components, groupId, index),
        });
      } else {
        return all.concat(item);
      }
    }, []);
  }
}

export function insert({ layers, selected, groupId, index }) {
  if (groupId === 'top') {
    layers.splice(index, 0, ...selected);
    return layers;
  } else {
    return layers.reduce((all, item) => {
      if (item && item.id === groupId) {
        item.components.splice(index, 0, ...selected);
        return all.concat(item);
      } else if (item && item.components) {
        return all.concat({
          ...item,
          components: insert({ layers: item.components, selected, groupId, index }),
        });
      } else {
        return all.concat(item);
      }
    }, []);
  }
}

export function compact(layers) {
  return layers.reduce((all, item) => {
    if (!item) {
      return all;
    } else if (item.components) {
      return all.concat({
        ...item,
        components: compact(item.components),
      });
    } else {
      return all.concat(item);
    }
  }, []);
}

export function isInSelected({ layers, selected, id }) {
  const layersById = toObject(layers);
  return layersById[id].groupPath.some((d) => selected.indexOf(d) > -1);
}

export function isContainSelected({ layers, selected, id }) {
  const layersById = toObject(layers);
  const children = getGroupChildren(layersById[id]);
  return children.some((d) => selected.indexOf(d.id) > -1);
}

export function getGroupChildren(group) {
  return group && group.components
    ? group.components.reduce(
        (all, item) => (item.components ? all.concat(getGroupChildren(item)) : all.concat(item)),
        [],
      )
    : [];
}

export function range({ layers, startId, endId }) {
  const layersById = toObject(layers);
  let startIndex = layersById[startId].path;
  let endIndex = layersById[endId].path;
  if (!isOrder(startIndex, endIndex)) {
    const bridge = startIndex;
    startIndex = endIndex;
    endIndex = bridge;
  }

  const result: any = [];
  function _range(layers, startIndex, endIndex?) {
    const range = endIndex
      ? layers.slice(startIndex[0], +endIndex[0] + 1)
      : layers.slice(+startIndex[0]);
    if (range.length) {
      if (
        range.length === 1 &&
        startIndex.length > 1 &&
        (!endIndex || (endIndex && endIndex.length > 1 && startIndex[0] === endIndex[0]))
      ) {
        //同级
        _range(range[0].components, startIndex.slice(1), endIndex && endIndex.slice(1));
        return;
      }
      if (startIndex.length > 1) {
        const first = range.splice(0, 1);
        _range(first[0].components, startIndex.slice(1));
      }

      if (range.length && endIndex && endIndex.length > 1) {
        const last = range.splice(range.length - 1, 1);
        _range(last[0].components, [0], endIndex.slice(1));
      }

      range.map((item) => result.push(item.id));
    }
  }
  _range(layers, startIndex, endIndex);
  return result;
}

export function toFront(layers, selected) {
  return layers.reduce((all, item, index) => {
    // 当被选中的、不是第一个、且往上还有未被选中的，则移动至第一个未被选中的
    if (selected.indexOf(item.id) > -1 && index > 0) {
      const firstUnselected = all.findIndex((item) => selected.indexOf(item.id) === -1);
      if (firstUnselected !== -1) {
        all.splice(firstUnselected, 0, item);
        return all;
      } else {
        return all.concat(item);
      }
    } else if (item.components) {
      return all.concat({ ...item, components: toFront(item.components, selected) });
    } else {
      return all.concat(item);
    }
  }, []);
}

export function toBack(layers, selected) {
  return layers
    .concat()
    .reverse()
    .reduce((all, item, index) => {
      // 当被选中的、不是最后一个、且往下还有未被选中的，则移动至最后一个未被选中的
      if (selected.indexOf(item.id) > -1 && index > 0) {
        const firstUnselected = all.findIndex((item) => selected.indexOf(item.id) === -1);
        if (firstUnselected !== -1) {
          all.splice(firstUnselected, 0, item);
          return all;
        } else {
          return all.concat(item);
        }
      } else if (item.components) {
        return all.concat({ ...item, components: toBack(item.components, selected) });
      } else {
        return all.concat(item);
      }
    }, [])
    .reverse();
}

export function toUp(layers, selected) {
  return layers.reduce((all, item, index) => {
    // 当被选中的、不是当前组的第一个、当前项的前面一个未被选中，则向前移一项
    if (selected.indexOf(item.id) > -1 && index > 0 && selected.indexOf(all[index - 1].id) === -1) {
      const merged = all.concat(item);
      merged[index] = merged[index - 1];
      merged[index - 1] = item;
      return merged;
    } else if (item.components) {
      return all.concat({ ...item, components: toUp(item.components, selected) });
    } else {
      return all.concat(item);
    }
  }, []);
}

export const toDown = (layers, selected) => {
  return layers
    .concat()
    .reverse()
    .reduce((all, item, index) => {
      // 当被选中的、不是当前组的第一个、当前项的前面一个未被选中，则向前移一项
      if (
        selected.indexOf(item.id) > -1 &&
        index > 0 &&
        selected.indexOf(all[index - 1].id) === -1
      ) {
        const merged = all.concat(item);
        merged[index] = merged[index - 1];
        merged[index - 1] = item;
        return merged;
      } else if (item.components) {
        return all.concat({ ...item, components: toDown(item.components, selected) });
      } else {
        return all.concat(item);
      }
    }, [])
    .reverse();
};

export function groupBy(layers, selected) {
  let groupByComponents: any = [];

  function _groupBy(layers, selected) {
    return layers.reduce((all, item) => {
      if (!selected.length) {
        return all.concat(item);
      }

      const i = selected.indexOf(item.id);
      if (i > -1) {
        selected.splice(i, 1);
        groupByComponents.push(item);
        if (selected.length) {
          return all;
        } else {
          groupByComponents = [
            {
              id: `group_${Math.random().toString(36).substr(3, 20)}`,
              name: '分组',
              show: true,
              lock: false,
              collapsed: true,
              components: groupByComponents,
            },
          ];
          return all.concat(groupByComponents);
        }
      } else if (item.components) {
        const result = _groupBy(item.components, selected);
        return all.concat({ ...item, components: result });
      } else {
        return all.concat(item);
      }
    }, []);
  }
  const newLayers = _groupBy(layers, selected.concat());
  return [newLayers, groupByComponents[0].id];
}

export function cancelGroup(layers, selected) {
  return layers.reduce(
    (all, item) => {
      if (item.components && selected.indexOf(item.id) > -1) {
        return [all[0].concat(item.components), all[1].concat(item.components.map((d) => d.id))];
      } else if (item.components) {
        const result = cancelGroup(item.components, selected);
        return [all[0].concat({ ...item, components: result[0] }), all[1].concat(result[1])];
      } else {
        return [all[0].concat(item), all[1]];
      }
    },
    [[], []],
  );
}

export function bindDimension({ layers, componentsById, panelsById }) {
  return layers
    .map((d) => {
      if (isComponent(d.id)) {
        const component = componentsById[d.id];
        if (component) {
          const dimension = getComponentDimension(component.config);
          return {
            ...d,
            dimension: dimension,
          };
        } else {
          return null;
        }
      } else if (isGroup(d.id)) {
        const groupChildren = getGroupChildren(d);
        const childrenDimensions = groupChildren
          .map((v: any) => {
            if (isComponent(v.id)) {
              const component = componentsById[v.id];
              if (component) {
                const dimension = getComponentDimension(component.config);
                return { id: v.id, ...dimension };
              } else {
                return null;
              }
            } else if (isGeneralPanel(v.id)) {
              const panel = panelsById[getId(v.id) as number];
              if (panel) {
                const { config } = panel;
                const { width, height, top, left } = config;

                return { ...d, width, height, left, top };
              }
            }
          })
          .filter((d) => d);
        const groupDimension = getGroupDimension(childrenDimensions);

        return {
          ...d,
          dimension: groupDimension,
          components: bindDimension({ layers: d.components, componentsById, panelsById }),
        };
      } else if (isGeneralPanel(d.id)) {
        const panel = panelsById[getId(d.id) as number];
        if (panel) {
          const { config } = panel;
          const { width, height, top, left } = config;

          return { ...d, dimension: { width, height, left, top } };
        }
      }
    })
    .filter((d) => d);
}

export function containPoint({ layers, point }) {
  return layers
    .map((d) => {
      if (!d.show) {
        return null;
      }

      const { dimension } = d;
      if (isPointInRect(point, dimension)) {
        if (d.components) {
          return {
            ...d,
            components: containPoint({ layers: d.components, point }),
          };
        } else {
          return d;
        }
      } else {
        return null;
      }
    })
    .filter((d) => d);
}

export function isPointInRect(point, rect) {
  return (
    point.left > rect.left &&
    point.left < rect.left + rect.width &&
    point.top > rect.top &&
    point.top < rect.top + rect.height
  );
}

export function isOrder(path1, path2) {
  let isBefore = true;
  let i = 0;
  let isBreak = false;
  while (!isBreak && i <= Math.min(path1.length, path2.length)) {
    if (path1[i] > path2[i]) {
      isBefore = false;
      isBreak = true;
    } else if (path1[i] < path2[i]) {
      isBreak = true;
    }
    i++;
  }
  return isBefore;
}

/**
 * 获取图层中第一个被选中的id (component/group/panel)
 */
export function getFirstSelectedId({ layers, selected = [] }) {
  if (!selected.length) {
    return;
  }

  let result;

  const _getFirstSelectedId = (layers, selected) => {
    for (let i = 0; i < layers.length; i++) {
      if (selected.includes(layers[i].id)) {
        result = layers[i].id;
        break;
      } else if (layers[i].components) {
        if (result) {
          break;
        } else {
          _getFirstSelectedId(layers[i].components, selected);
        }
      }
    }
  };

  _getFirstSelectedId(layers, selected);

  return result;
}

export function insertLayerById({ layers, layer, insertId }) {
  if (insertId) {
    let finish = false;

    const _insertLayerById = (layers) => {
      for (let i = 0; i < layers.length; i++) {
        if (layers[i].id === insertId) {
          layers.splice(i, 0, layer);
          finish = true;
          break;
        } else if (layers[i].components) {
          if (finish) {
            break;
          } else {
            _insertLayerById(layers[i].components);
          }
        }
      }
    };

    layers = cloneDeep(layers);
    _insertLayerById(layers);

    return layers;
  } else {
    return [layer].concat(layers);
  }
}

export function filterSelectedLayers(selected, layers) {
  return layers.reduce((all, item) => {
    if (selected.indexOf(item.id) > -1) {
      return all.concat({ id: item.id });
    } else if (item.components) {
      const children = filterSelectedLayers(selected, item.components);
      if (children.length) {
        return all.concat({ id: item.id, components: children });
      } else {
        return all;
      }
    } else {
      return all;
    }
  }, []);
}

export function getPanelsFromLayers(layers: any = []) {
  return layers.reduce(
    (total, item) =>
      isGeneralPanel(item.id)
        ? total.concat(item)
        : item.components
        ? total.concat(getPanelsFromLayers(item.components))
        : total,
    [],
  );
}

// 获取大屏需要显示的组件
export function getShowedComponents({ id, screensById, panelsById }) {
  const { layers } = screensById[id];

  const _getShowedComponents = (layers) => {
    if (!layers) {
      return [];
    }

    return layers.reduce(
      (total, item: any) =>
        item.show && item.hideDefault !== true
          ? isGroup(item.id)
            ? total.concat(_getShowedComponents(item.components))
            : isGeneralPanel(item.id)
            ? total.concat(
                _getShowedComponents(
                  screensById[panelsById[getId(item.id) as number]?.states[0]]?.layers,
                ),
              )
            : total.concat(item)
          : total,
      [],
    );
  };

  return _getShowedComponents(layers);
}

// 通过id找到对应的图层
export function findLayerById({ id, layers }) {
  let result;

  const _findLayerById = (layers) => {
    for (let i = 0; i < layers.length; i++) {
      const layer = layers[i];
      if (id === layer.id) {
        result = layer;
        break;
      }

      if (layer.components) {
        _findLayerById(layer.components);
      }
    }
  };

  _findLayerById(layers);

  return result;
}

export function getGlobalLayers({ entry, screensById = {}, panelsById = {} }) {
  try {
    const { layers } = screensById[entry];
    return deepMap(
      layers,
      (item: any) => {
        if (isPanel(item.id)) {
          const panelId = getId(item.id) as number;
          const { states } = panelsById[panelId];

          return {
            ...item,
            components: states.map((state) => {
              const { name } = screensById[state];
              return {
                id: `screen_${state}`,
                name: name,
                components: getGlobalLayers({ entry: state, screensById, panelsById }),
              };
            }),
          };
        } else if (isRefScreen(item.id)) {
          const panelId = getId(item.id) as number;
          const { states } = panelsById[panelId];

          return {
            ...item,
            components: states.map((state) => {
              const { name } = screensById[state];
              return {
                id: `screen_${state}`,
                name: name,
              };
            }),
          };
        } else {
          return item;
        }
      },
      'components',
    );
  } catch (error) {
    return [];
  }
}

export function getGlobalPanels(globalLayers: any = []) {
  return globalLayers.reduce((all, item) => {
    if (isGeneralPanel(item.id)) {
      const states = item.components.map((state) => ({
        name: state.name,
        value: `$state(${getId(state.id)})`,
      }));
      const components = item.components.reduce((acc, cur) => {
        const panels = getGlobalPanels(cur.components);
        if (panels.length) {
          return acc.concat({ name: cur.name, value: cur.id, components: panels });
        } else {
          return acc;
        }
      }, []);
      return all.concat({
        name: item.name,
        value: `$panel(${item.id})`,
        states,
        components: components,
      });
    } else if (isGroup(item.id)) {
      return all.concat(getGlobalPanels(item.components));
    } else {
      return all;
    }
  }, []);
}

export function withChildren(layers, componentsById) {
  return layers.reduce((all, item) => {
    if (isComponent(item.id)) {
      const component = componentsById[item.id];

      if (component && component.children && component.children.length) {
        const children = component.children.reduce((acc, cur) => {
          const child = componentsById[cur];
          if (child) {
            return acc.concat({
              _id: `$component(${cur})`,
              id: cur,
              name: child.name,
            });
          } else {
            return acc;
          }
        }, []);

        return all.concat({
          ...item,
          children: children,
        });
      } else {
        return all.concat(item);
      }
    } else if (item.components) {
      return all.concat({
        ...item,
        components: withChildren(item.components, componentsById),
      });
    } else {
      return all.concat(item);
    }
  }, []);
}
