/* 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/>. */

import React, { Component } from "devtools/client/shared/vendor/react";
import {
  button,
  div,
  main,
  span,
} from "devtools/client/shared/vendor/react-dom-factories";
import PropTypes from "devtools/client/shared/vendor/react-prop-types";
import { connect } from "devtools/client/shared/vendor/react-redux";
import { prefs } from "../utils/prefs";
import { primaryPaneTabs } from "../constants";
import actions from "../actions/index";
import AccessibleImage from "./shared/AccessibleImage";

import {
  getSelectedLocation,
  getPaneCollapse,
  getActiveSearch,
  getQuickOpenEnabled,
  getOrientation,
  getIsCurrentThreadPaused,
  isMapScopesEnabled,
  getSourceMapErrorForSourceActor,
} from "../selectors/index";
const KeyShortcuts = require("resource://devtools/client/shared/key-shortcuts.js");

const SplitBox = require("resource://devtools/client/shared/components/splitter/SplitBox.js");
const AppErrorBoundary = require("resource://devtools/client/shared/components/AppErrorBoundary.js");

const horizontalLayoutBreakpoint = window.matchMedia("(min-width: 800px)");
const verticalLayoutBreakpoint = window.matchMedia(
  "(min-width: 10px) and (max-width: 799px)"
);

import { ShortcutsModal } from "./ShortcutsModal";
import PrimaryPanes from "./PrimaryPanes/index";
import Editor from "./Editor/index";
import SecondaryPanes from "./SecondaryPanes/index";
import WelcomeBox from "./WelcomeBox";
import EditorTabs from "./Editor/Tabs";
import EditorFooter from "./Editor/Footer";
import QuickOpenModal from "./QuickOpenModal";

class App extends Component {
  #shortcuts;

  constructor(props) {
    super(props);

    // The shortcuts should be built as early as possible because they are
    // exposed via getChildContext.
    this.#shortcuts = new KeyShortcuts({ window });

    this.state = {
      shortcutsModalEnabled: false,
      startPanelSize: 0,
      endPanelSize: 0,
    };
  }

  static get propTypes() {
    return {
      activeSearch: PropTypes.oneOf(["file", "project"]),
      closeActiveSearch: PropTypes.func.isRequired,
      closeQuickOpen: PropTypes.func.isRequired,
      endPanelCollapsed: PropTypes.bool.isRequired,
      fluentBundles: PropTypes.array.isRequired,
      openQuickOpen: PropTypes.func.isRequired,
      orientation: PropTypes.oneOf(["horizontal", "vertical"]).isRequired,
      quickOpenEnabled: PropTypes.bool.isRequired,
      showWelcomeBox: PropTypes.bool.isRequired,
      setActiveSearch: PropTypes.func.isRequired,
      setOrientation: PropTypes.func.isRequired,
      setPrimaryPaneTab: PropTypes.func.isRequired,
      startPanelCollapsed: PropTypes.bool.isRequired,
      toolboxDoc: PropTypes.object.isRequired,
      showOriginalVariableMappingWarning: PropTypes.bool,
    };
  }

  getChildContext() {
    return {
      fluentBundles: this.props.fluentBundles,
      toolboxDoc: this.props.toolboxDoc,
      shortcuts: this.#shortcuts,
      l10n: L10N,
    };
  }

  componentDidMount() {
    horizontalLayoutBreakpoint.addListener(this.onLayoutChange);
    verticalLayoutBreakpoint.addListener(this.onLayoutChange);
    this.setOrientation();

    this.#shortcuts.on(L10N.getStr("symbolSearch.search.key2"), e =>
      this.toggleQuickOpenModal(e, "@")
    );

    [
      L10N.getStr("sources.search.key2"),
      L10N.getStr("sources.search.alt.key"),
    ].forEach(key => this.#shortcuts.on(key, this.toggleQuickOpenModal));

    [
      L10N.getStr("gotoLineModal.key3"),
      // For consistency with sourceeditor and codemirror5 shortcuts, map
      // sourceeditor jumpToLine command key.
      `CmdOrCtrl+${L10N.getStr("jumpToLine.commandkey")}`,
    ].forEach(key => this.#shortcuts.on(key, this.toggleJumpToLine));

    this.#shortcuts.on(
      L10N.getStr("projectTextSearch.key"),
      this.jumpToProjectSearch
    );

    this.#shortcuts.on("Escape", this.onEscape);
    this.#shortcuts.on("CmdOrCtrl+/", this.onCommandSlash);
  }

