"use strict";

import React from "react";
import PropTypes from "prop-types";
import createReactClass from "create-react-class";
import defined from "terriajs-cesium/Source/Core/defined";
import classNames from "classnames";

import Styles from "./dropdown.scss";

// Use this as drop down rather than the html <select> tag so we have more consistent styling
// Uses the contents of the element as the name of the dropdown if none selected.
const Dropdown = createReactClass({
  propTypes: {
    theme: PropTypes.object,
    options: PropTypes.array, // Must be an array of objects with name properties. Uses <a> when there is an href property, else <button type='button'>.
    selected: PropTypes.object,
    selectOption: PropTypes.func, // The callback function; its arguments are the chosen object and its index.
    textProperty: PropTypes.string, // property to display as text
    matchWidth: PropTypes.bool,
    children: PropTypes.any,
    disabled: PropTypes.bool
  },

  getDefaultProps() {
    return {
      options: [],
      selected: undefined,
      textProperty: "name",
      align: "left",
      disabled: false,
      theme: {}
    };
  },

  getInitialState() {
    return {
      isOpen: false
    };
  },

  /* eslint-disable-next-line camelcase */
  UNSAFE_componentWillMount() {
    // this._element is updated by the ref callback attribute, https://facebook.github.io/react/docs/more-about-refs.html
    this.buttonElement = undefined;
  },

  componentWillUnmount() {
    this.removeListeners();
    this.dismounted = true;
  },

  hideList() {
    this.setState({
      isOpen: false
    });
    this.removeListeners();
  },

  removeListeners() {
    document.body.removeEventListener("click", this.hideList);
    this.buttonElement?.removeEventListener("click", this.nativeButtonListener);
    this.nativeButtonListener = undefined;

    (this.scrollListeners || []).forEach((listenerElement) =>
      listenerElement?.removeEventListener("scroll", this.hideList)
    );
    this.scrollListeners = undefined;
  },

  showList() {
    // Add a listener to every ancestor capable of scrolling that will close the dropdown when this occurs.
    const addScrollListeners = (element, listeningToSoFar) => {
      if (element.scrollHeight > element.clientHeight) {
        element.addEventListener("scroll", this.hideList);
        listeningToSoFar.push(element);
      }

      if (element !== document.body) {
        return addScrollListeners(element.parentNode, listeningToSoFar);
      } else {
        return listeningToSoFar;
      }
    };
    this.scrollListeners = addScrollListeners(this.buttonElement, []);

    this.setState({
      isOpen: true
    });

    // Add the listener to be triggered when a click happens anywhere on the body (including the toggle button)
    // or the outer panel is scrolled.
    document.body.addEventListener("click", this.hideList);

    // Unfortunately we need to add a native event listener because the native event hits document.body before
    // the react event ever gets triggered.
    this.nativeButtonListener = (event) => {
      event.stopPropagation();
      this.hideList();
    };

    this.buttonElement.addEventListener("click", this.nativeButtonListener);
  },

  select(option, index) {
    this.props.selectOption(option, index);
    this.hideList();
  },

  onButtonClicked() {
    if (!this.state.isOpen) {
      this.showList();
    } else {
      this.hideList();
    }
  },

  render() {
    const isOpenStyle = Styles.isOpen + " " + (this.props.theme.isOpen || "");
    return (
      <div className={classNames(Styles.dropdown, this.props.theme.dropdown)}>
        <button
          type="button"
          onClick={this.onButtonClicked}
          className={classNames(this.props.theme.button, Styles.btnDropdown)}
          ref={(element) => {
            this.buttonElement = element;
          }}
          disabled={this.props.disabled}
        >
          {defined(this.props.selected)
            ? this.props.selected[this.props.textProperty]
            : this.props.children}
          {defined(this.props.theme.icon) ? this.props.theme.icon : null}
        </button>
        <ul
          className={classNames(Styles.list, this.props.theme.list, {
            [isOpenStyle]: this.state.isOpen
          })}
        >
          <For each="option" of={this.props.options} index="index">
            <li key={option[this.props.textProperty]}>
              <Choose>
                <When condition={option.href}>
                  <a
                    href={option.href}
                    target="_blank"
                    rel="noopener noreferrer"
                    className={classNames(
                      Styles.btnOption,
                      this.props.theme.btnOption || "",
                      { [Styles.isSelected]: option === this.props.selected }
                    )}
                    download={option.download}
                  >
                    {option[this.props.textProperty]}
                  </a>
                </When>
                <Otherwise>
                  <button
                    type="button"
                    className={classNames(
                      Styles.btnOption,
                      this.props.theme.btnOption || "",
                      { [Styles.isSelected]: option === this.props.selected }
                    )}
                    onClick={() => this.select(option, index)}
                  >
                    {option[this.props.textProperty]}
                  </button>
                </Otherwise>
              </Choose>
            </li>
          </For>
        </ul>
      </div>
    );
  }
});

module.exports = Dropdown;
