export type NStep = {
  id: number;
  parentIds: number[];
  name: string;
  bound: { width: number; height: number; left: number; top: number };
  prevId: number;
  raw: any;
  level: number;
};

const idFactory = () => {
  let id = 0;
  return () => id++;
};
const getId = idFactory();

const _flatSteps = (
  steps: any,
  parentIds: number[] = [],
  level = 0,
  parallel = false,
  nsteps: NStep[] = []
): number[] => {
  const lonelyStepIds: number[] = [];

  let tempLevel = level;
  let tempParentIds = parentIds;

  for (const step of steps) {
    const id = getId();
    const stepParallel = parallel;

    if (!stepParallel && lonelyStepIds.length) {
      tempParentIds = [...lonelyStepIds];
      tempLevel =
        Math.max(
          ...nsteps
            .filter((nstep) => lonelyStepIds.includes(nstep.id))
            .map((nstep) => nstep.level)
        ) + 1;
      lonelyStepIds.length = 0;
    }

    let tempPrevId = -1;
    for (let i = nsteps.length - 1; i >= 0; i--) {
      if (nsteps[i].level === tempLevel) {
        tempPrevId = nsteps[i].id;
        break;
      }
    }

    const nstep = {
      id,
      name: step.name,
      parentIds: tempParentIds,
      prevId: tempPrevId,
      raw: step,
      level: tempLevel,
    } as NStep;
    nsteps.push(nstep);

    if (step.steps && step.steps.length) {
      lonelyStepIds.push(
        ..._flatSteps(
          step.steps as any,
          [id],
          tempLevel + 1,
          step.subStepsParallel === "True" ||
            step.serviceTypeParallel === "True",
          nsteps
        )
      );
    } else {
      lonelyStepIds.push(nstep.id);
    }
  }

  return lonelyStepIds;
};

