import NylasStore from 'nylas-store';
import {
  Thread,
  Actions,
  ContactStore,
  AccountStore,
  DatabaseStore,
  ComponentRegistry,
  FocusedPerspectiveStore,
  SearchQueryParser,
} from 'nylas-exports';

import SearchActions from './search-actions';
import SearchMailboxPerspective from './search-mailbox-perspective';

// Stores should closely match the needs of a particular part of the front end.
// For example, we might create a "MessageStore" that observes this store
// for changes in selectedThread, "DatabaseStore" for changes to the underlying database,
// and vends up the array used for that view.

class SearchStore extends NylasStore {
  constructor() {
    super();

    this._searchQuery = FocusedPerspectiveStore.current().searchQuery || "";
    this._searchSuggestionsVersion = 1;
    this._isSearching = false;
    this._extensionData = []
    this._clearResults();

    this.listenTo(FocusedPerspectiveStore, this._onPerspectiveChanged);
    this.listenTo(SearchActions.querySubmitted, this._onQuerySubmitted);
    this.listenTo(SearchActions.queryChanged, this._onQueryChanged);
    this.listenTo(SearchActions.searchBlurred, this._onSearchBlurred);
    this.listenTo(SearchActions.searchCompleted, this._onSearchCompleted);
  }

  query() {
    return this._searchQuery;
  }

  queryPopulated() {
    return this._searchQuery && this._searchQuery.trim().length > 0;
  }

  suggestions() {
    return this._suggestions;
  }

  isSearching() {
    return this._isSearching;
  }

  _onSearchCompleted = () => {
    this._isSearching = false;
    this.trigger();
  }

  _onPerspectiveChanged = () => {
    this._searchQuery = FocusedPerspectiveStore.current().searchQuery || "";
    this.trigger();
  }

  _onQueryChanged = (query) => {
    this._searchQuery = query;
    if (this._searchQuery.length <= 1) {
      this.trigger()
      return
    }
    this._compileResults();
    setTimeout(() => this._rebuildResults(), 0);
  }

  _onQuerySubmitted = (query) => {
    this._searchQuery = query;
    const current = FocusedPerspectiveStore.current();

    if (this.queryPopulated()) {
      this._isSearching = true;
      if (this._perspectiveBeforeSearch == null) {
        this._perspectiveBeforeSearch = current;
      }
      const next = new SearchMailboxPerspective(current, this._searchQuery.trim());
      Actions.focusMailboxPerspective(next);
    } else if (current instanceof SearchMailboxPerspective) {
      if (this._perspectiveBeforeSearch) {
        Actions.focusMailboxPerspective(this._perspectiveBeforeSearch);
        this._perspectiveBeforeSearch = null;
      } else {
        Actions.focusDefaultMailboxPerspectiveForAccounts(AccountStore.accounts());
      }
    }

    this._clearResults();
  }

  _onSearchBlurred = () => {
    this._clearResults();
  }

  _clearResults() {
    this._searchSuggestionsVersion = 1;
    this._threadResults = [];
    this._contactResults = [];
    this._suggestions = [];
    this.trigger();
  }

  _rebuildResults() {
    if (!this.queryPopulated()) {
      this._clearResults();
      return;
    }
    this._searchSuggestionsVersion += 1;
    const searchExtensions = ComponentRegistry.findComponentsMatching({
      role: "SearchBarResults",
    })

    Promise.all(searchExtensions.map((ext) => {
      return Promise.props({
        label: ext.searchLabel(),
        suggestions: ext.fetchSearchSuggestions(this._searchQuery),
      })
    })).then((extensionData = []) => {
      this._extensionData = extensionData;
      this._compileResults();
    })

    this._fetchThreadResults();
    this._fetchContactResults();
  }

  _fetchContactResults() {
    const version = this._searchSuggestionsVersion;
    ContactStore.searchContacts(this._searchQuery, {limit: 10}).then(contacts => {
      if (version !== this._searchSuggestionsVersion) {
        return;
      }
      this._contactResults = contacts;
      this._compileResults();
    });
  }

  _fetchThreadResults() {
    if (this._fetchingThreadResultsVersion) { return; }
    this._fetchingThreadResultsVersion = this._searchSuggestionsVersion;

    const {accountIds} = FocusedPerspectiveStore.current();
    let dbQuery = DatabaseStore.findAll(Thread).distinct()
    if (Array.isArray(accountIds) && accountIds.length === 1) {
      dbQuery = dbQuery.where({accountId: accountIds[0]})
    }

    try {
      const parsedQuery = SearchQueryParser.parse(this._searchQuery);
      // console.info('Successfully parsed and codegened search query', parsedQuery);
      dbQuery = dbQuery.structuredSearch(parsedQuery);
    } catch (e) {
      // console.info('Failed to parse local search query, falling back to generic query', e);
      dbQuery = dbQuery.search(this._searchQuery);
    }
    dbQuery = dbQuery
      .order(Thread.attributes.lastMessageReceivedTimestamp.descending())
      .limit(10)

    dbQuery.background().then(results => {
      // We've fetched the latest thread results - display them!
      if (this._searchSuggestionsVersion === this._fetchingThreadResultsVersion) {
        this._fetchingThreadResultsVersion = null;
        this._threadResults = results;
        this._compileResults();
      // We're behind and need to re-run the search for the latest results
      } else if (this._searchSuggestionsVersion > this._fetchingThreadResultsVersion) {
        this._fetchingThreadResultsVersion = null;
        this._fetchThreadResults();
      } else {
        this._fetchingThreadResultsVersion = null;
      }
    }
    );
  }

  _compileResults() {
    this._suggestions = [];

    this._suggestions.push({
      label: `Message Contains: ${this._searchQuery}`,
      value: this._searchQuery,
    });

    if (this._threadResults.length) {
      this._suggestions.push({divider: 'Threads'});
      for (const thread of this._threadResults) {
        this._suggestions.push({thread});
      }
    }

    if (this._contactResults.length) {
      this._suggestions.push({divider: 'People'});
      for (const contact of this._contactResults) {
        this._suggestions.push({
          contact: contact,
          value: contact.email,
        });
      }
    }

    if (this._extensionData.length) {
      for (const {label, suggestions} of this._extensionData) {
        this._suggestions.push({divider: label});
        this._suggestions = this._suggestions.concat(suggestions)
      }
    }

    this.trigger();
  }
}

export default new SearchStore();
