import { last } from 'lodash';
import AvailableDropdownMappings from 'ee_else_ce/filtered_search/available_dropdown_mappings';
import DropLab from '~/droplab/drop_lab';
import { DROPDOWN_TYPE } from './constants';
import FilteredSearchContainer from './container';
import DropdownUtils from './dropdown_utils';
import FilteredSearchTokenKeys from './filtered_search_token_keys';
import FilteredSearchVisualTokens from './filtered_search_visual_tokens';

export default class FilteredSearchDropdownManager {
  constructor({
    runnerTagsEndpoint = '',
    labelsEndpoint = '',
    milestonesEndpoint = '',
    iterationsEndpoint = '',
    releasesEndpoint = '',
    environmentsEndpoint = '',
    epicsEndpoint = '',
    tokenizer,
    page,
    isGroup,
    isGroupAncestor,
    isGroupDecendent,
    filteredSearchTokenKeys,
  }) {
    const removeTrailingSlash = (url) => url.replace(/\/$/, '');

    this.container = FilteredSearchContainer.container;
    this.runnerTagsEndpoint = removeTrailingSlash(runnerTagsEndpoint);
    this.labelsEndpoint = removeTrailingSlash(labelsEndpoint);
    this.milestonesEndpoint = removeTrailingSlash(milestonesEndpoint);
    this.iterationsEndpoint = removeTrailingSlash(iterationsEndpoint);
    this.releasesEndpoint = removeTrailingSlash(releasesEndpoint);
    this.epicsEndpoint = removeTrailingSlash(epicsEndpoint);
    this.environmentsEndpoint = removeTrailingSlash(environmentsEndpoint);
    this.tokenizer = tokenizer;
    this.filteredSearchTokenKeys = filteredSearchTokenKeys || FilteredSearchTokenKeys;
    this.filteredSearchInput = this.container.querySelector('.filtered-search');
    this.page = page;
    this.groupsOnly = isGroup;
    this.includeAncestorGroups = isGroupAncestor;
    this.includeDescendantGroups = isGroupDecendent;

    this.setupMapping();

    this.cleanupWrapper = this.cleanup.bind(this);
    document.addEventListener('beforeunload', this.cleanupWrapper);
  }

  cleanup() {
    if (this.droplab) {
      this.droplab.destroy();
      this.droplab = null;
    }

    this.setupMapping();

    document.removeEventListener('beforeunload', this.cleanupWrapper);
  }

  setupMapping() {
    const supportedTokens = this.filteredSearchTokenKeys.getKeys();

    const availableMappings = new AvailableDropdownMappings({ ...this });

    this.mapping = availableMappings.getAllowedMappings(supportedTokens);
  }

  static addWordToInput({
    tokenName,
    tokenOperator = '',
    tokenValue = '',
    clicked = false,
    options = {},
  }) {
    const { uppercaseTokenName = false, capitalizeTokenValue = false } = options;
    const input = FilteredSearchContainer.container.querySelector('.filtered-search');
    FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenOperator, tokenValue, {
      uppercaseTokenName,
      capitalizeTokenValue,
    });
    input.value = '';

