import { ORIG_ID } from "@excalidraw/common";

import { Excalidraw } from "../index";
import { API } from "../tests/helpers/api";
import {
  act,
  assertElements,
  getCloneByOrigId,
  render,
} from "../tests/test-utils";

import { actionDuplicateSelection } from "./actionDuplicateSelection";

const { h } = window;

describe("actionDuplicateSelection", () => {
  beforeEach(async () => {
    await render(<Excalidraw />);
  });

  describe("duplicating frames", () => {
    it("frame selected only", async () => {
      const frame = API.createElement({
        type: "frame",
      });

      const rectangle = API.createElement({
        type: "rectangle",
        frameId: frame.id,
      });

      API.setElements([frame, rectangle]);
      API.setSelectedElements([frame]);

      act(() => {
        h.app.actionManager.executeAction(actionDuplicateSelection);
      });

      assertElements(h.elements, [
        { id: frame.id },
        { id: rectangle.id, frameId: frame.id },
        { [ORIG_ID]: rectangle.id, frameId: getCloneByOrigId(frame.id)?.id },
        { [ORIG_ID]: frame.id, selected: true },
      ]);
    });

    it("frame selected only (with text container)", async () => {
      const frame = API.createElement({
        type: "frame",
      });

      const [rectangle, text] = API.createTextContainer({ frameId: frame.id });

      API.setElements([frame, rectangle, text]);
      API.setSelectedElements([frame]);

      act(() => {
        h.app.actionManager.executeAction(actionDuplicateSelection);
      });

      assertElements(h.elements, [
        { id: frame.id },
        { id: rectangle.id, frameId: frame.id },
        { id: text.id, containerId: rectangle.id, frameId: frame.id },
        { [ORIG_ID]: rectangle.id, frameId: getCloneByOrigId(frame.id)?.id },
        {
          [ORIG_ID]: text.id,
          containerId: getCloneByOrigId(rectangle.id)?.id,
          frameId: getCloneByOrigId(frame.id)?.id,
        },
        { [ORIG_ID]: frame.id, selected: true },
      ]);
    });

    it("frame + text container selected (order A)", async () => {
      const frame = API.createElement({
        type: "frame",
      });

      const [rectangle, text] = API.createTextContainer({ frameId: frame.id });

      API.setElements([frame, rectangle, text]);
      API.setSelectedElements([frame, rectangle]);

      act(() => {
        h.app.actionManager.executeAction(actionDuplicateSelection);
      });

      assertElements(h.elements, [
        { id: frame.id },
        { id: rectangle.id, frameId: frame.id },
        { id: text.id, containerId: rectangle.id, frameId: frame.id },
        {
          [ORIG_ID]: rectangle.id,
          frameId: getCloneByOrigId(frame.id)?.id,
        },
        {
          [ORIG_ID]: text.id,
          containerId: getCloneByOrigId(rectangle.id)?.id,
          frameId: getCloneByOrigId(frame.id)?.id,
        },
        {
          [ORIG_ID]: frame.id,
          selected: true,
        },
      ]);
    });

    it("frame + text container selected (order B)", async () => {
      const frame = API.createElement({
        type: "frame",
      });

      const [rectangle, text] = API.createTextContainer({ frameId: frame.id });

      API.setElements([text, rectangle, frame]);
      API.setSelectedElements([rectangle, frame]);

      act(() => {
        h.app.actionManager.executeAction(actionDuplicateSelection);
      });

      assertElements(h.elements, [
        { id: rectangle.id, frameId: frame.id },
        { id: text.id, containerId: rectangle.id, frameId: frame.id },
        { id: frame.id },
        {
          type: "rectangle",
          [ORIG_ID]: `${rectangle.id}`,
        },
        {
          [ORIG_ID]: `${text.id}`,
          type: "text",
          containerId: getCloneByOrigId(rectangle.id)?.id,
          frameId: getCloneByOrigId(frame.id)?.id,
        },
        { [ORIG_ID]: `${frame.id}`, type: "frame", selected: true },
      ]);
    });
  });

  describe("duplicating frame children", () => {
    it("frame child selected", () => {
      const frame = API.createElement({
        type: "frame",
      });

      const rectangle = API.createElement({
        type: "rectangle",
        frameId: frame.id,
      });

      API.setElements([frame, rectangle]);
      API.setSelectedElements([rectangle]);

      act(() => {
        h.app.actionManager.executeAction(actionDuplicateSelection);
      });

      assertElements(h.elements, [
        { id: frame.id },
        { id: rectangle.id, frameId: frame.id },
        { [ORIG_ID]: rectangle.id, frameId: frame.id, selected: true },
      ]);
    });

    it("frame text container selected (rectangle selected)", () => {
      const frame = API.createElement({
        type: "frame",
      });

      const [rectangle, text] = API.createTextContainer({ frameId: frame.id });

      API.setElements([frame, rectangle, text]);
      API.setSelectedElements([rectangle]);

      act(() => {
        h.app.actionManager.executeAction(actionDuplicateSelection);
      });

      assertElements(h.elements, [
        { id: frame.id },
        { id: rectangle.id, frameId: frame.id },
        { id: text.id, containerId: rectangle.id, frameId: frame.id },
        { [ORIG_ID]: rectangle.id, frameId: frame.id, selected: true },
        {
          [ORIG_ID]: text.id,
          containerId: getCloneByOrigId(rectangle.id).id,
          frameId: frame.id,
        },
      ]);
    });

    it("frame bound text selected (container not selected)", () => {
      const frame = API.createElement({
        type: "frame",
      });

      const [rectangle, text] = API.createTextContainer({ frameId: frame.id });

      API.setElements([frame, rectangle, text]);
      API.setSelectedElements([text]);

      act(() => {
        h.app.actionManager.executeAction(actionDuplicateSelection);
      });

      assertElements(h.elements, [
        { id: frame.id },
        { id: rectangle.id, frameId: frame.id },
        { id: text.id, containerId: rectangle.id, frameId: frame.id },
        { [ORIG_ID]: rectangle.id, frameId: frame.id, selected: true },
        {
          [ORIG_ID]: text.id,
          containerId: getCloneByOrigId(rectangle.id).id,
          frameId: frame.id,
        },
      ]);
    });

    it("frame text container selected (text not exists)", () => {
      const frame = API.createElement({
        type: "frame",
      });

      const [rectangle] = API.createTextContainer({ frameId: frame.id });

      API.setElements([frame, rectangle]);
      API.setSelectedElements([rectangle]);

      act(() => {
        h.app.actionManager.executeAction(actionDuplicateSelection);
      });

      assertElements(h.elements, [
        { id: frame.id },
        { id: rectangle.id, frameId: frame.id },
        { [ORIG_ID]: rectangle.id, frameId: frame.id, selected: true },
      ]);
    });

    // shouldn't happen
    it("frame bound text selected (container not exists)", () => {
      const frame = API.createElement({
        type: "frame",
      });

      const [, text] = API.createTextContainer({ frameId: frame.id });

      API.setElements([frame, text]);
      API.setSelectedElements([text]);

      act(() => {
        h.app.actionManager.executeAction(actionDuplicateSelection);
      });

      assertElements(h.elements, [
        { id: frame.id },
        { id: text.id, frameId: frame.id },
        { [ORIG_ID]: text.id, frameId: frame.id, selected: true },
      ]);
    });

    it("frame bound container selected (text has no frameId)", () => {
      const frame = API.createElement({
        type: "frame",
      });

      const [rectangle, text] = API.createTextContainer({
        frameId: frame.id,
        label: { frameId: null },
      });

      API.setElements([frame, rectangle, text]);
      API.setSelectedElements([rectangle]);

      act(() => {
        h.app.actionManager.executeAction(actionDuplicateSelection);
      });

      assertElements(h.elements, [
        { id: frame.id },
        { id: rectangle.id, frameId: frame.id },
        { id: text.id, containerId: rectangle.id },
        { [ORIG_ID]: rectangle.id, frameId: frame.id, selected: true },
        {
          [ORIG_ID]: text.id,
          containerId: getCloneByOrigId(rectangle.id).id,
        },
      ]);
    });
  });

  describe("duplicating multiple frames", () => {
    it("multiple frames selected (no children)", () => {
      const frame1 = API.createElement({
        type: "frame",
      });

      const rect1 = API.createElement({
        type: "rectangle",
        frameId: frame1.id,
      });

      const frame2 = API.createElement({
        type: "frame",
      });

      const rect2 = API.createElement({
        type: "rectangle",
        frameId: frame2.id,
      });

      const ellipse = API.createElement({
        type: "ellipse",
      });

      API.setElements([rect1, frame1, ellipse, rect2, frame2]);
      API.setSelectedElements([frame1, frame2]);

      act(() => {
        h.app.actionManager.executeAction(actionDuplicateSelection);
      });

      assertElements(h.elements, [
        { id: rect1.id, frameId: frame1.id },
        { id: frame1.id },
        { [ORIG_ID]: rect1.id, frameId: getCloneByOrigId(frame1.id)?.id },
        { [ORIG_ID]: frame1.id, selected: true },
        { id: ellipse.id },
        { id: rect2.id, frameId: frame2.id },
        { id: frame2.id },
        { [ORIG_ID]: rect2.id, frameId: getCloneByOrigId(frame2.id)?.id },
        { [ORIG_ID]: frame2.id, selected: true },
      ]);
    });

    it("multiple frames selected (no children) + unrelated element", () => {
      const frame1 = API.createElement({
        type: "frame",
      });

      const rect1 = API.createElement({
        type: "rectangle",
        frameId: frame1.id,
      });

      const frame2 = API.createElement({
        type: "frame",
      });

      const rect2 = API.createElement({
        type: "rectangle",
        frameId: frame2.id,
      });

      const ellipse = API.createElement({
        type: "ellipse",
      });

      API.setElements([rect1, frame1, ellipse, rect2, frame2]);
      API.setSelectedElements([frame1, ellipse, frame2]);

      act(() => {
        h.app.actionManager.executeAction(actionDuplicateSelection);
      });

      assertElements(h.elements, [
        { id: rect1.id, frameId: frame1.id },
        { id: frame1.id },
        { [ORIG_ID]: rect1.id, frameId: getCloneByOrigId(frame1.id)?.id },
        { [ORIG_ID]: frame1.id, selected: true },
        { id: ellipse.id },
        { [ORIG_ID]: ellipse.id, selected: true },
        { id: rect2.id, frameId: frame2.id },
        { id: frame2.id },
        { [ORIG_ID]: rect2.id, frameId: getCloneByOrigId(frame2.id)?.id },
        { [ORIG_ID]: frame2.id, selected: true },
      ]);
    });
  });

  describe("duplicating containers/bound elements", () => {
    it("labeled arrow (arrow selected)", () => {
      const [arrow, text] = API.createLabeledArrow();

      API.setElements([arrow, text]);
      API.setSelectedElements([arrow]);

      act(() => {
        h.app.actionManager.executeAction(actionDuplicateSelection);
      });

      assertElements(h.elements, [
        { id: arrow.id },
        { id: text.id, containerId: arrow.id },
        { [ORIG_ID]: arrow.id, selected: true },
        { [ORIG_ID]: text.id, containerId: getCloneByOrigId(arrow.id)?.id },
      ]);
    });

    // shouldn't happen
    it("labeled arrow (text selected)", () => {
      const [arrow, text] = API.createLabeledArrow();

      API.setElements([arrow, text]);
      API.setSelectedElements([text]);

      act(() => {
        h.app.actionManager.executeAction(actionDuplicateSelection);
      });

      assertElements(h.elements, [
        { id: arrow.id },
        { id: text.id, containerId: arrow.id },
        { [ORIG_ID]: arrow.id, selected: true },
        { [ORIG_ID]: text.id, containerId: getCloneByOrigId(arrow.id)?.id },
      ]);
    });
  });

  describe("duplicating groups", () => {
    it("duplicate group containing frame (children don't have groupIds set)", () => {
      const frame = API.createElement({
        type: "frame",
        groupIds: ["A"],
      });

      const [rectangle, text] = API.createTextContainer({
        frameId: frame.id,
      });

      const ellipse = API.createElement({
        type: "ellipse",
        groupIds: ["A"],
      });

      API.setElements([rectangle, text, frame, ellipse]);
      API.setSelectedElements([frame, ellipse]);

      act(() => {
        h.app.actionManager.executeAction(actionDuplicateSelection);
      });

      assertElements(h.elements, [
        { id: rectangle.id, frameId: frame.id },
        { id: text.id, frameId: frame.id },
        { id: frame.id },
        { id: ellipse.id },
        { [ORIG_ID]: rectangle.id, frameId: getCloneByOrigId(frame.id)?.id },
        { [ORIG_ID]: text.id, frameId: getCloneByOrigId(frame.id)?.id },
        { [ORIG_ID]: frame.id, selected: true },
        { [ORIG_ID]: ellipse.id, selected: true },
      ]);
    });

    it("duplicate group containing frame (children have groupIds)", () => {
      const frame = API.createElement({
        type: "frame",
        groupIds: ["A"],
      });

      const [rectangle, text] = API.createTextContainer({
        frameId: frame.id,
        groupIds: ["A"],
      });

      const ellipse = API.createElement({
        type: "ellipse",
        groupIds: ["A"],
      });

      API.setElements([rectangle, text, frame, ellipse]);
      API.setSelectedElements([frame, ellipse]);

      act(() => {
        h.app.actionManager.executeAction(actionDuplicateSelection);
      });

      assertElements(h.elements, [
        { id: rectangle.id, frameId: frame.id },
        { id: text.id, frameId: frame.id },
        { id: frame.id },
        { id: ellipse.id },
        {
          [ORIG_ID]: rectangle.id,
          frameId: getCloneByOrigId(frame.id)?.id,
          // FIXME shouldn't be selected (in selectGroupsForSelectedElements)
          selected: true,
        },
        {
          [ORIG_ID]: text.id,
          frameId: getCloneByOrigId(frame.id)?.id,
          // FIXME shouldn't be selected (in selectGroupsForSelectedElements)
          selected: true,
        },
        { [ORIG_ID]: frame.id, selected: true },
        { [ORIG_ID]: ellipse.id, selected: true },
      ]);
    });

    it("duplicating element nested in group", () => {
      const ellipse = API.createElement({
        type: "ellipse",
        groupIds: ["B"],
      });
      const rect1 = API.createElement({
        type: "rectangle",
        groupIds: ["A", "B"],
      });
      const rect2 = API.createElement({
        type: "rectangle",
        groupIds: ["A", "B"],
      });

      API.setElements([ellipse, rect1, rect2]);
      API.setSelectedElements([ellipse], "B");

      act(() => {
        h.app.actionManager.executeAction(actionDuplicateSelection);
      });

      assertElements(h.elements, [
        { id: ellipse.id },
        { [ORIG_ID]: ellipse.id, groupIds: ["B"], selected: true },
        { id: rect1.id, groupIds: ["A", "B"] },
        { id: rect2.id, groupIds: ["A", "B"] },
      ]);
    });
  });
});
