import { CarouselItemData, CommandResult, Model, UID } from "../../../src";
import { toZone } from "../../../src/helpers/zones";
import { toChartDataSource } from "../../test_helpers/chart_helpers";
import {
  addChartFigureToCarousel,
  addColumns,
  addNewChartToCarousel,
  addRows,
  createCarousel,
  createCarouselWithDataView,
  createChart,
  duplicateSheet,
  popOutChartFromCarousel,
  selectCarouselItem,
  undo,
  updateCarousel,
  updateChart,
} from "../../test_helpers/commands_helpers";
import { TEST_CHART_DATA } from "../../test_helpers/constants";
import { toRangeData } from "../../test_helpers/helpers";

let model: Model;
let sheetId: UID;

beforeEach(() => {
  model = new Model();
  sheetId = model.getters.getActiveSheetId();
});

describe("Carousel figure", () => {
  describe("dispatch results", () => {
    test("Cannot create carousel with duplicated figure id", () => {
      createChart(model, { type: "bar" }, "chartId", undefined, { figureId: "figureId" });
      const result = createCarousel(model, { items: [] }, "figureId");
      expect(result).toBeCancelledBecause(CommandResult.DuplicatedFigureId);
    });

    test("Cannot update carousel with invalid figure id", () => {
      const result = model.dispatch("UPDATE_CAROUSEL", {
        figureId: "carouselId",
        sheetId,
        definition: { items: [] },
      });
      expect(result).toBeCancelledBecause(CommandResult.InvalidFigureId);
    });

    test("Cannot add a new chart to a non carousel-figure", () => {
      createChart(model, { type: "bar" }, "chartId", undefined, { figureId: "chartFigureId" });

      const sheetId = model.getters.getActiveSheetId();
      let result = model.dispatch("ADD_NEW_CHART_TO_CAROUSEL", {
        figureId: "invalidId",
        sheetId,
        newChartId: "chartId",
        chartDefinition: TEST_CHART_DATA.combo,
      });
      expect(result).toBeCancelledBecause(CommandResult.InvalidFigureId);

      result = model.dispatch("ADD_NEW_CHART_TO_CAROUSEL", {
        figureId: "chartFigureId",
        sheetId,
        newChartId: "chartId",
        chartDefinition: TEST_CHART_DATA.combo,
      });
      expect(result).toBeCancelledBecause(CommandResult.InvalidFigureId);
    });

    test("Cannot add an existing chart to a non-carousel figure", () => {
      createChart(model, { type: "bar" }, "chartId", undefined, { figureId: "chartFigureId" });
      createChart(model, { type: "bar" }, "chartId2", undefined, { figureId: "otherFigureId" });
      createCarousel(model, { items: [] }, "carouselId");
      createCarousel(model, { items: [] }, "otherCarouselId");

      let result = addChartFigureToCarousel(model, "chartFigureId", "otherFigureId");
      expect(result).toBeCancelledBecause(CommandResult.InvalidFigureId);

      result = addChartFigureToCarousel(model, "carouselId", "invalidId");
      expect(result).toBeCancelledBecause(CommandResult.InvalidFigureId);

      result = addChartFigureToCarousel(model, "otherCarouselId", "carouselId");
      expect(result).toBeCancelledBecause(CommandResult.InvalidFigureId);

      result = addChartFigureToCarousel(model, "carouselId", "chartFigureId");
      expect(result).toBeSuccessfullyDispatched();
    });

    test("Cannot update the carousel to a wrong state", () => {
      createCarousel(model, { items: [] }, "carouselId");
      const chartId = addNewChartToCarousel(model, "carouselId");

      let result = model.dispatch("UPDATE_CAROUSEL_ACTIVE_ITEM", {
        figureId: "wrongCarouselId",
        sheetId,
        item: { type: "chart", chartId: "invalidChartId" },
      });
      expect(result).toBeCancelledBecause(CommandResult.InvalidFigureId);

      result = model.dispatch("UPDATE_CAROUSEL_ACTIVE_ITEM", {
        figureId: "carouselId",
        sheetId,
        item: { type: "chart", chartId: "invalidChartId" },
      });
      expect(result).toBeCancelledBecause(CommandResult.InvalidCarouselItem);

      result = model.dispatch("UPDATE_CAROUSEL_ACTIVE_ITEM", {
        figureId: "carouselId",
        sheetId,
        item: { type: "carouselDataView" },
      });
      expect(result).toBeCancelledBecause(CommandResult.InvalidCarouselItem);

      result = model.dispatch("UPDATE_CAROUSEL_ACTIVE_ITEM", {
        figureId: "carouselId",
        sheetId,
        item: { type: "chart", chartId },
      });
      expect(result).toBeSuccessfullyDispatched();
    });

    test("Cannot duplicate wrong carousel item", () => {
      createCarousel(model, { items: [] }, "carouselId");
      const chartId = addNewChartToCarousel(model, "carouselId");

      let result = model.dispatch("DUPLICATE_CAROUSEL_CHART", {
        carouselId: "wrongCarouselId",
        sheetId,
        chartId,
        duplicatedChartId: "anyId",
      });
      expect(result).toBeCancelledBecause(CommandResult.InvalidFigureId);

      result = model.dispatch("DUPLICATE_CAROUSEL_CHART", {
        carouselId: "carouselId",
        sheetId,
        chartId: "invalidChartId",
        duplicatedChartId: "anyId",
      });
      expect(result).toBeCancelledBecause(CommandResult.InvalidFigureId);
    });

    test("Cannot add data view with wrong ranges", () => {
      expect(
        createCarouselWithDataView(model, toRangeData("notASheet", "A1"))
      ).toBeCancelledBecause(CommandResult.InvalidRange);
      expect(createCarouselWithDataView(model, toRangeData(sheetId, "AZ50"))).toBeCancelledBecause(
        CommandResult.InvalidRange
      );

      createCarousel(model, { items: [] }, "id");
      expect(
        updateCarousel(model, "id", {
          items: [{ type: "carouselDataView", rangeData: toRangeData("notASheet", "A1") }],
        })
      ).toBeCancelledBecause(CommandResult.InvalidRange);
      expect(
        updateCarousel(model, "id", {
          items: [{ type: "carouselDataView", rangeData: toRangeData(sheetId, "A250") }],
        })
      ).toBeCancelledBecause(CommandResult.InvalidRange);
    });
  });

  test("Can create a carousel figure", () => {
    createCarousel(model, { items: [] }, "carouselId");
    expect(model.getters.getFigures(sheetId)).toHaveLength(1);
    expect(model.getters.getFigures(sheetId)[0].tag).toBe("carousel");
    expect(model.getters.getCarousel("carouselId")).toEqual({ items: [] });
  });

  test("Can update a carousel", () => {
    createCarousel(model, { items: [] }, "carouselId");
    expect(model.getters.getCarousel("carouselId").items).toEqual([]);

    updateCarousel(model, "carouselId", { items: [{ type: "carouselDataView" }] });
    expect(model.getters.getCarousel("carouselId").items).toEqual([{ type: "carouselDataView" }]);
  });

  test("Can add a new chart to a carousel", () => {
    createCarousel(model, { items: [] }, "carouselId");

    addNewChartToCarousel(model, "carouselId", { type: "radar" });

    const carouselItems = model.getters.getCarousel("carouselId").items;
    expect(carouselItems).toMatchObject([{ type: "chart", chartId: expect.any(String) }]);
    expect(model.getters.getChartDefinition(carouselItems[0]["chartId"])).toMatchObject({
      type: "radar",
    });
    expect(model.getters.getFigureIdFromChartId(carouselItems[0]["chartId"])).toBe("carouselId");
  });

  test("Can add an existing chart to a carousel", () => {
    createCarousel(model, { items: [] }, "carouselId");
    createChart(model, { type: "radar" }, "chartId", undefined, { figureId: "chartFigureId" });
    expect(model.getters.getFigures(sheetId)).toHaveLength(2);

    addChartFigureToCarousel(model, "carouselId", "chartFigureId");

    expect(model.getters.getCarousel("carouselId").items).toEqual([
      { type: "chart", chartId: "chartId" },
    ]);
    expect(model.getters.getChartDefinition("chartId")).toMatchObject({ type: "radar" });
    expect(model.getters.getFigureIdFromChartId("chartId")).toBe("carouselId");
    expect(model.getters.getFigures(sheetId)).toHaveLength(1);
  });

  test("Cannot add a non-existing chart to a carousel", () => {
    createCarousel(model, { items: [] }, "carouselId");
    const result = updateCarousel(model, "carouselId", {
      items: [{ type: "chart", chartId: "fakeChartId" }],
    });
    expect(result).toBeCancelledBecause(CommandResult.ChartDoesNotExist);
    expect(model.getters.getCarousel("carouselId").items).toEqual([]);
  });

  test("Can pop a chart out of a carousel", () => {
    createCarousel(model, { items: [] }, "carouselId");
    addNewChartToCarousel(model, "carouselId");
    expect(model.getters.getFigures(sheetId)).toHaveLength(1);
    const carouselFigureId = model.getters.getFigures(sheetId)![0].id;
    const carousel = model.getters.getCarousel(carouselFigureId);
    const chartId = carousel.items[0]["chartId"];

    popOutChartFromCarousel(model, sheetId, carouselFigureId, chartId);
    expect(model.getters.getCarousel(carouselFigureId).items).toHaveLength(0);

    const newFigures = model.getters.getFigures(sheetId);
    expect(newFigures).toHaveLength(2); // the carousel is still there, but without chart
    expect(newFigures[0].tag).toBe("carousel");
    expect(model.getters.getCarousel(carouselFigureId).items).toHaveLength(0);
    expect(newFigures[1].tag).toBe("chart");
  });

  test("Carousel data view range is adapted on sheet modification", () => {
    createCarouselWithDataView(model, toRangeData(sheetId, "A1"), "carouselId");
    expect(model.getters.getCarousel("carouselId").items[0]).toMatchObject({
      range: { zone: toZone("A1") },
    });

    addRows(model, "before", 0, 2);
    expect(model.getters.getCarousel("carouselId").items[0]).toMatchObject({
      range: { zone: toZone("A3") },
    });

    undo(model);
    expect(model.getters.getCarousel("carouselId").items[0]).toMatchObject({
      range: { zone: toZone("A1") },
    });
  });

  test("Column weights are removed when adding/removing columns inside the carousel range", () => {
    const columnWeights = [500, 250];
    createCarousel(
      model,
      {
        items: [
          { type: "carouselDataView", rangeData: toRangeData(sheetId, "A1:B1"), columnWeights },
        ],
      },
      "carouselId"
    );

    addColumns(model, "before", "A", 1);
    expect(model.getters.getCarousel("carouselId").items[0]).toMatchObject({
      range: { zone: toZone("B1:C1") },
      columnWeights,
    });

    addColumns(model, "after", "B", 1);
    expect(model.getters.getCarousel("carouselId").items[0]).toMatchObject({
      range: { zone: toZone("B1:D1") },
      columnWeights: undefined,
    });
  });

  test("Can duplicate a sheet with a carousel", () => {
    createCarousel(model, { items: [] }, "carouselId");
    const chartId = addNewChartToCarousel(model, "carouselId", { type: "funnel" });

    duplicateSheet(model, sheetId, "newSheetId");

    const carouselItems = model.getters.getCarousel("carouselId").items;
    expect(carouselItems).toMatchObject([{ type: "chart", chartId }]);
    expect(model.getters.getChartDefinition(chartId)).toMatchObject({ type: "funnel" });
    expect(model.getters.getFigureIdFromChartId(chartId)).toBe("carouselId");

    const newCarouselId = model.getters.getFigures("newSheetId")[0].id;
    const newChartId = "newSheetId??" + chartId;
    expect(model.getters.getFigures("newSheetId")).toHaveLength(1);
    expect(model.getters.getFigures("newSheetId")[0].tag).toBe("carousel");
    expect(model.getters.getCarousel(newCarouselId).items).toEqual([
      { type: "chart", chartId: newChartId },
    ]);
    expect(model.getters.getChartDefinition("newSheetId??" + chartId)).toMatchObject({
      type: "funnel",
    });
    expect(model.getters.getFigureIdFromChartId("newSheetId??" + chartId)).toBe(newCarouselId);
  });

  test("Can export/import a carousel and its charts", () => {
    const title = { text: "Title1", fontSize: 20, bold: true };
    createCarousel(model, { items: [], title }, "carouselId");
    addNewChartToCarousel(model, "carouselId");
    const chartId = model.getters.getCarousel("carouselId").items[0]["chartId"];
    updateChart(model, chartId, {
      type: "pyramid",
      ...toChartDataSource({ dataSets: [{ dataRange: "A1:A6" }] }),
    });

    createChart(model, { type: "radar" }, "chartId2", undefined, { figureId: "chartFigureId" });
    addChartFigureToCarousel(model, "carouselId", "chartFigureId");
    const data = model.exportData();
    const newModel = new Model(data);
    expect(newModel.getters.getFigures(sheetId)).toHaveLength(1);
    expect(newModel.getters.getCarousel("carouselId").title).toEqual(title);
    expect(newModel.getters.getCarousel("carouselId").items).toEqual([
      { type: "chart", chartId },
      { type: "chart", chartId: "chartId2" },
    ]);
    expect(newModel.getters.getChartDefinition(chartId)).toMatchObject({
      type: "pyramid",
      ...toChartDataSource({ dataSets: [{ dataRange: "A1:A6" }] }),
    });
    expect(newModel.getters.getChartDefinition("chartId2")).toMatchObject({ type: "radar" });
  });

  test("Can import a model with a carousel with a chart, add a new chart, then re-export the model", () => {
    // Note: we need to be a bit careful during the import/export, when the carousel plugin exports the carousel, we need to
    // avoid overridding content that the chart plugin added to figure data
    // Bug was: import carousel with a spread keeping all the POJO keys (it keeps figure.data.chartDefinitions) => add a new chart
    // => export data => mistakenly overriding figure.data.chartDefinitions with the one originally imported
    createCarousel(model, { items: [] }, "carouselId");
    addNewChartToCarousel(model, "carouselId");

    const data = model.exportData();
    const model2 = new Model(data);
    addNewChartToCarousel(model2, "carouselId");

    const data2 = model2.exportData();
    expect(Object.keys(data2.sheets[0].figures![0].data.chartDefinitions)).toHaveLength(2);
    expect(new Model(data2).getters.getChartIds("Sheet1")).toHaveLength(2);
  });

  test("Can export/import a carousel and its ranges", () => {
    const carouselItem: CarouselItemData = {
      type: "carouselDataView",
      title: "Range1",
      rangeData: toRangeData(sheetId, "A1:B2"),
    };
    createCarousel(model, { items: [carouselItem] }, "carouselId");
    const data = model.exportData();
    expect(data.sheets[0].figures).toHaveLength(1);
    expect(data.sheets[0].figures![0].data.items[0]).toEqual(carouselItem);

    const newModel = new Model(data);
    expect(newModel.getters.getFigures(sheetId)).toHaveLength(1);
    expect(newModel.getters.getCarousel("carouselId").items[0]).toMatchObject({
      type: "carouselDataView",
      title: "Range1",
      range: { zone: toZone("A1:B2") },
    });
  });

  test("Carousel item is still selected when changing its name", () => {
    createCarousel(model, { items: [{ type: "carouselDataView" }] }, "carouselId");
    const chartId = addNewChartToCarousel(model, "carouselId");
    selectCarouselItem(model, "carouselId", { type: "chart", chartId });

    expect(model.getters.getSelectedCarouselItem("carouselId")).toEqual({ type: "chart", chartId });

    updateCarousel(model, "carouselId", {
      items: [{ type: "carouselDataView" }, { type: "chart", chartId, title: "Title" }],
    });
    expect(model.getters.getSelectedCarouselItem("carouselId")).toEqual({
      type: "chart",
      chartId,
      title: "Title",
    });
  });
});
