import { AnyTypedQuizQuestion, QuestionBranchingRule, QuestionBranchingRuleMain } from "@frontend/squzanswerer";
import { QuizQuestionResult } from "@frontend/squzanswerer";
import { UntypedQuizQuestion } from "@model/questionTypes/shared";
import { createResult, getQuestionByContentId, updateQuestion } from "@root/questions/actions";
import { useQuestionsStore } from "@root/questions/store";
import { useQuizStore } from "@root/quizes/store";
import { updateOpenedModalSettingsId } from "@root/uiTools/actions";
import { useUiTools } from "@root/uiTools/store";
import { NodeSingular, PresetLayoutOptions } from "cytoscape";
import { enqueueSnackbar } from "notistack";

export interface Node {
  data: {
    isRoot: boolean;
    id: string;
    label: string;
    qtype: string;
    type: string;
    parent?: string;
    children: number;
  };
  classes: string;
}

export interface Edge {
  data: {
    source: string;
    target: string;
  };
}

export function isElementANode(element: Node | Edge): element is Node {
  return !("source" in element.data && "target" in element.data);
}

export function isNodeInViewport(node: NodeSingular, padding: number = 0) {
  const extent = node.cy().extent();
  const bb = node.boundingBox();

  return (
    bb.x2 > extent.x1 - padding &&
    bb.x1 < extent.x2 + padding &&
    bb.y2 > extent.y1 - padding &&
    bb.y1 < extent.y2 + padding
  );
}

export const storeToNodes = (questions: AnyTypedQuizQuestion[]) => {
  const nodes: Node[] = [];
  const edges: Edge[] = [];
  questions.forEach((question) => {
    if (question.content.rule.parentId) {
      const parentQuestion = {
        ...getQuestionByContentId(question.content.rule.parentId),
      } as AnyTypedQuizQuestion;
      let label = question.title === "" || question.title === " " ? "noname" : question.title;
      label = label.trim().replace(/\s+/g, " ");
      if (label.length > 20) label = label.slice(0, 20).toLowerCase() + "…";

      nodes.push({
        data: {
          isRoot: question.content.rule.parentId === "root",
          id: question.content.id,
          label,
          qtype: question.content.rule.parentId === "root" ? "root" : parentQuestion.type,
          type: question.type,
          children: question.content.rule.children.length,
        },
        classes: "multiline-auto",
      });
      if (question.content.rule.parentId !== "root")
        edges.push({
          data: {
            source: question.content.rule.parentId,
            target: question.content.id,
          },
        });
    }
  });
  return [...nodes, ...edges];
};

export const layoutOptions: PresetLayoutOptions = {
  name: "preset",
  positions: calcNodePosition,
  zoom: undefined,
  pan: 1,
  fit: false,
  padding: 30,
  animate: false,
  animationDuration: 500,
  animationEasing: undefined,
  animateFilter: () => false,
  ready: (event) => {
    if (event.cy.data("firstNode") === "nonroot") {
      event.cy.data("firstNode", "root");
      event.cy.nodes().sort((a, b) => (a.data("root") ? 1 : -1));
    } else {
      event.cy.removeData("firstNode");
    }
  },
  transform: (_, p) => p,
};

export function clearDataAfterAddNode({
  parentNodeContentId,
  targetQuestion,
}: {
  parentNodeContentId: string;
  targetQuestion: AnyTypedQuizQuestion;
}) {
  const parentQuestion = {
    ...getQuestionByContentId(parentNodeContentId),
  } as AnyTypedQuizQuestion;

  //смотрим не добавлен ли родителю result. Если да - делаем его неактивным. Веточкам result не нужен
  useQuestionsStore
    .getState()
    .questions.filter((question): question is QuizQuestionResult => question.type === "result")
    .forEach((targetQuestion) => {
      if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {
        updateQuestion<QuizQuestionResult>(targetQuestion.id, (q) => (q.content.usage = false));
      }
    });

  //предупреждаем добавленный вопрос о том, кто его родитель
  updateQuestion(targetQuestion.content.id, (question) => {
    question.content.rule.parentId = parentNodeContentId;
    question.content.rule.main = [];
    //Это листик. Сбросим ему на всякий случай не листиковые поля
    question.content.rule.children = [];
    question.content.rule.default = "";
  });

  const noChild = parentQuestion.content.rule.children.length === 0;

  //предупреждаем родителя о новом потомке (если он ещё не знает о нём)
  if (!parentQuestion.content.rule.children.includes(targetQuestion.content.id))
    updateQuestion(parentNodeContentId, (question) => {
      question.content.rule.children = [...question.content.rule.children, targetQuestion.content.id];
      //единственному ребёнку даём дефолт по-умолчанию
      question.content.rule.default = noChild ? targetQuestion.content.id : question.content.rule.default;
    });

  if (!noChild) {
    //детей больше 1
    //- предупреждаем стор вопросов об открытии модалки ветвления
    updateOpenedModalSettingsId(targetQuestion.content.id);
  }
}