    if (clicked) {
      FilteredSearchVisualTokens.moveInputToTheRight();
    }
  }

  updateCurrentDropdownOffset() {
    this.updateDropdownOffset(this.currentDropdown);
  }

  updateDropdownOffset(key) {
    // Always align dropdown with the input field
    let offset =
      this.filteredSearchInput.getBoundingClientRect().left -
      this.container.querySelector('.scroll-container').getBoundingClientRect().left;

    const maxInputWidth = 240;
    const currentDropdownWidth = this.mapping[key].element.clientWidth || maxInputWidth;

    // Make sure offset never exceeds the input container
    const offsetMaxWidth =
      this.container.querySelector('.scroll-container').clientWidth - currentDropdownWidth;
    if (offsetMaxWidth < offset) {
      offset = offsetMaxWidth;
    }

    this.mapping[key].reference.setOffset(offset);
  }

  load(key, firstLoad = false, dropdownKey = '') {
    const mappingKey = this.mapping[key];
    const glClass = mappingKey.gl;
    const { element } = mappingKey;
    let forceShowList = false;

    if (!mappingKey.reference) {
      const defaultArguments = {
        droplab: this.droplab,
        dropdown: element,
        input: this.filteredSearchInput,
        tokenKeys: this.filteredSearchTokenKeys,
        filter: key,
      };
      const extraArguments = mappingKey.extraArguments || {};
      const glArguments = { ...defaultArguments, ...extraArguments };

      // Passing glArguments to `new glClass(<arguments>)`
      mappingKey.reference = new (Function.prototype.bind.apply(glClass, [null, glArguments]))();
    }

    if (firstLoad) {
      mappingKey.reference.init();
    }

    if (
      this.currentDropdown === DROPDOWN_TYPE.hint ||
      this.currentDropdown === DROPDOWN_TYPE.operator
    ) {
      // Force the dropdown to show if it was clicked from the hint dropdown
      forceShowList = true;
    }

    this.updateDropdownOffset(key);
    mappingKey.reference.render(firstLoad, forceShowList, dropdownKey);

    this.currentDropdown = key;
  }

  loadDropdown(dropdownName = '', dropdownKey = '') {
    let firstLoad = false;

    if (!this.droplab) {
      firstLoad = true;
      this.droplab = new DropLab();
    }

    if (dropdownName === DROPDOWN_TYPE.operator) {
      this.load(dropdownName, firstLoad, dropdownKey);
      return;
    }

    const match = this.filteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase());
    const shouldOpenFilterDropdown =
      match && this.currentDropdown !== match.key && this.mapping[match.key];
    const shouldOpenHintDropdown = !match && this.currentDropdown !== DROPDOWN_TYPE.hint;

    if (shouldOpenFilterDropdown || shouldOpenHintDropdown) {
      const key = match && match.key ? match.key : DROPDOWN_TYPE.hint;

      this.load(key, firstLoad, dropdownKey);
    }
  }

  setDropdown() {
    const query = DropdownUtils.getSearchQuery(true);
    const { lastToken, searchToken } = this.tokenizer.processTokens(
      query,
      this.filteredSearchTokenKeys.getKeys(),
    );

    if (this.currentDropdown) {
      this.updateCurrentDropdownOffset();
    }
    if (lastToken === searchToken && lastToken !== null) {
      // Token is not fully initialized yet because it has no value
      // Eg. token = 'label:'

      const split = lastToken.split(':');
      const dropdownName = last(split[0].split(' '));
      const possibleOperatorToken = last(split[1]);

      const hasOperator = FilteredSearchVisualTokens.permissibleOperatorValues.includes(
        possibleOperatorToken && possibleOperatorToken.trim(),
      );

      let dropdownToOpen = '';

      if (split.length > 1) {
        const lastOperatorToken = FilteredSearchVisualTokens.getLastTokenOperator();
        dropdownToOpen = hasOperator && lastOperatorToken ? dropdownName : DROPDOWN_TYPE.operator;
      }

      this.loadDropdown(dropdownToOpen, dropdownName);
    } else if (lastToken) {
      const lastOperator = FilteredSearchVisualTokens.getLastTokenOperator();
      // Token has been initialized into an object because it has a value
      this.loadDropdown(lastOperator ? lastToken.key : DROPDOWN_TYPE.operator, lastToken.key);
    } else {
      this.loadDropdown(DROPDOWN_TYPE.hint);
    }
  }

  resetDropdowns() {
    if (!this.currentDropdown) {
      return;
    }

    // Force current dropdown to hide
    this.mapping[this.currentDropdown].reference.hideDropdown();

    // Re-Load dropdown
    this.setDropdown();

    // Reset filters for current dropdown
    this.mapping[this.currentDropdown].reference.resetFilters();

    // Reposition dropdown so that it is aligned with cursor
    this.updateDropdownOffset(this.currentDropdown);
  }

  destroyDroplab() {
    this.droplab.destroy();
  }
}
