/* 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 { assert } = require("resource://devtools/shared/DevToolsUtils.js");
const {
  getDOMMutationBreakpoint,
  getDOMMutationBreakpoints,
} = require("resource://devtools/client/framework/reducers/dom-mutation-breakpoints.js");

exports.registerWalkerListeners = registerWalkerListeners;
function registerWalkerListeners(store, walker) {
  walker.on("mutations", mutations => handleWalkerMutations(mutations, store));
}

/**
 * Called when a target is destroyed. This will allow the reducer to remove breakpoints on
 * nodeFront associated with the passed target
 *
 * @param {ToolboxStore} store: The toolbox redux store
 * @param {TargetFront} targetFront
 */
function removeTarget(store, targetFront) {
  store.dispatch({
    type: "REMOVE_TARGET",
    targetFront,
  });
}
exports.removeTarget = removeTarget;

function handleWalkerMutations(mutations, store) {
  // If we got BP updates for detach/unload, we want to drop those nodes from
  // the list of active DOM mutation breakpoints. We explicitly check these
  // cases because BP updates could also happen due to explicitly API
  // operations to add/remove bps.
  const mutationItems = mutations.filter(
    mutation => mutation.type === "mutationBreakpoint"
  );
  if (mutationItems.length) {
    store.dispatch(updateBreakpointsForMutations(mutationItems));
  }
}

exports.createDOMMutationBreakpoint = createDOMMutationBreakpoint;
function createDOMMutationBreakpoint(nodeFront, mutationType) {
  assert(typeof nodeFront === "object" && nodeFront);
  assert(typeof mutationType === "string");

  return async function ({ dispatch }) {
    const walker = nodeFront.walkerFront;

    dispatch({
      type: "ADD_DOM_MUTATION_BREAKPOINT",
      nodeFront,
      mutationType,
    });

    await walker.setMutationBreakpoints(nodeFront, {
      [mutationType]: true,
    });
  };
}

exports.deleteDOMMutationBreakpoint = deleteDOMMutationBreakpoint;
function deleteDOMMutationBreakpoint(nodeFront, mutationType) {
  assert(typeof nodeFront === "object" && nodeFront);
  assert(typeof mutationType === "string");

  return async function ({ dispatch }) {
    const walker = nodeFront.walkerFront;
    await walker.setMutationBreakpoints(nodeFront, {
      [mutationType]: false,
    });

    dispatch({
      type: "REMOVE_DOM_MUTATION_BREAKPOINT",
      nodeFront,
      mutationType,
    });
  };
}

function updateBreakpointsForMutations(mutationItems) {
  return async function ({ dispatch, getState }) {
    const removedNodeFronts = [];
    const changedNodeFronts = new Set();

    for (const { target: nodeFront, mutationReason } of mutationItems) {
      switch (mutationReason) {
        case "api":
          changedNodeFronts.add(nodeFront);
          break;
        default:
          console.error(
            "Unexpected mutation reason",
            mutationReason,
            ", removing"
          );
        // Fall Through
        case "detach":
        case "unload":
          removedNodeFronts.push(nodeFront);
          break;
      }
    }

    if (removedNodeFronts.length) {
      dispatch({
        type: "REMOVE_DOM_MUTATION_BREAKPOINTS_FOR_FRONTS",
        nodeFronts: removedNodeFronts,
      });
    }
    if (changedNodeFronts.size > 0) {
      const enabledStates = [];
      for (const {
        id,
        nodeFront,
        mutationType,
        enabled,
      } of getDOMMutationBreakpoints(getState())) {
        if (changedNodeFronts.has(nodeFront)) {
          const bpEnabledOnFront = nodeFront.mutationBreakpoints[mutationType];
          if (bpEnabledOnFront !== enabled) {
            // Sync the bp state from the front into the store.
            enabledStates.push([id, bpEnabledOnFront]);
          }
        }
      }

      dispatch({
        type: "SET_DOM_MUTATION_BREAKPOINTS_ENABLED_STATE",
        enabledStates,
      });
    }
  };
}

exports.toggleDOMMutationBreakpointState = toggleDOMMutationBreakpointState;
function toggleDOMMutationBreakpointState(id, enabled) {
  assert(typeof id === "string");
  assert(typeof enabled === "boolean");

  return async function ({ getState }) {
    const bp = getDOMMutationBreakpoint(getState(), id);
    if (!bp) {
      throw new Error(`No DOM mutation BP with ID ${id}`);
    }

    const walker = bp.nodeFront.getParent();
    await walker.setMutationBreakpoints(bp.nodeFront, {
      [bp.mutationType]: enabled,
    });
  };
}
