import { Model } from "../../src";
import { ClipboardStore } from "../../src/stores/clipboard_store";
import { LineChartDefinition } from "../../src/types/chart/line_chart";
import { MockTransportService } from "../__mocks__/transport_service";
import { toChartDataSource } from "../test_helpers/chart_helpers";
import {
  addColumns,
  addDataValidation,
  copy,
  createChart,
  createSheet,
  cut,
  deleteSheet,
  paste,
  setCellContent,
} from "../test_helpers/commands_helpers";
import { getCellRawContent } from "../test_helpers/getters_helpers";
import { makeStoreWithModel } from "../test_helpers/stores";
import { setupCollaborativeEnv } from "./collaborative_helpers";

describe("Collaborative range manipulation", () => {
  let network: MockTransportService;
  let alice: Model;
  let bob: Model;
  let charlie: Model;

  beforeEach(() => {
    ({ network, alice, bob, charlie } = setupCollaborativeEnv());
  });

  test("Copy - Paste of references are correctly updated", () => {
    makeStoreWithModel(bob, ClipboardStore);
    setCellContent(bob, "A1", "=SUM(B1:C1)");
    copy(bob, "A1");
    network.concurrent(() => {
      paste(bob, "C1");
      addColumns(alice, "after", "D", 1);
    });
    expect([alice, bob, charlie]).toHaveSynchronizedValue(
      (user) => getCellRawContent(user, "C1"),
      "=SUM(D1:F1)"
    );
  });

  test("cut and paste a range in the same sheet", () => {
    makeStoreWithModel(alice, ClipboardStore);
    setCellContent(alice, "A2", "=A1");
    cut(alice, "A1");
    paste(alice, "B1");
    expect([alice, bob, charlie]).toHaveSynchronizedValue(
      (user) => getCellRawContent(user, "A2"),
      "=B1"
    );
  });

  test("cut and paste a range in another sheet", () => {
    makeStoreWithModel(alice, ClipboardStore);
    setCellContent(alice, "A2", "=A1");
    cut(alice, "A1");
    createSheet(alice, { activate: true });
    paste(alice, "B1");
    expect([alice, bob, charlie]).toHaveSynchronizedValue(
      (user) => getCellRawContent(user, "A2", "Sheet1"),
      "=Sheet2!B1"
    );
  });

  test("cut and paste and delete origin sheet concurrently", () => {
    makeStoreWithModel(alice, ClipboardStore);
    setCellContent(alice, "A2", "hello");
    cut(alice, "A2");
    createSheet(alice, { sheetId: "Sheet2", activate: true });
    setCellContent(alice, "A1", "=Sheet1!A2");
    network.concurrent(() => {
      deleteSheet(bob, "Sheet1");
      paste(alice, "D4");
    });
    expect([alice, bob, charlie]).toHaveSynchronizedValue(
      (user) => getCellRawContent(user, "D4", "Sheet2"),
      "hello"
    );
    expect([alice, bob, charlie]).toHaveSynchronizedValue(
      (user) => getCellRawContent(user, "A1", "Sheet2"),
      "=#REF"
    );
  });

  test("cut and paste and delete target sheet concurrently (delete first)", () => {
    setCellContent(alice, "A2", "=A1");
    cut(alice, "A1");
    createSheet(alice, { sheetId: "Sheet2", activate: true });
    network.concurrent(() => {
      deleteSheet(bob, "Sheet2");
      paste(alice, "D4");
    });
    expect([alice, bob, charlie]).toHaveSynchronizedValue(
      (user) => getCellRawContent(user, "A2", "Sheet1"),
      "=A1"
    );
  });

  test("cut and paste and delete target sheet concurrently (paste first)", () => {
    makeStoreWithModel(alice, ClipboardStore);
    setCellContent(alice, "A2", "=A1");
    cut(alice, "A1");
    createSheet(alice, { sheetId: "Sheet2", activate: true });
    network.concurrent(() => {
      paste(alice, "D4");
      deleteSheet(bob, "Sheet2");
    });
    expect([alice, bob, charlie]).toHaveSynchronizedValue(
      (user) => getCellRawContent(user, "A2", "Sheet1"),
      "=#REF"
    );
  });

  test("cut and paste zone adapts chart range", () => {
    makeStoreWithModel(alice, ClipboardStore);
    createChart(
      alice,
      {
        ...toChartDataSource({
          dataSets: [{ dataRange: "A2" }],
          labelRange: "A1",
          dataSetsHaveTitle: false,
        }),
        type: "line",
      },
      "1"
    );
    cut(alice, "A2");
    paste(alice, "D4");
    expect([alice, bob, charlie]).toHaveSynchronizedValue(
      (user) => (user.getters.getChartDefinition("1") as LineChartDefinition<string>).dataSource,
      {
        type: "range",
        dataSets: [{ dataRange: "D4", dataSetId: expect.any(String) }],
        labelRange: "A1",
        dataSetsHaveTitle: false,
      }
    );
  });

  test("Can copy boolean datavalidation while preserving the cell values", () => {
    makeStoreWithModel(alice, ClipboardStore);
    setCellContent(alice, "A1", "TRUE");
    setCellContent(alice, "A3", "not a boolean");
    setCellContent(alice, "A4", "=TRANSPOSE(A1)");
    setCellContent(alice, "A5", "=TEXT(5)");
    setCellContent(alice, "A6", "=NOT(A1)");
    setCellContent(alice, "A7", "7");
    addDataValidation(alice, "A1:A7", "id", { type: "isBoolean", values: [] });

    copy(alice, "A1:A7");
    paste(alice, "B1");
    expect([alice, bob, charlie]).toHaveSynchronizedValue(
      (user) => getCellRawContent(user, "B1"),
      "TRUE"
    );
    expect([alice, bob, charlie]).toHaveSynchronizedValue(
      (user) => getCellRawContent(user, "B2"),
      "FALSE" // A2 was empty, which is falsy
    );
    expect([alice, bob, charlie]).toHaveSynchronizedValue(
      (user) => getCellRawContent(user, "B3"),
      "FALSE" // text is not a boolean -> falsy
    );
    expect([alice, bob, charlie]).toHaveSynchronizedValue(
      (user) => getCellRawContent(user, "B4"),
      "=TRANSPOSE(B1)" // is truthy
    );
    expect([alice, bob, charlie]).toHaveSynchronizedValue(
      (user) => getCellRawContent(user, "B5"),
      "FALSE" // text is not a boolean -> falsy
    );
    expect([alice, bob, charlie]).toHaveSynchronizedValue(
      (user) => getCellRawContent(user, "B6"),
      "=NOT(B1)"
    );
    expect([alice, bob, charlie]).toHaveSynchronizedValue(
      (user) => getCellRawContent(user, "B7"),
      "FALSE" // a number which does not represent a boolean is falsy
    );
  });
});
