"use strict";

let { SyncedTabs } = ChromeUtils.importESModule(
  "resource://services-sync/SyncedTabs.sys.mjs"
);
let { SyncedTabsListStore } = ChromeUtils.importESModule(
  "resource:///modules/syncedtabs/SyncedTabsListStore.sys.mjs"
);

const FIXTURE = [
  {
    id: "2xU5h-4bkWqA",
    type: "client",
    lastModified: 1492201200,
    name: "laptop",
    isMobile: false,
    tabs: [
      {
        type: "tab",
        title:
          "Firefox for iOS — Mobile Web browser for your iPhone, iPad and iPod touch — Mozilla",
        url: "https://www.mozilla.org/en-US/firefox/ios/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=synced-tabs-sidebar",
        icon: "moz-anno:favicon:https://www.mozilla.org/media/img/firefox/favicon.dc6635050bf5.ico",
        client: "2xU5h-4bkWqA",
        lastUsed: 1451519425,
      },
      {
        type: "tab",
        title: "Firefox Nightly First Run Page",
        url: "https://www.mozilla.org/en-US/firefox/nightly/firstrun/?oldversion=45.0a1",
        icon: "moz-anno:favicon:https://www.mozilla.org/media/img/firefox/favicon-nightly.560395bbb2e1.png",
        client: "2xU5h-4bkWqA",
        lastUsed: 1451519420,
      },
    ],
  },
  {
    id: "OL3EJCsdb2JD",
    type: "client",
    lastModified: 1492201200,
    name: "desktop",
    isMobile: false,
    tabs: [],
  },
];

add_task(async function testGetDataEmpty() {
  let store = new SyncedTabsListStore(SyncedTabs);
  let spy = sinon.spy();

  sinon.stub(SyncedTabs, "getTabClients").callsFake(() => {
    return Promise.resolve([]);
  });
  store.on("change", spy);

  await store.getData();

  Assert.ok(SyncedTabs.getTabClients.calledWith(""));
  Assert.ok(
    spy.calledWith({
      clients: [],
      canUpdateAll: false,
      canUpdateInput: false,
      filter: "",
      inputFocused: false,
    })
  );

  await store.getData("filter");

  Assert.ok(SyncedTabs.getTabClients.calledWith("filter"));
  Assert.ok(
    spy.calledWith({
      clients: [],
      canUpdateAll: false,
      canUpdateInput: true,
      filter: "filter",
      inputFocused: false,
    })
  );

  SyncedTabs.getTabClients.restore();
});

add_task(async function testRowSelectionWithoutFilter() {
  let store = new SyncedTabsListStore(SyncedTabs);
  let spy = sinon.spy();

  sinon.stub(SyncedTabs, "getTabClients").callsFake(() => {
    return Promise.resolve(FIXTURE);
  });

  await store.getData();
  SyncedTabs.getTabClients.restore();

  store.on("change", spy);

  store.selectRow(0, -1);
  Assert.ok(spy.args[0][0].canUpdateAll, "can update the whole view");
  Assert.ok(spy.args[0][0].clients[0].selected, "first client is selected");

  store.moveSelectionUp();
  Assert.ok(
    spy.calledOnce,
    "can't move up past first client, no change triggered"
  );

  store.selectRow(0, 0);
  Assert.ok(
    spy.args[1][0].clients[0].tabs[0].selected,
    "first tab of first client is selected"
  );

  store.selectRow(0, 0);
  Assert.ok(spy.calledTwice, "selecting same row doesn't trigger change");

  store.selectRow(0, 1);
  Assert.ok(
    spy.args[2][0].clients[0].tabs[1].selected,
    "second tab of first client is selected"
  );

  store.selectRow(1);
  Assert.ok(spy.args[3][0].clients[1].selected, "second client is selected");

  store.moveSelectionDown();
  Assert.equal(
    spy.callCount,
    4,
    "can't move selection down past last client, no change triggered"
  );

  store.moveSelectionUp();
  Assert.equal(spy.callCount, 5, "changed");
  Assert.ok(
    spy.args[4][0].clients[0].tabs[FIXTURE[0].tabs.length - 1].selected,
    "move selection up from client selects last tab of previous client"
  );

  store.moveSelectionUp();
  Assert.ok(
    spy.args[5][0].clients[0].tabs[FIXTURE[0].tabs.length - 2].selected,
    "move selection up from tab selects previous tab of client"
  );
});

