/* 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 {
  PureComponent,
  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 {
  CONNECTION_TYPES,
} = require("resource://devtools/client/shared/remote-debugging/constants.js");
const DESCRIPTOR_TYPES = require("resource://devtools/client/fronts/descriptors/descriptor-types.js");
const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
const Localized = createFactory(FluentReact.Localized);

/**
 * This is header that should be displayed on top of the toolbox when using
 * about:devtools-toolbox.
 */
class DebugTargetInfo extends PureComponent {
  static get propTypes() {
    return {
      alwaysOnTop: PropTypes.bool.isRequired,
      focusedState: PropTypes.bool,
      toggleAlwaysOnTop: PropTypes.func.isRequired,
      debugTargetData: PropTypes.shape({
        connectionType: PropTypes.oneOf(Object.values(CONNECTION_TYPES))
          .isRequired,
        runtimeInfo: PropTypes.shape({
          deviceName: PropTypes.string,
          icon: PropTypes.string.isRequired,
          name: PropTypes.string.isRequired,
          version: PropTypes.string.isRequired,
        }).isRequired,
        descriptorType: PropTypes.oneOf(Object.values(DESCRIPTOR_TYPES))
          .isRequired,
        descriptorName: PropTypes.string.isRequired,
      }).isRequired,
      L10N: PropTypes.object.isRequired,
      toolbox: PropTypes.object.isRequired,
    };
  }

  constructor(props) {
    super(props);

    this.state = { urlValue: props.toolbox.target.url };

    this.onChange = this.onChange.bind(this);
    this.onFocus = this.onFocus.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
  }

  componentDidMount() {
    this.updateTitle();
  }

  updateTitle() {
    const { L10N, debugTargetData, toolbox } = this.props;
    const title = toolbox.target.name;
    const descriptorTypeStr = L10N.getStr(
      this.getAssetsForDebugDescriptorType().l10nId
    );

    const { connectionType } = debugTargetData;
    if (connectionType === CONNECTION_TYPES.THIS_FIREFOX) {
      toolbox.doc.title = L10N.getFormatStr(
        "toolbox.debugTargetInfo.tabTitleLocal",
        descriptorTypeStr,
        title
      );
    } else {
      const connectionTypeStr = L10N.getStr(
        this.getAssetsForConnectionType().l10nId
      );
      toolbox.doc.title = L10N.getFormatStr(
        "toolbox.debugTargetInfo.tabTitleRemote",
        connectionTypeStr,
        descriptorTypeStr,
        title
      );
    }
  }

  getRuntimeText() {
    const { debugTargetData, L10N } = this.props;
    const { name, version } = debugTargetData.runtimeInfo;
    const { connectionType } = debugTargetData;
    const brandShorterName = L10N.getStr("brandShorterName");

    return connectionType === CONNECTION_TYPES.THIS_FIREFOX
      ? L10N.getFormatStr(
          "toolbox.debugTargetInfo.runtimeLabel.thisRuntime",
          brandShorterName,
          version
        )
      : L10N.getFormatStr(
          "toolbox.debugTargetInfo.runtimeLabel",
          name,
          version
        );
  }

  getAssetsForConnectionType() {
    const { connectionType } = this.props.debugTargetData;

    switch (connectionType) {
      case CONNECTION_TYPES.USB:
        return {
          image: "chrome://devtools/skin/images/aboutdebugging-usb-icon.svg",
          l10nId: "toolbox.debugTargetInfo.connection.usb",
        };
      case CONNECTION_TYPES.NETWORK:
        return {
          image: "chrome://devtools/skin/images/aboutdebugging-globe-icon.svg",
          l10nId: "toolbox.debugTargetInfo.connection.network",
        };
      default:
        return {};
    }
  }

