/* 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 { isSavedFrame } = require("resource://devtools/shared/DevToolsUtils.js");
const {
  Component,
  createFactory,
} = require("resource://devtools/client/shared/vendor/react.mjs");
const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs");
const {
  L10N,
  formatNumber,
  formatPercent,
} = require("resource://devtools/client/memory/utils.js");
const Frame = createFactory(
  require("resource://devtools/client/shared/components/Frame.js")
);
const {
  TREE_ROW_HEIGHT,
} = require("resource://devtools/client/memory/constants.js");
const models = require("resource://devtools/client/memory/models.js");

class CensusTreeItem extends Component {
  static get propTypes() {
    return {
      arrow: PropTypes.any,
      depth: PropTypes.number.isRequired,
      diffing: models.app.diffing,
      expanded: PropTypes.bool.isRequired,
      focused: PropTypes.bool.isRequired,
      getPercentBytes: PropTypes.func.isRequired,
      getPercentCount: PropTypes.func.isRequired,
      inverted: PropTypes.bool,
      item: PropTypes.object.isRequired,
      onViewIndividuals: PropTypes.func.isRequired,
      onViewSourceInDebugger: PropTypes.func.isRequired,
    };
  }

  constructor(props) {
    super(props);
    this.toLabel = this.toLabel.bind(this);
  }

  shouldComponentUpdate(nextProps) {
    return (
      this.props.item != nextProps.item ||
      this.props.depth != nextProps.depth ||
      this.props.expanded != nextProps.expanded ||
      this.props.focused != nextProps.focused ||
      this.props.diffing != nextProps.diffing
    );
  }

  toLabel(name, onViewSourceInDebugger) {
    if (isSavedFrame(name)) {
      return Frame({
        frame: name,
        onClick: onViewSourceInDebugger,
        showFunctionName: true,
        showHost: true,
      });
    }

    if (name === null) {
      return L10N.getStr("tree-item.root");
    }

    if (name === "noStack") {
      return L10N.getStr("tree-item.nostack");
    }

    if (name === "noFilename") {
      return L10N.getStr("tree-item.nofilename");
    }

    return String(name);
  }

  render() {
    const {
      item,
      depth,
      arrow,
      focused,
      getPercentBytes,
      getPercentCount,
      diffing,
      onViewSourceInDebugger,
      onViewIndividuals,
      inverted,
    } = this.props;

    const bytes = formatNumber(item.bytes, !!diffing);
    const percentBytes = formatPercent(getPercentBytes(item.bytes), !!diffing);

    const count = formatNumber(item.count, !!diffing);
    const percentCount = formatPercent(getPercentCount(item.count), !!diffing);

    const totalBytes = formatNumber(item.totalBytes, !!diffing);
    const percentTotalBytes = formatPercent(
      getPercentBytes(item.totalBytes),
      !!diffing
    );

    const totalCount = formatNumber(item.totalCount, !!diffing);
    const percentTotalCount = formatPercent(
      getPercentCount(item.totalCount),
      !!diffing
    );

    let pointer;
    if (inverted && depth > 0) {
      pointer = dom.span({ className: "children-pointer" }, "↖");
    } else if (!inverted && item.children?.length) {
      pointer = dom.span({ className: "children-pointer" }, "↘");
    }

    let individualsCell;
    if (!diffing) {
      let individualsButton;
      if (item.reportLeafIndex !== undefined) {
        individualsButton = dom.button(
          {
            key: `individuals-button-${item.id}`,
            title: L10N.getStr("tree-item.view-individuals.tooltip"),
            className: "devtools-button individuals-button",
            onClick: e => {
              // Don't let the event bubble up to cause this item to focus after
              // we have switched views, which would lead to assertion failures.
              e.preventDefault();
              e.stopPropagation();

              onViewIndividuals(item);
            },
          },
          "⁂"
        );
      }
      individualsCell = dom.span(
        { className: "heap-tree-item-field heap-tree-item-individuals" },
        individualsButton
      );
    }

    return dom.div(
      { className: `heap-tree-item ${focused ? "focused" : ""}` },
      dom.span(
        { className: "heap-tree-item-field heap-tree-item-bytes" },
        dom.span({ className: "heap-tree-number" }, bytes),
        dom.span({ className: "heap-tree-percent" }, percentBytes)
      ),
      dom.span(
        { className: "heap-tree-item-field heap-tree-item-count" },
        dom.span({ className: "heap-tree-number" }, count),
        dom.span({ className: "heap-tree-percent" }, percentCount)
      ),
      dom.span(
        { className: "heap-tree-item-field heap-tree-item-total-bytes" },
        dom.span({ className: "heap-tree-number" }, totalBytes),
        dom.span({ className: "heap-tree-percent" }, percentTotalBytes)
      ),
      dom.span(
        { className: "heap-tree-item-field heap-tree-item-total-count" },
        dom.span({ className: "heap-tree-number" }, totalCount),
        dom.span({ className: "heap-tree-percent" }, percentTotalCount)
      ),
      individualsCell,
      dom.span(
        {
          className: "heap-tree-item-field heap-tree-item-name",
          style: { marginInlineStart: depth * TREE_ROW_HEIGHT },
        },
        arrow,
        pointer,
        this.toLabel(item.name, onViewSourceInDebugger)
      )
    );
  }
}

module.exports = CensusTreeItem;