add_task(async function testToggleBranches() {
  let store = new SyncedTabsListStore(SyncedTabs);
  let spy = sinon.spy();

  sinon.stub(SyncedTabs, "getTabClients").callsFake(() => {
    return Promise.resolve(FIXTURE);
  });

  await store.getData();
  SyncedTabs.getTabClients.restore();

  store.selectRow(0);
  store.on("change", spy);

  let clientId = FIXTURE[0].id;
  store.closeBranch(clientId);
  Assert.ok(spy.args[0][0].clients[0].closed, "first client is closed");

  store.openBranch(clientId);
  Assert.ok(!spy.args[1][0].clients[0].closed, "first client is open");

  store.toggleBranch(clientId);
  Assert.ok(spy.args[2][0].clients[0].closed, "first client is toggled closed");

  store.moveSelectionDown();
  Assert.ok(
    spy.args[3][0].clients[1].selected,
    "selection skips tabs if client is closed"
  );

  store.moveSelectionUp();
  Assert.ok(
    spy.args[4][0].clients[0].selected,
    "selection skips tabs if client is closed"
  );
});

add_task(async function testRowSelectionWithFilter() {
  let store = new SyncedTabsListStore(SyncedTabs);
  let spy = sinon.spy();

  sinon.stub(SyncedTabs, "getTabClients").callsFake(() => {
    return Promise.resolve(FIXTURE);
  });

  await store.getData("filter");
  SyncedTabs.getTabClients.restore();

  store.on("change", spy);

  store.selectRow(0);
  Assert.ok(
    spy.args[0][0].clients[0].tabs[0].selected,
    "first tab is selected"
  );

  store.moveSelectionUp();
  Assert.ok(
    spy.calledOnce,
    "can't move up past first tab, no change triggered"
  );

  store.moveSelectionDown();
  Assert.ok(
    spy.args[1][0].clients[0].tabs[1].selected,
    "selection skips tabs if client is closed"
  );

  store.moveSelectionDown();
  Assert.equal(
    spy.callCount,
    2,
    "can't move selection down past last tab, no change triggered"
  );

  store.selectRow(1);
  Assert.equal(spy.callCount, 2, "doesn't trigger change if same row selected");
});

add_task(async function testFilterAndClearFilter() {
  let store = new SyncedTabsListStore(SyncedTabs);
  let spy = sinon.spy();

  sinon.stub(SyncedTabs, "getTabClients").callsFake(() => {
    return Promise.resolve(FIXTURE);
  });
  store.on("change", spy);

  await store.getData("filter");

  Assert.ok(SyncedTabs.getTabClients.calledWith("filter"));
  Assert.ok(!spy.args[0][0].canUpdateAll, "can't update all");
  Assert.ok(spy.args[0][0].canUpdateInput, "can update just input");

  store.selectRow(0);

  Assert.equal(spy.args[1][0].filter, "filter");
  Assert.ok(spy.args[1][0].clients[0].tabs[0].selected, "tab is selected");

  await store.clearFilter();

  Assert.ok(SyncedTabs.getTabClients.calledWith(""));
  Assert.ok(!spy.args[2][0].canUpdateAll, "can't update all");
  Assert.ok(!spy.args[2][0].canUpdateInput, "can't just update input");

  Assert.equal(spy.args[2][0].filter, "");
  Assert.ok(
    !spy.args[2][0].clients[0].tabs[0].selected,
    "tab is no longer selected"
  );

  SyncedTabs.getTabClients.restore();
});

add_task(async function testFocusBlurInput() {
  let store = new SyncedTabsListStore(SyncedTabs);
  let spy = sinon.spy();

  sinon.stub(SyncedTabs, "getTabClients").callsFake(() => {
    return Promise.resolve(FIXTURE);
  });
  store.on("change", spy);

  await store.getData();
  SyncedTabs.getTabClients.restore();

  Assert.ok(!spy.args[0][0].canUpdateAll, "must rerender all");

  store.selectRow(0);
  Assert.ok(!spy.args[1][0].inputFocused, "input is not focused");
  Assert.ok(spy.args[1][0].clients[0].selected, "client is selected");
  Assert.ok(spy.args[1][0].clients[0].focused, "client is focused");

  store.focusInput();
  Assert.ok(spy.args[2][0].inputFocused, "input is focused");
  Assert.ok(spy.args[2][0].clients[0].selected, "client is still selected");
  Assert.ok(!spy.args[2][0].clients[0].focused, "client is no longer focused");

  store.blurInput();
  Assert.ok(!spy.args[3][0].inputFocused, "input is not focused");
  Assert.ok(spy.args[3][0].clients[0].selected, "client is selected");
  Assert.ok(spy.args[3][0].clients[0].focused, "client is focused");
});