  getAssetsForDebugDescriptorType() {
    const { descriptorType } = this.props.debugTargetData;

    // TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=1520723
    //       Show actual favicon (currently toolbox.target.activeTab.favicon
    //       is unpopulated)
    const favicon = "chrome://devtools/skin/images/globe.svg";

    switch (descriptorType) {
      case DESCRIPTOR_TYPES.EXTENSION:
        return {
          image: "chrome://devtools/skin/images/debugging-addons.svg",
          l10nId: "toolbox.debugTargetInfo.targetType.extension",
        };
      case DESCRIPTOR_TYPES.PROCESS:
        return {
          image: "chrome://devtools/skin/images/settings.svg",
          l10nId: "toolbox.debugTargetInfo.targetType.process",
        };
      case DESCRIPTOR_TYPES.TAB:
        return {
          image: favicon,
          l10nId: "toolbox.debugTargetInfo.targetType.tab",
        };
      case DESCRIPTOR_TYPES.WORKER:
        return {
          image: "chrome://devtools/skin/images/debugging-workers.svg",
          l10nId: "toolbox.debugTargetInfo.targetType.worker",
        };
      default:
        return {};
    }
  }

  onChange({ target }) {
    this.setState({ urlValue: target.value });
  }

  onFocus({ target }) {
    target.select();
  }

  onSubmit(event) {
    event.preventDefault();
    let url = this.state.urlValue;

    if (!url || !url.length) {
      return;
    }

    try {
      // Get the URL from the fixup service:
      const flags = Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS;
      const uriInfo = Services.uriFixup.getFixupURIInfo(url, flags);
      url = uriInfo.fixedURI.spec;
    } catch (ex) {
      // The getFixupURIInfo service will throw an error if a malformed URI is
      // produced from the input.
      console.error(ex);
    }

    // Do not waitForLoad as we don't wait navigateTo to resolve anyway.
    // Bug 1968023: navigateTo is flaky and sometimes never catches the
    // STATE_STOP notification necessary for waitForLoad=true.
    this.props.toolbox.commands.targetCommand.navigateTo(
      url,
      false /* waitForLoad */
    );
  }

  shallRenderConnection() {
    const { connectionType } = this.props.debugTargetData;
    const renderableTypes = [CONNECTION_TYPES.USB, CONNECTION_TYPES.NETWORK];

    return renderableTypes.includes(connectionType);
  }

  renderConnection() {
    const { connectionType } = this.props.debugTargetData;
    const { image, l10nId } = this.getAssetsForConnectionType();

    return dom.span(
      {
        className: "iconized-label qa-connection-info",
      },
      dom.img({ src: image, alt: `${connectionType} icon` }),
      this.props.L10N.getStr(l10nId)
    );
  }

  renderRuntime() {
    if (
      !this.props.debugTargetData.runtimeInfo ||
      (this.props.debugTargetData.connectionType ===
        CONNECTION_TYPES.THIS_FIREFOX &&
        this.props.debugTargetData.descriptorType ===
          DESCRIPTOR_TYPES.EXTENSION)
    ) {
      // Skip the runtime render if no runtimeInfo is available.
      // Runtime info is retrieved from the remote-client-manager, which might not be
      // setup if about:devtools-toolbox was not opened from about:debugging.
      //
      // Also skip the runtime if we are debugging firefox itself, mainly to save some space.
      return null;
    }

    const { icon, deviceName } = this.props.debugTargetData.runtimeInfo;

    return dom.span(
      {
        className: "iconized-label qa-runtime-info",
      },
      dom.img({ src: icon, className: "channel-icon qa-runtime-icon" }),
      dom.b({ className: "devtools-ellipsis-text" }, this.getRuntimeText()),
      dom.span({ className: "devtools-ellipsis-text" }, deviceName)
    );
  }

  renderDescriptorName() {
    const name = this.props.debugTargetData.descriptorName;

    const { image, l10nId } = this.getAssetsForDebugDescriptorType();

    return dom.span(
      {
        className: "iconized-label debug-descriptor-title",
      },
      dom.img({ src: image, alt: this.props.L10N.getStr(l10nId) }),
      name
        ? dom.b(
            { className: "devtools-ellipsis-text qa-descriptor-title" },
            name
          )
        : null
    );
  }