  componentWillUnmount() {
    horizontalLayoutBreakpoint.removeListener(this.onLayoutChange);
    verticalLayoutBreakpoint.removeListener(this.onLayoutChange);
    this.#shortcuts.destroy();
  }

  jumpToProjectSearch = e => {
    e.preventDefault();
    this.props.setPrimaryPaneTab(primaryPaneTabs.PROJECT_SEARCH);
    this.props.setActiveSearch(primaryPaneTabs.PROJECT_SEARCH);
  };

  onEscape = e => {
    const {
      activeSearch,
      closeActiveSearch,
      closeQuickOpen,
      quickOpenEnabled,
    } = this.props;
    const { shortcutsModalEnabled } = this.state;

    if (activeSearch) {
      e.preventDefault();
      closeActiveSearch();
    }

    if (quickOpenEnabled) {
      e.preventDefault();
      closeQuickOpen();
    }

    if (shortcutsModalEnabled) {
      e.preventDefault();
      this.toggleShortcutsModal();
    }
  };

  onCommandSlash = () => {
    this.toggleShortcutsModal();
  };

  isHorizontal() {
    return this.props.orientation === "horizontal";
  }

  toggleJumpToLine = e => {
    this.toggleQuickOpenModal(e, ":");
  };

  toggleQuickOpenModal = (e, query) => {
    const { quickOpenEnabled, openQuickOpen, closeQuickOpen } = this.props;

    e.preventDefault();
    e.stopPropagation();

    if (quickOpenEnabled === true) {
      closeQuickOpen();
      return;
    }

    if (query != null) {
      openQuickOpen(query);
      return;
    }
    openQuickOpen();
  };

  onLayoutChange = () => {
    this.setOrientation();
  };

  setOrientation() {
    // If the orientation does not match (if it is not visible) it will
    // not setOrientation, or if it is the same as before, calling
    // setOrientation will not cause a rerender.
    if (horizontalLayoutBreakpoint.matches) {
      this.props.setOrientation("horizontal");
    } else if (verticalLayoutBreakpoint.matches) {
      this.props.setOrientation("vertical");
    }
  }

  closeSourceMapError = () => {
    this.setState({ hiddenSourceMapError: this.props.sourceMapError });
  };

  renderEditorNotificationBar() {
    if (
      this.props.sourceMapError &&
      this.state.hiddenSourceMapError != this.props.sourceMapError
    ) {
      return div(
        { className: "editor-notification-footer", "aria-role": "status" },
        span(
          { className: "info icon" },
          React.createElement(AccessibleImage, { className: "sourcemap" })
        ),
        `Source Map Error: ${this.props.sourceMapError}`,
        button({ className: "close-button", onClick: this.closeSourceMapError })
      );
    }
    if (this.props.showOriginalVariableMappingWarning) {
      return div(
        { className: "editor-notification-footer", "aria-role": "status" },
        span(
          { className: "info icon" },
          React.createElement(AccessibleImage, { className: "sourcemap" })
        ),
        L10N.getFormatStr(
          "editorNotificationFooter.noOriginalScopes",
          L10N.getStr("scopes.showOriginalScopes")
        )
      );
    }
    return null;
  }

  renderEditorPane = () => {
    const { startPanelCollapsed, endPanelCollapsed } = this.props;
    const { endPanelSize, startPanelSize } = this.state;
    const horizontal = this.isHorizontal();
    return main(
      {
        className: "editor-pane",
      },
      div(
        {
          className: "editor-container",
        },
        React.createElement(EditorTabs, {
          startPanelCollapsed,
          endPanelCollapsed,
          horizontal,
        }),
        React.createElement(Editor, {
          startPanelSize,
          endPanelSize,
        }),
        this.props.showWelcomeBox
          ? React.createElement(WelcomeBox, {
              horizontal,
              toggleShortcutsModal: () => this.toggleShortcutsModal(),
            })
          : null,
        this.renderEditorNotificationBar(),
        React.createElement(EditorFooter, {
          horizontal,
        })
      )
    );
  };

