import { getStepLine, Step, Steps } from "./tree-helper";
import {
  getStepsTreeCtx as getStepsTreeCtxRaw,
  StepsCtx as StepsCtxRaw,
} from "./tree-helper";

type DataStep = {
  serviceTypeParallel?: boolean | "True" | "Flase";
  subStepsParallel?: boolean | "True" | "Flase";
  structure?: string;
};
export type DataStepTree<T extends DataStep = DataStep> = T & {
  steps: DataStepTree<T>[];
};

export type NStep<T extends DataStepTree> = Step<T>;

const _flatSteps = <T extends DataStep>(
  steps: DataStepTree<T>[],
  nsteps: NStep<DataStepTree<T>>[] = [],
  parents: NStep<DataStepTree<T>>[] = [],
  parallel = false
) => {
  const lonelySteps: NStep<DataStepTree<T>>[] = [];
  let tempParents = parents;

  for (const step of steps) {
    const stepParallel = parallel;
    if (!stepParallel && lonelySteps.length) {
      tempParents = [...lonelySteps];
      lonelySteps.length = 0;
    }
    const nstep = {
      raw: step,
      children: [],
      parents: tempParents,
    } as NStep<DataStepTree<T>>;
    nsteps.push(nstep);
    tempParents.forEach((parent) => parent.children.push(nstep));

    if (step.steps && step.steps.length) {
      step.serviceTypeParallel;
      lonelySteps.push(
        ..._flatSteps(
          step.steps,
          nsteps,
          [nstep],
          step.subStepsParallel === "True" ||
            step.serviceTypeParallel === "True" ||
            !!step.subStepsParallel ||
            !!step.serviceTypeParallel
        )
      );
    } else {
      lonelySteps.push(nstep);
    }
  }

  return lonelySteps;
};

const start: any = { displayName: "开始", type: "startEnd" };
const end: any = { displayName: "结束", type: "startEnd" };
export const flatSteps = <T extends DataStep>(steps: DataStepTree<T>[]) => {
  const nsteps: NStep<DataStepTree<T>>[] = [];
  for (let i = 0; i < steps.length; i++) {
    // steps[i].steps = [
    //   {
    //     ...steps[i],
    //     subStepsParallel: false,
    //     structure: "right",
    //   },
    // ];
    steps[i].structure = "left";
  }

  _flatSteps([start, ...steps, end] as any, nsteps);
  return {
    steps: nsteps,
    group: steps,
  };
};

// 获取step所有子级
const getStepFlatSteps = <T extends DataStep>(step: DataStepTree<T>) => {
  if (!step.steps || step.steps.length === 0) return [];

  const children: DataStepTree<T>[] = [];
  for (const child of step.steps) {
    children.push(child);
    children.push(...getStepFlatSteps(child));
  }

  return children;
};

// 获取steps组成的bound
const getStepsBound = <T>(nsteps: NStep<DataStepTree<T>>[]) => {
  const bounds = nsteps.map((nstep) => {
    return { ...nstep.box.offset, ...nstep.box.size };
  });

  let maxX = -Number.MAX_VALUE,
    maxY = -Number.MAX_VALUE,
    minX = Number.MAX_VALUE,
    minY = Number.MAX_VALUE;
  for (const bound of bounds) {
    minX = Math.min(bound.x, minX);
    minY = Math.min(bound.y, minY);
    maxX = Math.max(bound.x + bound.w, maxX);
    maxY = Math.max(bound.y + bound.h, maxY);
  }
  return {
    x: minX,
    y: minY,
    w: maxX - minX,
    h: maxY - minY,
  };
};

export type StepsCtx<T> = StepsCtxRaw<T> & {
  groupBoxs: {
    step: Step<T>;
    bound: { w: number; h: number; x: number; y: number };
    line: number[][];
  }[];
};

