/* 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/. */

/* exported getExtensionSidebarActors, expectNoSuchActorIDs,
            waitForObjectInspector, testSetExpressionSidebarPanel, assertTreeView,
            assertObjectInspector, moveMouseOnObjectInspectorDOMNode,
            moveMouseOnPanelCenter, clickOpenInspectorIcon */

"use strict";

const ACCORDION_LABEL_SELECTOR = ".accordion-header-label";
const ACCORDION_CONTENT_SELECTOR = ".accordion-content";

// Retrieve the array of all the objectValueGrip actors from the
// inspector extension sidebars state
// (used in browser_ext_devtools_panels_elements_sidebar.js).
function getExtensionSidebarActors(inspector) {
  const state = inspector.store.getState();

  const actors = [];

  for (const sidebarId of Object.keys(state.extensionsSidebar)) {
    const sidebarState = state.extensionsSidebar[sidebarId];

    if (
      sidebarState.viewMode === "object-value-grip-view" &&
      sidebarState.objectValueGrip &&
      sidebarState.objectValueGrip.actor
    ) {
      actors.push(sidebarState.objectValueGrip.actor);
    }
  }

  return actors;
}

// Test that the specified objectValueGrip actors have been released
// on the remote debugging server
// (used in browser_ext_devtools_panels_elements_sidebar.js).
async function expectNoSuchActorIDs(client, actors) {
  info(`Test that all the objectValueGrip actors have been released`);
  for (const actor of actors) {
    await Assert.rejects(
      client.request({ to: actor, type: "requestTypes" }),
      err => err.message == `No such actor for ID: ${actor}`
    );
  }
}

function waitForObjectInspector(panelDoc, waitForNodeWithType = "object") {
  const selector = `.object-inspector .objectBox-${waitForNodeWithType}`;
  return TestUtils.waitForCondition(() => {
    return !!panelDoc.querySelectorAll(selector).length;
  }, `Wait for objectInspector's node type "${waitForNodeWithType}" to be loaded`);
}

// Helper function used inside the sidebar.setExtensionPage test case.
async function testSetExtensionPageSidebarPanel(panelDoc, expectedURL) {
  const selector = "iframe.inspector-extension-sidebar-page";
  const iframesCount = await TestUtils.waitForCondition(() => {
    return panelDoc.querySelectorAll(selector).length;
  }, "Wait for the extension page iframe");

  is(
    iframesCount,
    1,
    "Got the expected number of iframes in the extension panel"
  );

  const iframeWindow = panelDoc.querySelector(selector).contentWindow;
  await TestUtils.waitForCondition(() => {
    return (
      iframeWindow.document.readyState === "complete" &&
      iframeWindow.location.href != "about:blank"
    );
  }, "Wait for the extension page iframe to complete to load");

  is(
    iframeWindow.location.href,
    expectedURL,
    "Got the expected url in the extension panel iframe"
  );
}

// Helper function used inside the sidebar.setObjectValueGrip test case.
async function testSetExpressionSidebarPanel(panel, expected) {
  const { nodesLength, propertiesNames, rootTitle } = expected;

  await waitForObjectInspector(panel);

  const objectInspectors = [...panel.querySelectorAll(".tree")];
  is(
    objectInspectors.length,
    1,
    "There is the expected number of object inspectors"
  );
  const [objectInspector] = objectInspectors;

  await TestUtils.waitForCondition(() => {
    return objectInspector.querySelectorAll(".node").length >= nodesLength;
  }, "Wait the objectInspector to have been fully rendered");

  const oiNodes = objectInspector.querySelectorAll(".node");

  is(
    oiNodes.length,
    nodesLength,
    "Got the expected number of nodes in the tree"
  );
  const propertiesNodes = [
    ...objectInspector.querySelectorAll(".object-label"),
  ].map(el => el.textContent);
  is(
    JSON.stringify(propertiesNodes),
    JSON.stringify(propertiesNames),
    "Got the expected property names"
  );

  if (rootTitle) {
    // Also check that the ObjectInspector is rendered inside
    // an Accordion component with the expected title.
    const accordion = panel.querySelector(".accordion");

    ok(accordion, "Got an Accordion component as expected");

    is(
      accordion.querySelector(ACCORDION_CONTENT_SELECTOR).firstChild,
      objectInspector,
      "The ObjectInspector should be inside the Accordion content"
    );

    is(
      accordion.querySelector(ACCORDION_LABEL_SELECTOR).textContent,
      rootTitle,
      "The Accordion has the expected label"
    );
  } else {
    // Also check that there is no Accordion component rendered
    // inside the sidebar panel.
    ok(
      !panel.querySelector(".accordion"),
      "Got no Accordion component as expected"
    );
  }
}

function assertTreeView(panelDoc, expectedContent) {
  const { expectedTreeTables, expectedStringCells, expectedNumberCells } =
    expectedContent;

  if (expectedTreeTables) {
    is(
      panelDoc.querySelectorAll("table.treeTable").length,
      expectedTreeTables,
      "The panel document contains the expected number of TreeView components"
    );
  }

  if (expectedStringCells) {
    is(
      panelDoc.querySelectorAll("table.treeTable .stringCell").length,
      expectedStringCells,
      "The panel document contains the expected number of string cells."
    );
  }

  if (expectedNumberCells) {
    is(
      panelDoc.querySelectorAll("table.treeTable .numberCell").length,
      expectedNumberCells,
      "The panel document contains the expected number of number cells."
    );
  }
}

async function assertObjectInspector(panelDoc, expectedContent) {
  const { expectedDOMNodes, expectedOpenInspectors } = expectedContent;

  // Get and verify the DOMNode and the "open inspector"" icon
  // rendered inside the ObjectInspector.
  const nodes = panelDoc.querySelectorAll(".objectBox-node");
  const nodeOpenInspectors = panelDoc.querySelectorAll(
    ".objectBox-node .open-inspector"
  );

  is(
    nodes.length,
    expectedDOMNodes,
    "Found the expected number of ObjectInspector DOMNodes"
  );
  is(
    nodeOpenInspectors.length,
    expectedOpenInspectors,
    "Found the expected nuber of open-inspector icons inside the ObjectInspector"
  );
}

function moveMouseOnObjectInspectorDOMNode(panelDoc, nodeIndex = 0) {
  const nodes = panelDoc.querySelectorAll(".objectBox-node");
  const node = nodes[nodeIndex];

  ok(node, "Found the ObjectInspector DOMNode");

  EventUtils.synthesizeMouseAtCenter(
    node,
    { type: "mousemove" },
    node.ownerDocument.defaultView
  );
}

function moveMouseOnPanelCenter(panelDoc) {
  EventUtils.synthesizeMouseAtCenter(
    panelDoc,
    { type: "mousemove" },
    panelDoc.window
  );
}

function clickOpenInspectorIcon(panelDoc, nodeIndex = 0) {
  const nodes = panelDoc.querySelectorAll(".objectBox-node .open-inspector");
  const node = nodes[nodeIndex];

  ok(node, "Found the ObjectInspector open-inspector icon");

  node.click();
}
