import Yoga, { Node, YogaNode } from 'yoga-layout-prebuilt';
import { RawNode } from '../Register/getDataFromReactNode';
import getSizeOfShape from './getShapeSize';
import {
  DisplayMap,
  FlexDirectionMap,
  FlexWrapMap,
  JustifyContentMap,
  LayoutAlignMap,
} from './LayoutEnums';

const getFourFromNumOrArr = (target: string | number | (string | number)[]) => {
  if (target instanceof Array) {
    switch (target.length) {
      case 1:
        const m = target[0];
        return [m, m, m, m];
      case 2:
        const [tb, lr] = target;
        return [tb, lr, tb, lr];
      case 3:
        const [t, lar, b] = target;
        return [t, lar, b, lar];
      case 4:
        return target;
      default:
        return [0, 0, 0, 0];
    }
  } else {
    return [target, target, target, target];
  }
};

const constructYogaNode = (node: RawNode) => {
  const yogaNode = Node.create();
  const style = node.attrs;
  if (style.position === 'absolute') {
    yogaNode.setWidth(0);
    yogaNode.setHeight(0);
    return yogaNode;
  }
  if (style.alignContent) {
    yogaNode.setAlignContent(LayoutAlignMap[style.alignContent]);
  }
  if (style.alignItems) {
    yogaNode.setAlignItems(LayoutAlignMap[style.alignItems]);
  }
  if (style.alignSelf) {
    yogaNode.setAlignSelf(LayoutAlignMap[style.alignSelf]);
  }
  if (style.display) {
    yogaNode.setDisplay(DisplayMap[style.display]);
  } else {
    yogaNode.setDisplay(DisplayMap['flex']);
  }
  if (style.flex) {
    yogaNode.setFlex(style.flex);
  }
  if (style.flexBasis) {
    yogaNode.setFlexBasis(style.flexBasis);
  }
  if (style.flexGrow) {
    yogaNode.setFlexGrow(style.flexGrow);
  }
  if (style.flexShrink) {
    yogaNode.setFlexShrink(style.flexShrink);
  }
  if (style.flexDirection) {
    yogaNode.setFlexDirection(FlexDirectionMap[style.flexDirection]);
  }
  if (style.flexWrap) {
    yogaNode.setFlexWrap(FlexWrapMap[style.flexWrap]);
  }
  if (style.justifyContent) {
    yogaNode.setJustifyContent(JustifyContentMap[style.justifyContent]);
  }
  if (style.maxHeight) {
    yogaNode.setMaxHeight(style.maxHeight);
  }
  if (style.minHeight) {
    yogaNode.setMinHeight(style.minHeight);
  }
  if (style.maxWidth) {
    yogaNode.setMaxWidth(style.maxWidth);
  }
  if (style.minWidth) {
    yogaNode.setMinWidth(style.minWidth);
  }
  if (style.height) {
    if (style.height === 'auto') {
      yogaNode.setHeightAuto();
    } else {
      yogaNode.setHeight(style.height);
    }
  }
  if (style.width) {
    if (style.width === 'auto') {
      yogaNode.setWidthAuto();
    } else {
      yogaNode.setWidth(style.width);
    }
  }
  if (style.margin) {
    const marginArray = getFourFromNumOrArr(style.margin);

    if (marginArray[0] === 'auto') {
      yogaNode.setMarginAuto(Yoga.EDGE_TOP);
    } else {
      yogaNode.setMargin(Yoga.EDGE_TOP, Number(marginArray[0]));
    }

    if (marginArray[1] === 'auto') {
      yogaNode.setMarginAuto(Yoga.EDGE_RIGHT);
    } else {
      yogaNode.setMargin(Yoga.EDGE_RIGHT, Number(marginArray[1]));
    }

    if (marginArray[2] === 'auto') {
      yogaNode.setMarginAuto(Yoga.EDGE_BOTTOM);
    } else {
      yogaNode.setMargin(Yoga.EDGE_BOTTOM, Number(marginArray[2]));
    }

    if (marginArray[3] === 'auto') {
      yogaNode.setMarginAuto(Yoga.EDGE_LEFT);
    } else {
      yogaNode.setMargin(Yoga.EDGE_LEFT, Number(marginArray[3]));
    }
  }

  if (style.padding) {
    const paddingArray = getFourFromNumOrArr(style.padding);
    yogaNode.setPadding(Yoga.EDGE_TOP, paddingArray[0]);
    yogaNode.setPadding(Yoga.EDGE_RIGHT, paddingArray[1]);
    yogaNode.setPadding(Yoga.EDGE_BOTTOM, paddingArray[2]);
    yogaNode.setPadding(Yoga.EDGE_LEFT, paddingArray[3]);
  }

  return yogaNode;
};