const setGroupBack = <T>(
  steps: NStep<DataStepTree<T>>[],
  ctx: StepsCtx<DataStepTree<T>>,
  groups: NStep<DataStepTree<T>>[]
) => {
  const maxWidth = Math.max(...groups.map(({ box }) => box.size.w));
  for (const groupStep of groups) {
    const children = getStepFlatSteps(groupStep.raw);
    const childSteps = children.map((raw) =>
      steps.find((step) => raw === step.raw)
    );
    const bound = getStepsBound(childSteps);
    const padding = 10;
    bound.y -= padding;
    bound.h += 2 * padding;
    ctx.groupBoxs.push({
      step: groupStep,
      line: [],
      bound: bound,
    });
    groupStep.box.offset.x = -maxWidth + (maxWidth - groupStep.box.size.w) / 2;
  }
  ctx.offset.x = -maxWidth;
  ctx.size.w += maxWidth;
};

const levelTraversalSteps = <T>(
  steps: Steps<T>,
  oper: (steps: Step<T>[]) => void,
  reverse = false,
  level = 0
) => {
  const cSteps = steps.filter((item) => item.box.level === level);
  if (cSteps.length === 0) return;
  reverse || oper(cSteps);
  levelTraversalSteps(steps, oper, reverse, level + 1);
  reverse && oper(cSteps);
};

const setGroupOffset = <T>(
  steps: NStep<DataStepTree<T>>[],
  ctx: StepsCtx<DataStepTree<T>>,
  groupSteps: NStep<DataStepTree<T>>[],
  margin: number
) => {
  // margin = 0;
  const offsetYs: number[] = [];
  const offsetLYs: number[] = [];
  for (let i = 0; i < groupSteps.length; i++) {
    const groupStep = groupSteps[i];
    if (start === groupStep.raw) {
      offsetYs[i] = 0;
      offsetLYs[i] = 0;
      // offsetLYs[i] = -groupStep.box.size.h + margin;
    } else if (end === groupStep.raw) {
      offsetYs[i] = offsetYs[i - 1];
      offsetLYs[i] = offsetLYs[i - 1];
    } else if (i > 0) {
      offsetYs[i] = -groupStep.box.size.h + offsetYs[i - 1] + margin;
      offsetLYs[i] = offsetLYs[i - 1] - groupStep.box.size.h + margin;
    } else {
      offsetYs[i] = -groupStep.box.size.h + margin;
      offsetLYs[i] = -groupStep.box.size.h + margin;
    }

    groupStep.box.offset.y += margin;
  }

  let offsetNdx = offsetYs.length - 1;
  let offsetLNdx = offsetYs.length - 1;
  let prevG = null;

  levelTraversalSteps(
    steps,
    (currents) => {
      const isBorder = currents.some((current) => groupSteps.includes(current));
      if (isBorder) {
        offsetNdx -= 1;
      }

      for (const current of currents) {
        if (end === prevG || offsetNdx <= 0) {
          current.box.lines = [];
        }
        if (offsetNdx === -1) {
          break;
        }
        current.box.offset.y += offsetYs[offsetNdx];
        for (const points of current.box.lines) {
          for (const point of points) {
            point[1] = point[1] + offsetLYs[offsetLNdx];
          }
        }
      }
      prevG = currents[0].raw;
      if (isBorder) {
        offsetLNdx -= 1;
      }
    },
    true
  );

  ctx.size.h += offsetYs[offsetYs.length - 1];
};

export const setGroupLine = <T>(
  ctx: StepsCtx<DataStepTree<T>>,
  groupSteps: NStep<DataStepTree<T>>[],
  margin: number[]
) => {
  for (let i = 0; i < groupSteps.length - 1; i++) {
    ctx.groupBoxs[i].line = getStepLine(
      ctx,
      groupSteps[i],
      groupSteps[i + 1],
      margin
    );
  }
};

export const getStepsTreeCtx = <T extends DataStep>(
  steps: NStep<DataStepTree<T>>[],
  margin: number[],
  getStepSize: (step: T) => { w: number; h: number },
  groups: DataStepTree<T>[]
) => {
  const ctx = getStepsTreeCtxRaw(steps, margin, getStepSize) as StepsCtx<
    DataStepTree<T>
  >;
  console.log(steps, groups);
  groups = [start, ...groups, end];
  const groupSteps = groups.map((group) =>
    steps.find((step) => group === step.raw)
  );
  ctx.groupBoxs = [];
  setGroupOffset(steps, ctx, groupSteps, margin[0]);
  setGroupBack(steps, ctx, groupSteps);
  setGroupLine(ctx, groupSteps, margin);
  return ctx;
};