export function clearDataAfterRemoveNode({
  trashQuestions,
  targetQuestionContentId,
  parentQuestionContentId,
}: {
  trashQuestions: (AnyTypedQuizQuestion | UntypedQuizQuestion)[];
  targetQuestionContentId: string;
  parentQuestionContentId: string;
}) {
  updateQuestion(targetQuestionContentId, (question) => {
    question.content.rule.parentId = "";
    question.content.rule.children = [];
    question.content.rule.main = [];
    question.content.rule.default = "";
  });

  //Ищём родителя
  const parentQuestion = getQuestionByContentId(parentQuestionContentId);

  //Делаем результат родителя активным
  const parentResult = trashQuestions.find(
    (q): q is QuizQuestionResult => q.type === "result" && q.content.rule.parentId === parentQuestionContentId
  );
  if (parentResult) {
    updateQuestion<QuizQuestionResult>(parentResult.content.id, (q) => {
      q.content.usage = true;
    });
  } else {
    createResult(useQuizStore.getState().editQuizId, parentQuestionContentId);
  }

  //чистим rule родителя
  if (!parentQuestion?.type) {
    return;
  }

  const newChildren = [...parentQuestion.content.rule.children];
  newChildren.splice(parentQuestion.content.rule.children.indexOf(targetQuestionContentId), 1);

  const newRule: QuestionBranchingRule = {
    children: newChildren,
    default: parentQuestion.content.rule.default === targetQuestionContentId ? "" : parentQuestion.content.rule.default,
    //удаляем условия перехода от родителя к этому вопросу,
    main: parentQuestion.content.rule.main.filter(
      (data: QuestionBranchingRuleMain) => data.next !== targetQuestionContentId
    ),
    parentId: parentQuestion.content.rule.parentId,
  };

  updateQuestion(parentQuestionContentId, (PQ) => {
    PQ.content.rule = newRule;
  });
}

export function calcNodePosition(node: any) {
  const id = node.id();
  const incomming = node.cy().edges(`[target="${id}"]`);
  const layer = 0;
  node.removeData("lastChild");

  if (incomming.length === 0) {
    if (node.cy().data("firstNode") === undefined) node.cy().data("firstNode", "root");
    node.data("root", true);
    const children = node.cy().edges(`[source="${id}"]`).targets();
    node.data("layer", layer);
    node.data("children", children.length);
    const queue: any[] = [];
    children.forEach((n: any) => {
      queue.push({ task: n, layer: layer + 1 });
    });
    while (queue.length) {
      const task = queue.pop();
      task.task.data("layer", task.layer);
      task.task.removeData("subtreeWidth");
      const children = node.cy().edges(`[source="${task.task.id()}"]`).targets();
      task.task.data("children", children.length);
      if (children.length !== 0) {
        children.forEach((n: any) => queue.push({ task: n, layer: task.layer + 1 }));
      }
    }
    queue.push({ parent: node, children: children });
    while (queue.length) {
      const task = queue.pop();
      if (task.children.length === 0) {
        task.parent.data("subtreeWidth", task.parent.height() + 50);
        continue;
      }
      const unprocessed = task?.children.filter((node: any) => {
        return node.data("subtreeWidth") === undefined;
      });
      if (unprocessed.length !== 0) {
        queue.push(task);
        unprocessed.forEach((t: any) => {
          queue.push({
            parent: t,
            children: t.cy().edges(`[source="${t.id()}"]`).targets(),
          });
        });
        continue;
      }

      task?.parent.data(
        "subtreeWidth",
        task.children.reduce((p: any, n: any) => p + n.data("subtreeWidth"), 0)
      );
    }

    const pos = { x: 0, y: 0 };
    node.data("oldPos", pos);

    queue.push({ task: children, parent: node });
    while (queue.length) {
      const task = queue.pop();
      const oldPos = task.parent.data("oldPos");
      let yoffset = oldPos.y - task.parent.data("subtreeWidth") / 2;
      task.task.forEach((n: any) => {
        const width = n.data("subtreeWidth");

        n.data("oldPos", {
          x: 350 * n.data("layer"),
          y: yoffset + width / 2,
        });
        yoffset += width;
        queue.push({
          task: n.cy().edges(`[source="${n.id()}"]`).targets(),
          parent: n,
        });
      });
    }
    return pos;
  } else {
    const opos = node.data("oldPos");
    if (opos) {
      return opos;
    }
  }
}

export const canAddChildToQuestion = (parentNodeContentId: string) => {
  const parentQuestion = {
    ...getQuestionByContentId(parentNodeContentId),
  } as AnyTypedQuizQuestion;
  if (
    parentQuestion.type !== undefined &&
    isQuestionProhibited(parentQuestion.type) &&
    parentQuestion.content.rule.children.length > 0
  ) {
    enqueueSnackbar("У вопроса этого типа может быть только 1 потомок");
    return false;
  }
  return true;
};

export const addNode = ({
  parentNodeContentId,
  targetNodeContentId,
}: {
  parentNodeContentId: string;
  targetNodeContentId?: string;
}) => {
  //запрещаем работу родителя-ребенка если это один и тот же вопрос
  if (parentNodeContentId === targetNodeContentId) return;

  //У 4 типов вопросов не может быть больше 1 потомка
  const parentQuestion = {
    ...getQuestionByContentId(parentNodeContentId),
  } as AnyTypedQuizQuestion;
  if (
    parentQuestion.type !== undefined &&
    isQuestionProhibited(parentQuestion.type) &&
    parentQuestion.content.rule.children.length > 0
  ) {
    enqueueSnackbar("у вопроса этого типа может быть только 1 потомок");
    return;
  }

  //если есть инфо о выбранном вопросе из модалки - берём родителя из инфо модалки. Иначе из значения дропа
  const targetQuestion = {
    ...getQuestionByContentId(targetNodeContentId || useUiTools.getState().dragQuestionContentId),
  } as AnyTypedQuizQuestion;

  if (Object.keys(targetQuestion).length !== 0 && parentNodeContentId) {
    clearDataAfterAddNode({ parentNodeContentId, targetQuestion });
    createResult(useQuizStore.getState().editQuizId, targetQuestion.content.id);
  } else {
    enqueueSnackbar("Добавляемый вопрос не найден. Перетащите вопрос из списка");
  }
};

export const isQuestionProhibited = (parentQType: string) =>
  parentQType === "text" || parentQType === "date" || parentQType === "number" || parentQType === "page";
