import {Actions, Task, UndoRedoStore} from 'nylas-exports'

class Undoable extends Task {
  constructor() {
    super();
    this.canBeUndone = true;
  }
}

class PermanentTask extends Task {
  constructor() {
    super();
    this.canBeUndone = false;
  }
}

describe("UndoRedoStore", function undoRedoStoreSpec() {
  beforeEach(() => {
    UndoRedoStore._undo = []
    UndoRedoStore._redo = []
    spyOn(UndoRedoStore, "trigger")
    spyOn(Actions, "queueTask").andCallFake((...args) => {
      UndoRedoStore._onQueue(...args)
    });
    spyOn(Actions, "queueTasks").andCallFake((...args) => {
      UndoRedoStore._onQueue(...args)
    });

    this.ids = (arrarr) => arrarr.map((arr) => arr.map((itm) => itm.id))
    this.t1 = new Undoable();
    this.t2 = new Undoable();
    this.t3 = new Undoable();
    this.t4 = new Undoable();
    this.p1 = new PermanentTask();
    this.t1.id = "t1"
    this.t2.id = "t2"
    this.t3.id = "t3"
    this.t4.id = "t4"
    this.p1.id = "p1"
  });

  it("pushes single tasks onto undo/redo", () => {
    Actions.queueTask(this.t1)
    expect(UndoRedoStore._redo).toEqual([])
    expect(UndoRedoStore._undo).toEqual([[this.t1]])
    expect(UndoRedoStore.trigger).toHaveBeenCalled()
  });

  it("pushes multiple tasks onto redo", () => {
    Actions.queueTasks([this.t1, this.t2])
    expect(UndoRedoStore._redo).toEqual([])
    expect(UndoRedoStore._undo).toEqual([[this.t1, this.t2]])
    expect(UndoRedoStore.trigger).toHaveBeenCalled()
  });

  it("only undoes task if they're all 'undoable'", () => {
    Actions.queueTask([this.t1, this.p1])
    expect(UndoRedoStore._redo).toEqual([])
    expect(UndoRedoStore._undo).toEqual([])
    expect(UndoRedoStore.trigger).not.toHaveBeenCalled()
  });

  it("refreshes redo if we get a new task", () => {
    UndoRedoStore._redo = [[this.t1, this.t2], [this.t3]]
    Actions.queueTask(this.t3)
    expect(UndoRedoStore._redo).toEqual([])
  });

  it("doesn't refresh redo if our task is itself a redo task", () => {
    UndoRedoStore._redo = [[this.t1, this.t2], [this.t3]]
    const tr = new Undoable()
    tr.source = 'redo'
    Actions.queueTask(tr)
    expect(UndoRedoStore._redo).toEqual([[this.t1, this.t2], [this.t3]])
    expect(UndoRedoStore._undo).toEqual([[tr]])
  });

  it("runs undoTask on each group of undo tasks", () => {
    UndoRedoStore._undo = [[this.t3], [this.t1, this.t2]]
    UndoRedoStore.undo()
    expect(Actions.queueTask.calls.length).toBe(2)
    expect(Actions.queueTask.calls[0].args[0]).toBe("t1")
    expect(Actions.queueTask.calls[1].args[0]).toBe("t2")
    expect(UndoRedoStore._undo).toEqual([[this.t3]])
  });

  it("creates identical redo tasks and pushes on the stack", () => {
    UndoRedoStore._undo = [[this.t3], [this.t1, this.t2]]
    UndoRedoStore.undo()
    expect(UndoRedoStore._undo).toEqual([[this.t3]])
    expect(UndoRedoStore._redo[0][0].id).toBe("t1")
    expect(UndoRedoStore._redo[0][1].id).toBe("t2")
    expect(UndoRedoStore._redo.length).toBe(1)
  });

  it("redoes the latest task", () => {
    UndoRedoStore._undo = [[this.t3], [this.t1, this.t2]]
    UndoRedoStore.undo()
    UndoRedoStore.redo()
    expect(Actions.queueTasks.calls[0].args[0][0].id).toBe('t1')
    expect(Actions.queueTasks.calls[0].args[0][1].id).toBe('t2')
    expect(UndoRedoStore._undo[0]).toEqual([this.t3])
    expect(UndoRedoStore._undo[1][0].id).toBe('t1')
    expect(UndoRedoStore._undo[1][1].id).toBe('t2')
  });

  it("marks the incoming task as a redo task", () => {
    UndoRedoStore._undo = [[this.t3], [this.t1, this.t2]]
    UndoRedoStore.undo()
    UndoRedoStore.redo()
    expect(Actions.queueTasks.calls[0].args[0][0].source).toEqual('redo')
    expect(Actions.queueTasks.calls[0].args[0][1].source).toEqual('redo')
  });

  it("correctly follows the undo redo sequence of events", () => {
    Actions.queueTask(this.t1)
    Actions.queueTask(this.t2)
    Actions.queueTasks([this.t3, this.t4])
    expect(UndoRedoStore._undo).toEqual([[this.t1], [this.t2], [this.t3, this.t4]])
    UndoRedoStore.undo()
    UndoRedoStore.undo()
    UndoRedoStore.undo()
    UndoRedoStore.undo()
    UndoRedoStore.undo()
    expect(UndoRedoStore._undo).toEqual([])
    expect(this.ids(UndoRedoStore._redo)).toEqual([["t3", "t4"], ["t2"], ["t1"]])
    UndoRedoStore.redo()
    UndoRedoStore.redo()
    UndoRedoStore.redo()
    UndoRedoStore.redo()
    UndoRedoStore.redo()
    UndoRedoStore.redo()
    expect(this.ids(UndoRedoStore._undo)).toEqual([["t1"], ["t2"], ["t3", "t4"]])
    expect(this.ids(UndoRedoStore._redo)).toEqual([])
  });
});
