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

import { showMenu } from "../../context-menu/menu";

import {
  isSourceMapIgnoreListEnabled,
  isSourceOnSourceMapIgnoreList,
  getProjectDirectoryRoot,
  getSourcesTreeSources,
  getBlackBoxRanges,
} from "../../selectors/index";

import {
  setNetworkOverride,
  removeNetworkOverride,
} from "devtools/client/framework/actions/index";

import { loadSourceText } from "../sources/loadSourceText";
import { toggleBlackBox, blackBoxSources } from "../sources/blackbox";
import {
  setProjectDirectoryRoot,
  clearProjectDirectoryRoot,
} from "../sources-tree";

import { shouldBlackbox } from "../../utils/source";
import { copyToTheClipboard } from "../../utils/clipboard";
import { saveAsLocalFile } from "../../utils/utils";

/**
 * Show the context menu of SourceTreeItem.
 *
 * @param {object} event
 *        The context-menu DOM event.
 * @param {object} item
 *        Source Tree Item object.
 */
export function showSourceTreeItemContextMenu(
  event,
  item,
  depth,
  setExpanded,
  itemName,
  isOverridden
) {
  return async ({ dispatch, getState, panel }) => {
    const copySourceUri2Label = L10N.getStr("copySourceUri2");
    const copySourceUri2Key = L10N.getStr("copySourceUri2.accesskey");
    const setDirectoryRootLabel = L10N.getStr("setDirectoryRoot.label");
    const setDirectoryRootKey = L10N.getStr("setDirectoryRoot.accesskey");
    const removeDirectoryRootLabel = L10N.getStr("removeDirectoryRoot.label");

    const menuOptions = [];

    const state = getState();
    const isSourceOnIgnoreList =
      isSourceMapIgnoreListEnabled(state) &&
      isSourceOnSourceMapIgnoreList(state, item.source);
    const projectRoot = getProjectDirectoryRoot(state);

    if (item.type == "source") {
      const { source } = item;
      const copySourceUri2 = {
        id: "node-menu-copy-source",
        label: copySourceUri2Label,
        accesskey: copySourceUri2Key,
        disabled: false,
        click: () => copyToTheClipboard(source.url),
      };

      const ignoreStr = item.isBlackBoxed ? "unignore" : "ignore";
      const blackBoxMenuItem = {
        id: "node-menu-blackbox",
        label: L10N.getStr(`ignoreContextItem.${ignoreStr}`),
        accesskey: L10N.getStr(`ignoreContextItem.${ignoreStr}.accesskey`),
        disabled: isSourceOnIgnoreList || !shouldBlackbox(source),
        click: () => dispatch(toggleBlackBox(source)),
      };
      const downloadFileItem = {
        id: "node-menu-download-file",
        label: L10N.getStr("downloadFile.label"),
        accesskey: L10N.getStr("downloadFile.accesskey"),
        disabled: false,
        click: () => saveLocalFile(dispatch, source),
      };

      const overrideStr = !isOverridden ? "override" : "removeOverride";
      const overridesItem = {
        id: "node-menu-overrides",
        label: L10N.getStr(`overridesContextItem.${overrideStr}`),
        accesskey: L10N.getStr(`overridesContextItem.${overrideStr}.accesskey`),
        // Network overrides are disabled for original files.
        disabled: source.isOriginal,
        // Network overrides are disabled for remote debugging (bug 1881441).
        visible: panel.toolbox.commands.descriptorFront.isLocalTab,
        click: () =>
          handleLocalOverride(dispatch, panel.toolbox, source, isOverridden),
      };

      menuOptions.push(
        copySourceUri2,
        blackBoxMenuItem,
        downloadFileItem,
        overridesItem
      );
    }

    // All other types other than source are folder-like
    if (item.type != "source") {
      addCollapseExpandAllOptions(menuOptions, item, setExpanded);

      if (projectRoot == item.uniquePath) {
        menuOptions.push({
          id: "node-remove-directory-root",
          label: removeDirectoryRootLabel,
          disabled: false,
          click: () => dispatch(clearProjectDirectoryRoot()),
        });
      } else {
        menuOptions.push({
          id: "node-set-directory-root",
          label: setDirectoryRootLabel,
          accesskey: setDirectoryRootKey,
          disabled: false,
          click: () =>
            dispatch(
              setProjectDirectoryRoot(
                item.uniquePath,
                itemName,
                getItemProjectDirectoryRootName(item)
              )
            ),
        });
      }

      addBlackboxAllOption(dispatch, state, menuOptions, item, depth);
    }

    showMenu(event, menuOptions);
  };
}

/**
 * Compute the string which will be displayed as tooltip on the project directory root header
 */
function getItemProjectDirectoryRootName(item) {
  if (item.thread) {
    return item.thread.name;
  }

  // Go up the source tree to get to the group item
  let groupItem = item;
  while (!groupItem.groupName) {
    groupItem = groupItem.parent;
  }

  // Group's origin is the base URL
  const origin = groupItem.origin;
  const path = item != groupItem ? item.path : "";
  // The group item's parent is always a thread item
  const threadName = groupItem.parent.thread.name;

  return `${origin}${path} on ${threadName}`;
}