  renderTargetURI() {
    const { descriptorType } = this.props.debugTargetData;
    const { url } = this.props.toolbox.target;
    const isWebExtension = descriptorType === DESCRIPTOR_TYPES.EXTENSION;

    // Avoid displaying the target url for web extension as it is always
    // the fallback document URL. Keeps rendering the url component
    // as it use flex to align the "always on top" button on the right.
    if (isWebExtension) {
      return dom.span({
        className: "debug-target-url",
      });
    }

    const isURLEditable = descriptorType === DESCRIPTOR_TYPES.TAB;

    return dom.span(
      {
        key: url,
        className: "debug-target-url",
      },
      isURLEditable
        ? this.renderTargetInput(url)
        : dom.span(
            { className: "debug-target-url-readonly devtools-ellipsis-text" },
            url
          )
    );
  }

  renderTargetInput(url) {
    return dom.form(
      {
        className: "debug-target-url-form",
        onSubmit: this.onSubmit,
      },
      dom.input({
        className: "devtools-textinput debug-target-url-input",
        onChange: this.onChange,
        onFocus: this.onFocus,
        defaultValue: url,
      })
    );
  }

  renderAlwaysOnTopButton() {
    // This is only displayed for local web extension debugging
    const { descriptorType, connectionType } = this.props.debugTargetData;
    const isLocalWebExtension =
      descriptorType === DESCRIPTOR_TYPES.EXTENSION &&
      connectionType === CONNECTION_TYPES.THIS_FIREFOX;
    if (!isLocalWebExtension) {
      return [];
    }

    const checked = this.props.alwaysOnTop;
    const toolboxFocused = this.props.focusedState;
    return [
      Localized(
        {
          id: checked
            ? "toolbox-always-on-top-enabled2"
            : "toolbox-always-on-top-disabled2",
          attrs: { title: true },
        },
        dom.button({
          className:
            `toolbox-always-on-top` +
            (checked ? " checked" : "") +
            (toolboxFocused ? " toolbox-is-focused" : ""),
          onClick: this.props.toggleAlwaysOnTop,
        })
      ),
    ];
  }

  renderNavigationButton(detail) {
    const { L10N } = this.props;

    return dom.button(
      {
        className: `iconized-label navigation-button ${detail.className}`,
        onClick: detail.onClick,
        title: L10N.getStr(detail.l10nId),
      },
      dom.img({
        src: detail.icon,
        alt: L10N.getStr(detail.l10nId),
      })
    );
  }

  renderNavigation() {
    const { debugTargetData } = this.props;
    const { descriptorType } = debugTargetData;

    if (
      descriptorType !== DESCRIPTOR_TYPES.TAB &&
      descriptorType !== DESCRIPTOR_TYPES.EXTENSION
    ) {
      return null;
    }

    const items = [];

    // There is little value in exposing back/forward for WebExtensions
    if (
      this.props.toolbox.target.getTrait("navigation") &&
      descriptorType === DESCRIPTOR_TYPES.TAB
    ) {
      items.push(
        this.renderNavigationButton({
          className: "qa-back-button",
          icon: "chrome://browser/skin/back.svg",
          l10nId: "toolbox.debugTargetInfo.back",
          onClick: () => this.props.toolbox.commands.targetCommand.goBack(),
        }),
        this.renderNavigationButton({
          className: "qa-forward-button",
          icon: "chrome://browser/skin/forward.svg",
          l10nId: "toolbox.debugTargetInfo.forward",
          onClick: () => this.props.toolbox.commands.targetCommand.goForward(),
        })
      );
    }

    items.push(
      this.renderNavigationButton({
        className: "qa-reload-button",
        icon: "chrome://global/skin/icons/reload.svg",
        l10nId: "toolbox.debugTargetInfo.reload",
        onClick: () => this.props.toolbox.reload(),
      })
    );

    return dom.div(
      {
        className: "debug-target-navigation",
      },
      ...items
    );
  }

  render() {
    return dom.header(
      {
        className: "debug-target-info qa-debug-target-info",
      },
      this.shallRenderConnection() ? this.renderConnection() : null,
      this.renderRuntime(),
      this.renderDescriptorName(),
      this.renderNavigation(),
      this.renderTargetURI(),
      ...this.renderAlwaysOnTopButton()
    );
  }
}

module.exports = DebugTargetInfo;