type Size = { width: number; height: number };
export const attachBoundAttrib = (
  steps: NStep[],
  getStepSize: (step: any) => { width: number; height: number }
) => {
  steps.sort((a, b) => a.level - b.level);
  const stepSizeMap = new Map<NStep, Size>();
  for (const step of steps) {
    const size = getStepSize(step.raw);
    stepSizeMap.set(step, size);
  }

  const treeSizeMap = new Map<NStep, Size>();
  const levelHeights: number[] = new Array(
    steps[steps.length - 1].level + 1
  ).fill(0);
  for (let i = steps.length - 1; i >= 0; i--) {
    const root = steps[i];
    const child = steps.filter(
      (oStep) =>
        oStep.parentIds.length === 1 &&
        oStep.parentIds.some((id) => root.id === id)
    );

    const rootBound = stepSizeMap.get(steps[i])!;
    const topLevel = root.level;
    let treeWidth = rootBound.width;
    let treeHeight = rootBound.height;
    levelHeights[topLevel] = Math.max(levelHeights[topLevel], treeHeight);

    if (child.length) {
      const bottomLevel = child[child.length - 1]?.level;
      for (let i = topLevel; i <= bottomLevel; i++) {
        let width = 0;
        let height = 0;
        for (let j = 0; j < child.length; j++) {
          if (child[j].level === i) {
            const childTreeSize = treeSizeMap.get(child[j])!;
            width += childTreeSize.width;
            height = Math.max(childTreeSize.height + rootBound.height, height);
          }
        }
        treeWidth = Math.max(treeWidth, width);
        treeHeight = Math.max(treeHeight, height);
      }
    }

    treeSizeMap.set(root, { width: treeWidth, height: treeHeight });
  }

  const levelsSteps: NStep[][] = [];
  let level = 0;
  while (true) {
    const levelSteps = steps.filter((step) => step.level === level);
    if (levelSteps.length === 0) {
      break;
    } else {
      levelsSteps[level] = levelSteps;
    }
    level++;
  }

  const getStepOffset = (step: NStep) => {
    const stepBound = stepSizeMap.get(step)!;
    const treeBound = treeSizeMap.get(step)!;

    let offset = 0;
    let prevId = step.prevId;
    while (prevId !== -1) {
      const prevStep = steps.find((tstep) => tstep.id === prevId)!;
      const treeBound = treeSizeMap.get(prevStep)!;
      offset += treeBound.width;
      prevId = prevStep.prevId;
    }
    const prevStep = steps.find((tstep) => tstep.id === step.prevId)!;
    if (prevStep) {
      offset = Math.max(prevStep.bound.left + prevStep.bound.width, offset);
    }

    let left = offset + (treeBound.width - stepBound.width) / 2;

    // 如果超出预设范围则修正
    if (step.parentIds.length === 1 && step.parentIds[0] !== -1) {
      if (step.parentIds[0] !== -1) {
        const parent = steps.find((pstep) => pstep.id === step.parentIds[0])!;
        const paretnBound = treeSizeMap.get(parent)!;
        if (parent.bound.left - left > paretnBound.width / 2) {
          // const width = stepBound.width;
          const width = steps
            .filter((step) => step.parentIds.includes(parent.id))
            .reduce((t, c) => t + stepSizeMap.get(c)!.width, 0);

          left = Math.max(
            left,
            parent.bound.left + (parent.bound.width - width) / 2
          );
        }
      }
    }

    return left;
  };

  let top = 0;
  let width = 0;
  for (let i = 0; i < levelsSteps.length; i++) {
    let levelHeight = levelHeights[i];
    for (const step of levelsSteps[i]) {
      const stepSize = stepSizeMap.get(step)!;
      step.bound = {
        left: getStepOffset(step),
        top: top + (levelHeight - stepSize.height) / 2,
        ...stepSize,
      };

      width = Math.max(step.bound.left + step.bound.width, width);
    }
    top += levelHeight;
  }

  const getParentBound = (step: NStep) => {
    const parentSteps = step.parentIds.map(
      (id) => steps.find((parentStep) => parentStep.id === id)!
    );
    let left = 0;
    let top = 0;
    let width = 0,
      height = 0;
    for (const step of parentSteps) {
      left = Math.min(step.bound.left, left);
      top = Math.min(step.bound.top, top);
      width += step.bound.width;
      height += step.bound.height;
    }
    return {
      width,
      height,
      left,
      top,
    };
  };
  const getCompleteTree = (root: NStep) => {
    const completeTree = [root];
    const completeTreeIds = [root.id];
    const levelsChilds = levelsSteps.slice(root.level);
    for (const childs of levelsChilds) {
      for (const step of childs) {
        if (step.parentIds.some((id) => completeTreeIds.includes(id))) {
          completeTree.push(step);
          completeTreeIds.push(step.id);
        }
      }
    }
    return completeTree;
  };
  // 偏移所有多父级树
  for (let i = 0; i < levelsSteps.length; i++) {
    for (const step of levelsSteps[i]) {
      if (step.parentIds.length <= 1) continue;
      const parentBound = getParentBound(step);
      const offset = (parentBound.width - treeSizeMap.get(step)!.width) / 2;
      getCompleteTree(step).forEach((ctStep) => {
        ctStep.bound.left += offset;
      });
    }
  }

  let left = 0,
    right = 0;
  for (let i = 0; i < steps.length; i++) {
    left = Math.min(steps[i].bound.left, left);
    right = Math.max(steps[i].bound.left + steps[i].bound.width, right);
  }
  return { left, right, top: 0, bottom: top, levelHeights };
};

export const flatSteps = (data: any) => {
  const nsteps: NStep[] = [];
  _flatSteps(data, [], 0, false, nsteps);
  return nsteps;
};

const ctx = document.createElement("canvas").getContext("2d")!;
export const getTextBound = (
  text: string,
  padding: number[],
  margin: number[],
  font: string
) => {
  ctx.font = font;
  const textMetrics = ctx.measureText(text);
  const width = textMetrics.width + (padding[1] + margin[1]) * 2;
  const height = textMetrics.hangingBaseline + (padding[0] + margin[0]) * 2;

  return { width, height };
};