export interface LayoutedNode extends RawNode {
  boundaryBox: {
    width: number;
    height: number;
    x: number;
    y: number;
  };
  children: LayoutedNode[];
}

type ContainerNode = RawNode & {
  container: YogaNode;
  children: ContainerNode[];
};

const constructNodes = (
  root: RawNode,
  basicContainer: YogaNode,
): ContainerNode | null => {
  const childrenArr = [[root]];
  const parentArr = [];
  let resultNode: ContainerNode | null = null;

  while (childrenArr[0]) {
    const children = childrenArr.pop() || [];
    const parent = parentArr.pop();
    const newChilren: ContainerNode[] = [];

    for (let i = 0; i < children?.length; i += 1) {
      const node = children[i];
      const size = getSizeOfShape(node.type, node.attrs);
      if (!node.attrs.width) {
        node.attrs.width = size.width || 0;
      }
      if (!node.attrs.height) {
        node.attrs.height = size.height || 0;
      }
      const containerNode: ContainerNode = {
        ...node,
        container: constructYogaNode(node),
        children: [],
      };

      if (node.children.length) {
        parentArr.push(containerNode);
        childrenArr.push(node.children);
      }

      newChilren.push(containerNode);
    }

    if (!parent) {
      resultNode = newChilren[0];
      basicContainer.insertChild(newChilren[0].container, 0);
    } else {
      parent.children = newChilren;
      for (let j = 0; j < parent.children.length; j += 1) {
        parent.container.insertChild(parent.children[j].container, j);
      }
    }
  }

  return resultNode;
};

const caculateNodes = (
  node: ContainerNode,
  parentBoundaryBox: {
    width: number;
    height: number;
    x: number;
    y: number;
  },
): LayoutedNode => {
  const boundaryBox = {
    width: Number(node.attrs.width) || 0,
    height: Number(node.attrs.height) || 0,
    x: 0,
    y: 0,
  };
  let actualBondary = { ...boundaryBox };
  const { container, ...restNode } = node;

  if (restNode.attrs.position === 'absolute') {
    boundaryBox.x = restNode.attrs.x;
    boundaryBox.y = restNode.attrs.y;
    actualBondary = boundaryBox;
  } else if (container) {
    const layout = container.getComputedLayout();
    boundaryBox.width = layout.width;
    boundaryBox.height = layout.height;
    boundaryBox.x = layout.left + parentBoundaryBox.x;
    boundaryBox.y = layout.top + parentBoundaryBox.y;
    actualBondary = { ...boundaryBox };
    if (['circle', 'ellipse'].includes(restNode.type)) {
      boundaryBox.x += boundaryBox.width / 2;
      boundaryBox.y += boundaryBox.height / 2;
    }
    if (restNode.type === 'text') {
      boundaryBox.y += boundaryBox.height;
    }
  }

  const children: LayoutedNode[] = [];

  for (let i = 0; i < restNode.children.length; i += 1) {
    children.push(caculateNodes(restNode.children[i], actualBondary));
  }

  return {
    ...restNode,
    attrs: {
      ...restNode.attrs,
      ...boundaryBox,
    },
    children,
    boundaryBox,
  };
};

const getPositionUsingYoga = (root: RawNode): LayoutedNode => {
  const basicContainer = Node.create();

  // init container
  basicContainer.setWidthAuto();
  basicContainer.setHeightAuto();
  const newNodes =
    constructNodes(root, basicContainer) ||
    ({ ...root, container: basicContainer } as ContainerNode);
  basicContainer.calculateLayout();
  const result = caculateNodes(newNodes, {
    width: 0,
    height: 0,
    x: 0,
    y: 0,
  });
  basicContainer.freeRecursive();
  return result;
};

export default getPositionUsingYoga;
