import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { batch, useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import Immutable from 'immutable';
import qs from 'qs';
import get from 'lodash/get';
import { defineMessages, injectIntl, intlShape } from 'react-intl';
import classNames from 'classnames';
import {
  SEARCH_RESULT_GRID_VIEW,
  SEARCH_RESULT_LIST_VIEW,
  SEARCH_RESULT_PAGE_SEARCH_NAME,
  SEARCH_RESULT_TABLE_VIEW,
} from '../../../constants/searchNames';
import SearchResultTitleBar from '../../search/SearchResultTitleBar';
import SearchResultFooter from '../../search/SearchResultFooter';
import SearchResultTable from '../../search/table/SearchTable';
import SearchResultGrid from '../../search/grid/SearchResultGrid';
import SearchDetailList from '../../search/list/SearchList';
import SearchResultSidebar from '../../search/SearchResultSidebar';
import SearchResultSummary from '../../search/SearchResultSummary';
import SortBy from '../../search/SortBy';
import { ToggleButton, ToggleButtonContainer } from '../../search/header/ToggleButtons';
import { useConfig } from '../../config/ConfigProvider';
import styles from '../../../../styles/cspace-ui/SearchResults.css';
import buttonBarStyles from '../../../../styles/cspace-ui/ButtonBar.css';

import {
  setSearchPageRecordType,
  setSearchPageVocabulary,
  setSearchResultPagePageSize,
  setSearchResultPageView,
} from '../../../actions/prefs';

import {
  search, setAllResultItemsSelected,
} from '../../../actions/search';
import {
  getSearchResult, isSearchResultSidebarOpen, getSearchSelectedItems, getUserPerms,
  getSearchResultPageView,
} from '../../../reducers';
import SelectBar from '../../search/SelectBar';
import RelateResults from '../../search/RelateResults';
import ExportResults from '../../search/ExportResults';
import {
  getListTypeFromResult,
  createPageSizeChangeHandler,
  extractAdvancedSearchGroupedTerms,
  createSortByHandler,
  createSortDirHandler,
  normalizeSearchQueryParams,
} from '../../../helpers/searchHelpers';
import {
  setSearchPageAdvanced,
  setSearchPageAdvancedLimitBy,
  setSearchPageAdvancedSearchTerms,
  setSearchPageKeyword,
} from '../../../actions/searchPage';

const selectBarPropTypes = {
  toggleBar: PropTypes.object,
  searchResult: PropTypes.instanceOf(Immutable.Map),
  config: PropTypes.object,
  searchDescriptor: PropTypes.instanceOf(Immutable.Map),
};

export function SelectExportRelateToggleBar({
  toggleBar, searchResult, config, searchDescriptor,
}) {
  if (!searchResult) {
    return null;
  }

  const selectedItems = useSelector((state) => getSearchSelectedItems(state,
    SEARCH_RESULT_PAGE_SEARCH_NAME));
  const perms = useSelector((state) => getUserPerms(state));

  const dispatch = useDispatch();

  // button bar (relate/export)
  const exportButton = (
    <ExportResults
      config={config}
      selectedItems={selectedItems}
      searchDescriptor={searchDescriptor}
    />
  );

  const relateButton = (
    <RelateResults
      config={config}
      selectedItems={selectedItems}
      searchDescriptor={searchDescriptor}
      perms={perms}
      disabled={false}
      key="relate"
    />
  );

  const buttonBar = (
    <div className={buttonBarStyles.common}>
      {relateButton}
      {exportButton}
    </div>
  );

  const listType = getListTypeFromResult(config, searchResult);
  return (
    <SelectBar
      config={config}
      listType={listType}
      searchDescriptor={searchDescriptor}
      searchName={SEARCH_RESULT_PAGE_SEARCH_NAME}
      searchResult={searchResult}
      selectedItems={selectedItems}
      setAllItemsSelected={
      (...args) => dispatch(setAllResultItemsSelected(...args))
    }
    >
      {buttonBar}
      {toggleBar}
    </SelectBar>
  );
}

// memoize?
const getSearchDescriptor = (query, props) => {
  const {
    match,
  } = props;

  const {
    params,
  } = match;

  const { view, ...queryWithoutView } = query;

  const searchQuery = {
    ...queryWithoutView,
    p: parseInt(query.p, 10) - 1,
    size: parseInt(query.size, 10),
  };

  const advancedSearchCondition = query.as;

  if (advancedSearchCondition) {
    searchQuery.as = JSON.parse(advancedSearchCondition);
  }

  const searchDescriptor = {
    searchQuery,
  };

  ['recordType', 'vocabulary', 'csid', 'subresource'].forEach((param) => {
    const value = params[param];

    if (typeof value !== 'undefined') {
      searchDescriptor[param] = value;
    }
  });

  return Immutable.fromJS(searchDescriptor);
};

function setPreferredPageSize(props, dispatch) {
  const {
    location,
  } = props;

  const {
    search: searchFromLoc,
  } = location;

  const query = qs.parse(searchFromLoc.substring(1));
  dispatch(setSearchResultPagePageSize(parseInt(query.size, 10)));
}

function normalizeQuery(props, config) {
  const {
    location,
    preferredPageSize,
  } = props;

  const {
    search: searchFromLoc,
  } = location;

  const query = qs.parse(searchFromLoc.substring(1));
  const { normalizedQuery } = normalizeSearchQueryParams(
    query,
    preferredPageSize,
    config.defaultSearchPageSize,
  );

  return normalizedQuery;
}

/**
 * Gets the current view which we want to display. We prioritize
 * query parameters first, then the last view a user was on (in redux),
 * and finally fall back to the table view.
 */
const currentView = (props) => {
  const {
    history,
    location,
  } = props;

  const {
    search: searchFromLoc,
  } = location;

  const preferred = useSelector((state) => getSearchResultPageView(state));

  let current;
  const query = qs.parse(searchFromLoc.substring(1));
  if (query) {
    current = query.view;

    const hasCurrent = current && (
      current === SEARCH_RESULT_GRID_VIEW
      || current === SEARCH_RESULT_LIST_VIEW
      || current === SEARCH_RESULT_TABLE_VIEW
    );

    // update the query parameter to include our view
    if (preferred && !hasCurrent) {
      query.view = preferred;
      history.push({
        pathname: location.pathname,
        search: `?${qs.stringify(query)}`,
        state: location.state,
      });
    }
  }

  return current || preferred || SEARCH_RESULT_TABLE_VIEW;
};

const messages = defineMessages({
  table: {
    id: 'search.result.view.table',
    description: 'The table button label',
    defaultMessage: 'Switch to table view',
  },
  detailList: {
    id: 'search.result.view.detailList',
    description: 'The detailList button label',
    defaultMessage: 'Switch to detail list view',
  },
  grid: {
    id: 'search.result.view.grid',
    description: 'The grid button label',
    defaultMessage: 'Switch to grid view',
  },
});

/**
 * The page for displaying Search Results. Before rendering it first executes the search based on
 * the query parameters provided by encapsulating them in a search descriptor and calling the search
 * action in redux.
 *
 * Currently this is more for the new search views on CollectionObjects which includes a grid and
 * detail based view compared to the older table based view. Ideally this be the only component for
 * displaying search results but we first would need to make sure we only display the views which
 * are supported for a given procedure or authority.
 *
 * @param {*} props
 * @returns the SearchResults page component
 */
function SearchResults(props) {
  const config = useConfig();
  const dispatch = useDispatch();
  const history = useHistory();
  const { intl, location } = props;

  const normalizedQuery = normalizeQuery(props, config);
  const searchDescriptor = getSearchDescriptor(normalizedQuery, props);

  const searchResults = useSelector((state) => getSearchResult(state,
    SEARCH_RESULT_PAGE_SEARCH_NAME,
    searchDescriptor));
  const isSidebarOpen = useSelector((state) => isSearchResultSidebarOpen(state));
  const display = currentView(props);

  useEffect(() => {
    setPreferredPageSize(props, dispatch);
    dispatch(search(config, SEARCH_RESULT_PAGE_SEARCH_NAME, searchDescriptor));
  }, [searchDescriptor.toString()]);

  const handlePageSizeChange = createPageSizeChangeHandler({
    history,
    location,
    dispatch,
    setPreferredPageSize: setSearchResultPagePageSize,
  });

  const handleEditSearchLinkClick = () => {
    // Transfer the search descriptor from this search to the search page. If this search
    // originated from the search page, the original descriptor will be in the location state.
    // Otherwise, build it from the URL params. If present, the search descriptor from the
    // originating search page will be more complete than one constructed from the URL; for
    // example, it will contain fields that are blank, which will have been removed from the
    // URL, to reduce the size.
    const origin = get(location.state, 'originSearchPage');
    const conditionalSearchDescriptor = origin
      ? Immutable.fromJS(origin.searchDescriptor)
      : getSearchDescriptor(normalizedQuery, props);

    const searchQuery = conditionalSearchDescriptor.get('searchQuery');

    batch(() => {
      dispatch(setSearchPageRecordType(conditionalSearchDescriptor.get('recordType')));
      dispatch(setSearchPageVocabulary(conditionalSearchDescriptor.get('vocabulary')));
      dispatch(setSearchPageKeyword(searchQuery.get('kw')));
      dispatch(setSearchPageAdvanced(searchQuery.get('as')));
      dispatch(setSearchPageAdvancedLimitBy(extractAdvancedSearchGroupedTerms(searchQuery.get('as')).limitBy));
      dispatch(setSearchPageAdvancedSearchTerms(extractAdvancedSearchGroupedTerms(searchQuery.get('as')).searchTerms));
    });
  };

  const handleSortChange = createSortByHandler({ history, location });
  const handleSortDirChange = createSortDirHandler({ history, location });

  const renderSortBy = () => (
    <SortBy
      onSortChange={handleSortChange}
      onSortDirChange={handleSortDirChange}
      sort={normalizedQuery?.sort}
      recordType={searchDescriptor.get('recordType')}
    />
  );

  const gridLabel = intl.formatMessage(messages.grid);
  const tableLabel = intl.formatMessage(messages.table);
  const detailListLabel = intl.formatMessage(messages.detailList);
  const toggles = [
    { key: SEARCH_RESULT_TABLE_VIEW, label: tableLabel, icon: 'format_list_bulleted' },
    { key: SEARCH_RESULT_GRID_VIEW, label: gridLabel, icon: 'grid_view' },
    {
      key: SEARCH_RESULT_LIST_VIEW, label: detailListLabel, icon: 'vertical_split', class: styles.detailList,
    },
  ];

  const updatePageView = (key) => {
    if (history && location) {
      const { search: searchFromLoc } = location;
      const query = qs.parse(searchFromLoc.substring(1));

      query.view = key;
      const queryString = qs.stringify(query);

      history.push({
        pathname: location.pathname,
        search: `?${queryString}`,
        state: location.state,
      });
    }
    dispatch(setSearchResultPageView(key));
  };

  const displayToggles = (
    <ToggleButtonContainer
      items={toggles}
      renderButton={(item) => (
        <ToggleButton
          icon={item.icon}
          key={item.key}
          name={item.key}
          title={item.label}
          className={classNames(styles.toggle, item.class)}
          onClick={() => updatePageView(item.key)}
        />
      )}
    />
  );

  let searchDisplay;
  if (display === SEARCH_RESULT_GRID_VIEW) {
    searchDisplay = <SearchResultGrid searchDescriptor={searchDescriptor} />;
  } else if (display === SEARCH_RESULT_LIST_VIEW) {
    searchDisplay = <SearchDetailList searchDescriptor={searchDescriptor} />;
  } else {
    searchDisplay = <SearchResultTable searchDescriptor={searchDescriptor} />;
  }

  const handleBatchInvokeComplete = () => {
    dispatch(search(config, SEARCH_RESULT_PAGE_SEARCH_NAME, searchDescriptor));
  };

  const sidebar = (
    <SearchResultSidebar
      config={config}
      history={history}
      isOpen={isSidebarOpen}
      onInvokeComplete={handleBatchInvokeComplete}
      recordType={searchDescriptor.get('recordType')}
    />
  );

  return (
    <main className={styles.common}>
      <SearchResultTitleBar
        config={config}
        searchDescriptor={searchDescriptor}
        searchName={SEARCH_RESULT_PAGE_SEARCH_NAME}
        updateDocumentTitle
      />
      <div className={isSidebarOpen ? styles.body : styles.full}>
        {/* SearchResultHeader? */}
        <div className={styles.results}>
          <header>
            <SearchResultSummary
              config={config}
              searchName={SEARCH_RESULT_PAGE_SEARCH_NAME}
              searchDescriptor={searchDescriptor}
              onPageSizeChange={handlePageSizeChange}
              onEditSearchLinkClick={handleEditSearchLinkClick}
              renderSortBy={() => renderSortBy()}
            />
            <SelectExportRelateToggleBar
              toggleBar={displayToggles}
              searchResult={searchResults}
              config={config}
              searchDescriptor={searchDescriptor}
            />
          </header>
          {searchDisplay}
          <SearchResultFooter searchDescriptor={searchDescriptor} />
        </div>
        {sidebar}
      </div>
    </main>
  );
}

const searchResultsPropTypes = {
  intl: intlShape,
  location: PropTypes.object.isRequired,
};

SearchResults.propTypes = searchResultsPropTypes;
SelectExportRelateToggleBar.propTypes = selectBarPropTypes;

export default injectIntl(SearchResults);
