/* 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";

// React & Redux
const {
  createFactory,
} = require("resource://devtools/client/shared/vendor/react.js");
const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
const GripMessageBody = require("resource://devtools/client/webconsole/components/Output/GripMessageBody.js");

const {
  MESSAGE_TYPE,
} = require("resource://devtools/client/webconsole/constants.js");

const Message = createFactory(
  require("resource://devtools/client/webconsole/components/Output/Message.js")
);

JSTracerTrace.displayName = "JSTracerTrace";

JSTracerTrace.propTypes = {
  dispatch: PropTypes.func.isRequired,
  message: PropTypes.object.isRequired,
  serviceContainer: PropTypes.object.isRequired,
  timestampsVisible: PropTypes.bool.isRequired,
  maybeScrollToBottom: PropTypes.func,
};

function JSTracerTrace(props) {
  const {
    dispatch,
    message,
    serviceContainer,
    timestampsVisible,
    repeat,
    maybeScrollToBottom,
    setExpanded,
  } = props;

  const {
    // List of common attributes for all tracer messages
    timeStamp,
    prefix,
    depth,
    source,

    // Attribute specific to DOM event
    eventName,

    // Attributes specific to function calls
    frame,
    implementation,
    displayName,
    parameters,

    // Attributes specific to function call returns
    returnedValue,
    relatedTraceId,
    // See tracer.sys.mjs FRAME_EXIT_REASONS
    why,

    // Attributes specific to DOM Mutations
    mutationType,
    mutationElement,
  } = message;

  let messageBodyConfig;
  if (parameters || why || mutationType) {
    messageBodyConfig = {
      dispatch,
      serviceContainer,
      maybeScrollToBottom,
      setExpanded,
      type: "",
      useQuotes: true,

      // Disable custom formatter for now in traces
      customFormat: false,
    };
  }

  // When we are logging a DOM event, we have the `eventName` defined.
  let messageBody;
  if (eventName) {
    messageBody = [
      dom.span({ className: "jstracer-dom-event" }, `DOM | ${eventName}`),
    ];
  } else if (typeof relatedTraceId == "number") {
    messageBody = [
      dom.span({ className: "jstracer-io" }, "⟵ "),
      dom.span({ className: "jstracer-display-name" }, displayName),
    ];
  } else if (mutationType) {
    messageBody = [
      dom.span(
        { className: "jstracer-dom-mutation" },
        // Add an extra space at the end to have nice copy-paste messages
        "— DOM Mutation | " + mutationType + " "
      ),
      formatRep(messageBodyConfig, mutationElement),
    ];
  } else if (displayName) {
    messageBody = [
      dom.span({ className: "jstracer-io" }, "⟶ "),
      dom.span({ className: "jstracer-implementation" }, implementation),
      // Add a space in order to improve copy paste rendering
      dom.span({ className: "jstracer-display-name" }, " " + displayName),
    ];
  } else {
    messageBody = [dom.span({ className: "jstracer-io" }, "—")];
  }

  // Arguments will only be passed on-demand
  if (parameters) {
    messageBody.push("(", ...formatReps(messageBodyConfig, parameters), ")");
  }
  // Returned value will also only be passed on-demand
  if (why) {
    messageBody.push(
      // Add a spaces in order to improve copy paste rendering
      dom.span({ className: "jstracer-exit-frame-reason" }, " " + why + " ")
    );
    if (returnedValue !== undefined) {
      messageBody.push(formatRep(messageBodyConfig, returnedValue));
    }
  }

  if (prefix) {
    messageBody.unshift(
      dom.span(
        {
          className: "console-message-prefix",
        },
        `${prefix}`
      )
    );
  }

  const topLevelClasses = ["cm-s-mozilla"];

  return Message({
    collapsible: false,
    source,
    level: MESSAGE_TYPE.JSTRACER,
    topLevelClasses,
    messageBody,
    repeat,
    frame,
    stacktrace: null,
    attachment: null,
    serviceContainer,
    dispatch,
    indent: depth,
    timeStamp,
    timestampsVisible,
    parameters,
    message,
    maybeScrollToBottom,
  });
}

/**
 * Generated the list of GripMessageBody for a list of objects.
 * GripMessageBody is Rep's rendering for a given Object, via its object actor's front.
 */
function formatReps(messageBodyConfig, objects) {
  const elements = [];
  const length = objects.length;
  for (let i = 0; i < length; i++) {
    elements.push(formatRep(messageBodyConfig, objects[i], i));

    // We need to interleave a comma if we are not on the last element
    if (i !== length - 1) {
      elements.push(", ");
    }
  }

  return elements;
}

function formatRep(messageBodyConfig, grip, key) {
  return GripMessageBody({
    ...messageBodyConfig,
    grip,
    key,
  });
}

module.exports = JSTracerTrace;
