/* 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 { LocalizationHelper } = require("devtools/shared/l10n");
const STRINGS_URI = "devtools/client/locales/jit-optimizations.properties";
const L10N = new LocalizationHelper(STRINGS_URI);

const {PluralForm} = require("devtools/shared/plural-form");
const { DOM: dom, PropTypes, createClass, createFactory } = require("devtools/client/shared/vendor/react");
const Frame = createFactory(require("devtools/client/shared/components/frame"));
const PROPNAME_MAX_LENGTH = 4;
// If TREE_ROW_HEIGHT changes, be sure to change `var(--jit-tree-row-height)`
// in `devtools/client/themes/jit-optimizations.css`
const TREE_ROW_HEIGHT = 14;

const OPTIMIZATION_ITEM_TYPES = ["site", "attempts", "types", "attempt", "type",
                                 "observedtype"];

/* eslint-disable no-unused-vars */
/**
 * TODO - Re-enable this eslint rule. The JIT tool is a work in progress, and isn't fully
 *        integrated as of yet.
 */
const {
  JITOptimizations, hasSuccessfulOutcome, isSuccessfulOutcome
} = require("devtools/client/performance/modules/logic/jit");
const OPTIMIZATION_FAILURE = L10N.getStr("jit.optimizationFailure");
const JIT_SAMPLES = L10N.getStr("jit.samples");
const JIT_TYPES = L10N.getStr("jit.types");
const JIT_ATTEMPTS = L10N.getStr("jit.attempts");
/* eslint-enable no-unused-vars */

const JITOptimizationsItem = createClass({
  displayName: "JITOptimizationsItem",

  propTypes: {
    onViewSourceInDebugger: PropTypes.func.isRequired,
    frameData: PropTypes.object.isRequired,
    type: PropTypes.oneOf(OPTIMIZATION_ITEM_TYPES).isRequired,
  },

  _renderSite({ item: site, onViewSourceInDebugger, frameData }) {
    let attempts = site.data.attempts;
    let lastStrategy = attempts[attempts.length - 1].strategy;
    let propString = "";
    let propertyName = site.data.propertyName;

    // Display property name if it exists
    if (propertyName) {
      if (propertyName.length > PROPNAME_MAX_LENGTH) {
        propString = ` (.${propertyName.substr(0, PROPNAME_MAX_LENGTH)}…)`;
      } else {
        propString = ` (.${propertyName})`;
      }
    }

    let sampleString = PluralForm.get(site.samples, JIT_SAMPLES)
      .replace("#1", site.samples);
    let text = dom.span(
      { className: "optimization-site-title" },
      `${lastStrategy}${propString} – (${sampleString})`
    );
    let frame = Frame({
      onClick: () => onViewSourceInDebugger(frameData.url, site.data.line),
      frame: {
        source: frameData.url,
        line: +site.data.line,
        column: site.data.column,
      }
    });
    let children = [text, frame];

    if (!hasSuccessfulOutcome(site)) {
      children.unshift(dom.span({ className: "opt-icon warning" }));
    }

    return dom.span({ className: "optimization-site" }, ...children);
  },

  _renderAttempts({ item: attempts }) {
    return dom.span({ className: "optimization-attempts" },
      `${JIT_ATTEMPTS} (${attempts.length})`
    );
  },

  _renderTypes({ item: types }) {
    return dom.span({ className: "optimization-types" },
      `${JIT_TYPES} (${types.length})`
    );
  },

  _renderAttempt({ item: attempt }) {
    let success = isSuccessfulOutcome(attempt.outcome);
    let { strategy, outcome } = attempt;
    return dom.span({ className: "optimization-attempt" },
      dom.span({ className: "optimization-strategy" }, strategy),
      " → ",
      dom.span({ className: `optimization-outcome ${success ? "success" : "failure"}` },
               outcome)
    );
  },

  _renderType({ item: type }) {
    return dom.span({ className: "optimization-ion-type" },
                    `${type.site}:${type.mirType}`);
  },

  _renderObservedType({ onViewSourceInDebugger, item: type }) {
    let children = [
      dom.span({ className: "optimization-observed-type-keyed" },
        `${type.keyedBy}${type.name ? ` → ${type.name}` : ""}`)
    ];

    // If we have a line and location, make a link to the debugger
    if (type.location && type.line) {
      children.push(
        Frame({
          onClick: () => onViewSourceInDebugger(type.location, type.line),
          frame: {
            source: type.location,
            line: type.line,
            column: type.column,
          }
        })
      );
    // Otherwise if we just have a location, it's probably just a memory location.
    } else if (type.location) {
      children.push(`@${type.location}`);
    }

    return dom.span({ className: "optimization-observed-type" }, ...children);
  },

  render() {
    /* eslint-disable no-unused-vars */
    /**
     * TODO - Re-enable this eslint rule. The JIT tool is a work in progress, and these
     *        undefined variables may represent intended functionality.
     */
    let {
      depth,
      arrow,
      type,
      // TODO - The following are currently unused.
      item,
      focused,
      frameData,
      onViewSourceInDebugger,
    } = this.props;
    /* eslint-enable no-unused-vars */

    let content;
    switch (type) {
      case "site": content = this._renderSite(this.props); break;
      case "attempts": content = this._renderAttempts(this.props); break;
      case "types": content = this._renderTypes(this.props); break;
      case "attempt": content = this._renderAttempt(this.props); break;
      case "type": content = this._renderType(this.props); break;
      case "observedtype": content = this._renderObservedType(this.props); break;
    }

    return dom.div(
      {
        className: `optimization-tree-item optimization-tree-item-${type}`,
        style: { marginInlineStart: depth * TREE_ROW_HEIGHT }
      },
      arrow,
      content
    );
  },
});

module.exports = JITOptimizationsItem;