async function saveLocalFile(dispatch, source) {
  if (!source) {
    return null;
  }

  const data = await dispatch(loadSourceText(source));
  if (!data) {
    return null;
  }
  return saveAsLocalFile(data.value, source.displayURL.filename);
}

async function handleLocalOverride(dispatch, toolbox, source, isOverridden) {
  if (!source || !source.url) {
    return;
  }

  const toolboxStore = toolbox.store;
  if (!isOverridden) {
    const data = await dispatch(loadSourceText(source));
    if (data?.value && data.value.type == "text") {
      toolboxStore.dispatch(
        setNetworkOverride(
          toolbox.commands,
          source.url,
          data.value.value,
          window
        )
      );
    }
  } else {
    toolboxStore.dispatch(removeNetworkOverride(toolbox.commands, source.url));
  }
}

function addBlackboxAllOption(dispatch, state, menuOptions, item, depth) {
  const {
    sourcesInside,
    sourcesOutside,
    allInsideBlackBoxed,
    allOutsideBlackBoxed,
  } = getBlackBoxSourcesGroups(state, item);
  const projectRoot = getProjectDirectoryRoot(state);

  let blackBoxInsideMenuItemLabel;
  let blackBoxOutsideMenuItemLabel;
  if (depth === 0 || (depth === 1 && projectRoot === "")) {
    blackBoxInsideMenuItemLabel = allInsideBlackBoxed
      ? L10N.getStr("unignoreAllInGroup.label")
      : L10N.getStr("ignoreAllInGroup.label");
    if (sourcesOutside.length) {
      blackBoxOutsideMenuItemLabel = allOutsideBlackBoxed
        ? L10N.getStr("unignoreAllOutsideGroup.label")
        : L10N.getStr("ignoreAllOutsideGroup.label");
    }
  } else {
    blackBoxInsideMenuItemLabel = allInsideBlackBoxed
      ? L10N.getStr("unignoreAllInDir.label")
      : L10N.getStr("ignoreAllInDir.label");
    if (sourcesOutside.length) {
      blackBoxOutsideMenuItemLabel = allOutsideBlackBoxed
        ? L10N.getStr("unignoreAllOutsideDir.label")
        : L10N.getStr("ignoreAllOutsideDir.label");
    }
  }

  const blackBoxInsideMenuItem = {
    id: allInsideBlackBoxed
      ? "node-unblackbox-all-inside"
      : "node-blackbox-all-inside",
    label: blackBoxInsideMenuItemLabel,
    disabled: false,
    click: () => dispatch(blackBoxSources(sourcesInside, !allInsideBlackBoxed)),
  };

  if (sourcesOutside.length) {
    menuOptions.push({
      id: "node-blackbox-all",
      label: L10N.getStr("ignoreAll.label"),
      submenu: [
        blackBoxInsideMenuItem,
        {
          id: allOutsideBlackBoxed
            ? "node-unblackbox-all-outside"
            : "node-blackbox-all-outside",
          label: blackBoxOutsideMenuItemLabel,
          disabled: false,
          click: () =>
            dispatch(blackBoxSources(sourcesOutside, !allOutsideBlackBoxed)),
        },
      ],
    });
  } else {
    menuOptions.push(blackBoxInsideMenuItem);
  }
}

function addCollapseExpandAllOptions(menuOptions, item, setExpanded) {
  menuOptions.push({
    id: "node-menu-collapse-all",
    label: L10N.getStr("collapseAll.label"),
    disabled: false,
    click: () => setExpanded(item, false, true),
  });

  menuOptions.push({
    id: "node-menu-expand-all",
    label: L10N.getStr("expandAll.label"),
    disabled: false,
    click: () => setExpanded(item, true, true),
  });
}

/**
 * Computes 4 lists:
 *  - `sourcesInside`: the list of all Source Items that are
 *    children of the current item (can be thread/group/directory).
 *    This include any nested level of children.
 *  - `sourcesOutside`: all other Source Items.
 *    i.e. all sources that are in any other folder of any group/thread.
 *  - `allInsideBlackBoxed`, all sources of `sourcesInside` which are currently
 *    blackboxed.
 *  - `allOutsideBlackBoxed`, all sources of `sourcesOutside` which are currently
 *    blackboxed.
 */
function getBlackBoxSourcesGroups(state, item) {
  const allSources = [];
  function collectAllSources(list, _item) {
    if (_item.children) {
      _item.children.forEach(i => collectAllSources(list, i));
    }
    if (_item.type == "source") {
      list.push(_item.source);
    }
  }

  const rootItems = getSourcesTreeSources(state);
  const blackBoxRanges = getBlackBoxRanges(state);

  for (const rootItem of rootItems) {
    collectAllSources(allSources, rootItem);
  }

  const sourcesInside = [];
  collectAllSources(sourcesInside, item);

  const sourcesOutside = allSources.filter(
    source => !sourcesInside.includes(source)
  );
  const allInsideBlackBoxed = sourcesInside.every(
    source => blackBoxRanges[source.url]
  );
  const allOutsideBlackBoxed = sourcesOutside.every(
    source => blackBoxRanges[source.url]
  );

  return {
    sourcesInside,
    sourcesOutside,
    allInsideBlackBoxed,
    allOutsideBlackBoxed,
  };
}
