/* 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 {
  Component,
} = require("resource://devtools/client/shared/vendor/react.js");
const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");

class SearchBoxAutocompletePopup extends Component {
  static get propTypes() {
    return {
      /**
       * autocompleteProvider takes search-box's entire input text as `filter` argument
       * ie. "is:cached pr"
       * returned value is array of objects like below
       * [{value: "is:cached protocol", displayValue: "protocol"}[, ...]]
       * `value` is used to update the search-box input box for given item
       * `displayValue` is used to render the autocomplete list
       */
      autocompleteProvider: PropTypes.func.isRequired,
      filter: PropTypes.string.isRequired,
      onItemSelected: PropTypes.func.isRequired,
    };
  }

  constructor(props, context) {
    super(props, context);
    this.state = this.computeState(props);
    this.computeState = this.computeState.bind(this);
    this.jumpToTop = this.jumpToTop.bind(this);
    this.jumpToBottom = this.jumpToBottom.bind(this);
    this.jumpBy = this.jumpBy.bind(this);
    this.select = this.select.bind(this);
    this.onMouseDown = this.onMouseDown.bind(this);
  }

  // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507
  UNSAFE_componentWillReceiveProps(nextProps) {
    if (this.props.filter === nextProps.filter) {
      return;
    }
    this.setState(this.computeState(nextProps));
  }

  componentDidUpdate() {
    if (this.refs.selected) {
      this.refs.selected.scrollIntoView(false);
    }
  }

  computeState({ autocompleteProvider, filter }) {
    const list = autocompleteProvider(filter);
    const selectedIndex = list.length ? 0 : -1;

    return { list, selectedIndex };
  }

  /**
   * Use this method to select the top-most item
   * This method is public, called outside of the autocomplete-popup component.
   */
  jumpToTop() {
    this.setState({ selectedIndex: 0 });
  }

  /**
   * Use this method to select the bottom-most item
   * This method is public.
   */
  jumpToBottom() {
    this.setState({ selectedIndex: this.state.list.length - 1 });
  }

  /**
   * Increment the selected index with the provided increment value. Will cycle to the
   * beginning/end of the list if the index exceeds the list boundaries.
   * This method is public.
   *
   * @param {number} increment - No. of hops in the direction
   */
  jumpBy(increment = 1) {
    const { list, selectedIndex } = this.state;
    let nextIndex = selectedIndex + increment;
    if (increment > 0) {
      // Positive cycling
      nextIndex = nextIndex > list.length - 1 ? 0 : nextIndex;
    } else if (increment < 0) {
      // Inverse cycling
      nextIndex = nextIndex < 0 ? list.length - 1 : nextIndex;
    }
    this.setState({ selectedIndex: nextIndex });
  }

  /**
   * Submit the currently selected item to the onItemSelected callback
   * This method is public.
   */
  select() {
    if (this.refs.selected) {
      this.props.onItemSelected(this.refs.selected.dataset.value);
    }
  }

  onMouseDown(e) {
    e.preventDefault();
    this.setState(
      { selectedIndex: Number(e.target.dataset.index) },
      this.select
    );
  }

  render() {
    const { list } = this.state;

    return (
      !!list.length &&
      dom.div(
        { className: "devtools-autocomplete-popup devtools-monospace" },
        dom.ul(
          { className: "devtools-autocomplete-listbox" },
          list.map((item, i) => {
            const isSelected = this.state.selectedIndex == i;
            const itemClassList = ["autocomplete-item"];

            if (isSelected) {
              itemClassList.push("autocomplete-selected");
            }
            return dom.li(
              {
                key: i,
                "data-index": i,
                "data-value": item.value,
                className: itemClassList.join(" "),
                ref: isSelected ? "selected" : null,
                onMouseDown: this.onMouseDown,
              },
              item.displayValue
            );
          })
        )
      )
    );
  }
}

module.exports = SearchBoxAutocompletePopup;