  toggleShortcutsModal() {
    this.setState(prevState => ({
      shortcutsModalEnabled: !prevState.shortcutsModalEnabled,
    }));
  }

  // Important so that the tabs chevron updates appropriately when
  // the user resizes the left or right columns
  triggerEditorPaneResize() {
    const editorPane = window.document.querySelector(".editor-pane");
    if (editorPane) {
      editorPane.dispatchEvent(new Event("resizeend"));
    }
  }

  renderLayout = () => {
    const { startPanelCollapsed, endPanelCollapsed } = this.props;
    const horizontal = this.isHorizontal();
    return React.createElement(SplitBox, {
      style: {
        width: "100vw",
      },
      initialSize: prefs.endPanelSize,
      minSize: 30,
      maxSize: "70%",
      splitterSize: 1,
      vert: horizontal,
      onResizeEnd: num => {
        prefs.endPanelSize = num;
        this.triggerEditorPaneResize();
      },
      startPanel: React.createElement(SplitBox, {
        style: {
          width: "100vw",
        },
        initialSize: prefs.startPanelSize,
        minSize: 30,
        maxSize: "85%",
        splitterSize: 1,
        onResizeEnd: num => {
          prefs.startPanelSize = num;
          this.triggerEditorPaneResize();
        },
        startPanelCollapsed,
        startPanel: React.createElement(PrimaryPanes, {
          horizontal,
        }),
        endPanel: this.renderEditorPane(),
      }),
      endPanelControl: true,
      endPanel: React.createElement(SecondaryPanes, {
        horizontal,
      }),
      endPanelCollapsed,
    });
  };

  render() {
    const { quickOpenEnabled } = this.props;
    return div(
      {
        className: "debugger",
      },
      React.createElement(
        AppErrorBoundary,
        {
          componentName: "Debugger",
          panel: L10N.getStr("ToolboxDebugger.label"),
        },
        this.renderLayout(),
        quickOpenEnabled === true &&
          React.createElement(QuickOpenModal, {
            shortcutsModalEnabled: this.state.shortcutsModalEnabled,
            toggleShortcutsModal: () => this.toggleShortcutsModal(),
          }),
        React.createElement(ShortcutsModal, {
          enabled: this.state.shortcutsModalEnabled,
          handleClose: () => this.toggleShortcutsModal(),
        })
      )
    );
  }
}

App.childContextTypes = {
  toolboxDoc: PropTypes.object,
  shortcuts: PropTypes.object,
  l10n: PropTypes.object,
  fluentBundles: PropTypes.array,
};

const mapStateToProps = state => {
  const selectedLocation = getSelectedLocation(state);
  const mapScopeEnabled = isMapScopesEnabled(state);
  const isPaused = getIsCurrentThreadPaused(state);

  const showOriginalVariableMappingWarning =
    isPaused &&
    selectedLocation?.source.isOriginal &&
    !selectedLocation?.source.isPrettyPrinted &&
    !mapScopeEnabled;

  return {
    showOriginalVariableMappingWarning,
    showWelcomeBox: !selectedLocation,
    startPanelCollapsed: getPaneCollapse(state, "start"),
    endPanelCollapsed: getPaneCollapse(state, "end"),
    activeSearch: getActiveSearch(state),
    quickOpenEnabled: getQuickOpenEnabled(state),
    orientation: getOrientation(state),
    sourceMapError: selectedLocation?.sourceActor
      ? getSourceMapErrorForSourceActor(state, selectedLocation.sourceActor.id)
      : null,
  };
};

export default connect(mapStateToProps, {
  setActiveSearch: actions.setActiveSearch,
  closeActiveSearch: actions.closeActiveSearch,
  openQuickOpen: actions.openQuickOpen,
  closeQuickOpen: actions.closeQuickOpen,
  setOrientation: actions.setOrientation,
  setPrimaryPaneTab: actions.setPrimaryPaneTab,
})(App);
