/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const {
  getAllMessagesUiById,
  getAllCssMessagesMatchingElements,
  getAllNetworkMessagesUpdateById,
  getAllRepeatById,
  getCurrentGroup,
  getGroupsById,
  getMutableMessagesById,
  getVisibleMessages,
} = require("resource://devtools/client/webconsole/selectors/messages.js");
const {
  getFirstMessage,
  getLastMessage,
  getPrivatePacket,
  getWebConsoleUiMock,
  setupActions,
  setupStore,
} = require("resource://devtools/client/webconsole/test/node/helpers.js");
const {
  stubPackets,
} = require("resource://devtools/client/webconsole/test/node/fixtures/stubs/index.js");
const {
  CSS_MESSAGE_ADD_MATCHING_ELEMENTS,
} = require("resource://devtools/client/webconsole/constants.js");

const expect = require("expect");

describe("private messages", () => {
  let actions;
  beforeAll(() => {
    actions = setupActions();
  });

  it("removes private messages on PRIVATE_MESSAGES_CLEAR action", () => {
    const { dispatch, getState } = setupStore();

    dispatch(
      actions.messagesAdd([
        getPrivatePacket("console.trace()"),
        stubPackets.get("console.log('mymap')"),
        getPrivatePacket("console.log(undefined)"),
        getPrivatePacket("GET request"),
      ])
    );

    let state = getState();
    const messages = getMutableMessagesById(state);
    expect(messages.size).toBe(4);

    dispatch(actions.privateMessagesClear());

    state = getState();
    expect(getMutableMessagesById(state).size).toBe(1);
    expect(getVisibleMessages(state).length).toBe(1);
  });

  it("cleans messagesUiById on PRIVATE_MESSAGES_CLEAR action", () => {
    const { dispatch, getState } = setupStore();

    dispatch(
      actions.messagesAdd([
        getPrivatePacket("console.trace()"),
        stubPackets.get("console.trace()"),
      ])
    );

    let state = getState();
    expect(getAllMessagesUiById(state).length).toBe(2);

    dispatch(actions.privateMessagesClear());

    state = getState();
    expect(getAllMessagesUiById(state).length).toBe(1);
  });

  it("cleans repeatsById on PRIVATE_MESSAGES_CLEAR action", () => {
    const { dispatch, getState } = setupStore();

    dispatch(
      actions.messagesAdd([
        getPrivatePacket("console.log(undefined)"),
        getPrivatePacket("console.log(undefined)"),
        stubPackets.get("console.log(undefined)"),
        stubPackets.get("console.log(undefined)"),
      ])
    );

    let state = getState();
    expect(getAllRepeatById(state)).toEqual({
      [getFirstMessage(state).id]: 2,
      [getLastMessage(state).id]: 2,
    });

    dispatch(actions.privateMessagesClear());

    state = getState();
    expect(Object.keys(getAllRepeatById(state)).length).toBe(1);
    expect(getAllRepeatById(state)).toEqual({
      [getFirstMessage(state).id]: 2,
    });
  });

  it("cleans cssMessagesMatchingElements on PRIVATE_MESSAGES_CLEAR action", () => {
    const { dispatch, getState } = setupStore();

    dispatch(
      actions.messagesAdd([
        getPrivatePacket(
          `Unknown property ‘such-unknown-property’.  Declaration dropped.`
        ),
        stubPackets.get(
          `Error in parsing value for ‘padding-top’.  Declaration dropped.`
        ),
      ])
    );

    const privateData = Symbol("privateData");
    const publicData = Symbol("publicData");

    dispatch({
      type: CSS_MESSAGE_ADD_MATCHING_ELEMENTS,
      id: getFirstMessage(getState()).id,
      elements: privateData,
    });

    dispatch({
      type: CSS_MESSAGE_ADD_MATCHING_ELEMENTS,
      id: getLastMessage(getState()).id,
      elements: publicData,
    });

    let state = getState();
    expect(getAllCssMessagesMatchingElements(state).size).toBe(2);

    dispatch(actions.privateMessagesClear());

    state = getState();
    expect(getAllCssMessagesMatchingElements(state).size).toBe(1);
    expect(
      getAllCssMessagesMatchingElements(state).get(
        getFirstMessage(getState()).id
      )
    ).toBe(publicData);
  });

  it("cleans group properties on PRIVATE_MESSAGES_CLEAR action", () => {
    const { dispatch, getState } = setupStore();
    dispatch(
      actions.messagesAdd([
        stubPackets.get("console.group()"),
        getPrivatePacket("console.group()"),
      ])
    );

    let state = getState();
    const publicMessageId = getFirstMessage(state).id;
    const privateMessageId = getLastMessage(state).id;
    expect(getCurrentGroup(state)).toBe(privateMessageId);
    expect(getGroupsById(state).size).toBe(2);

    dispatch(actions.privateMessagesClear());

    state = getState();
    expect(getGroupsById(state).size).toBe(1);
    expect(getGroupsById(state).has(publicMessageId)).toBe(true);
    expect(getCurrentGroup(state)).toBe(publicMessageId);
  });

  it("cleans networkMessagesUpdateById on PRIVATE_MESSAGES_CLEAR action", () => {
    const { dispatch, getState } = setupStore();

    const publicActor = "network/public";
    const privateActor = "network/private";
    const publicPacket = {
      ...stubPackets.get("GET request"),
      actor: publicActor,
    };
    const privatePacket = {
      ...getPrivatePacket("XHR GET request"),
      actor: privateActor,
    };

    // We need to reassign the timeStamp of the packet to guarantee the order.
    publicPacket.timeStamp = publicPacket.timeStamp + 1;
    privatePacket.timeStamp = privatePacket.timeStamp + 2;

    dispatch(actions.messagesAdd([publicPacket, privatePacket]));

    let networkUpdates = getAllNetworkMessagesUpdateById(getState());
    expect(Object.keys(networkUpdates)).toEqual([publicActor, privateActor]);

    dispatch(actions.privateMessagesClear());

    networkUpdates = getAllNetworkMessagesUpdateById(getState());
    expect(Object.keys(networkUpdates)).toEqual([publicActor]);
  });

  it("releases private backend actors on PRIVATE_MESSAGES_CLEAR action", () => {
    const releasedActors = [];
    const { dispatch, getState } = setupStore([], {
      webConsoleUI: getWebConsoleUiMock({
        commands: {
          client: {
            mainRoot: {},
          },
          objectCommand: {
            releaseObjects: async frontsToRelease => {
              for (const front of frontsToRelease) {
                releasedActors.push(front.actorID);
              }
            },
          },
        },
      }),
    });
    const mockFrontRelease = function () {
      releasedActors.push(this.actorID);
    };

    const publicPacket = stubPackets.get(
      "console.log('myarray', ['red', 'green', 'blue'])"
    );
    const privatePacket = getPrivatePacket("console.log('mymap')");

    publicPacket.arguments[1].release = mockFrontRelease;
    privatePacket.arguments[1].release = mockFrontRelease;

    // Add a log message.
    dispatch(actions.messagesAdd([publicPacket, privatePacket]));

    const firstMessage = getFirstMessage(getState());
    const firstMessageActor = firstMessage.parameters[1].actorID;

    const lastMessage = getLastMessage(getState());
    const lastMessageActor = lastMessage.parameters[1].actorID;

    // Kick-off the actor release.
    dispatch(actions.privateMessagesClear());

    expect(releasedActors.length).toBe(1);
    expect(releasedActors).toInclude(lastMessageActor);
    expect(releasedActors).toNotInclude(firstMessageActor);
  });
});
