text: import classNames from 'classnames';
  import debounce from 'lodash/debounce';
  import unionWith from 'lodash/unionWith';
  import { array, bool, func, object, oneOf, shape, string } from 'prop-types';
  import React, { Component } from 'react';
  import { injectIntl, intlShape } from 'react-intl';
  import { connect } from 'react-redux';
  import { withRouter } from 'react-router-dom';
  import { compose } from 'redux';
  import { ModalInMobile, Page, SearchMap } from '../../components';
  import config from '../../config';
  import { TopbarContainer } from '../../containers';
  import { getListingsById } from '../../ducks/marketplaceData.duck';
  import { isScrollingDisabled, manageDisableScrolling } from '../../ducks/UI.duck';
  import routeConfiguration from '../../routeConfiguration';
  import { createResourceLocatorString, pathByRouteName } from '../../util/routes';
  import { propTypes } from '../../util/types';
  import { parse, stringify } from '../../util/urlHelpers';
  import MainPanel from './MainPanel';
  import css from './SearchPage.css';
  import { searchListings, searchMapListings, setActiveListing } from './SearchPage.duck';
  import {
    createSearchResultSchema,
    pickSearchParamsOnly,
    validFilterParams,
    validURLParamsForExtendedData,
  } from './SearchPage.helpers';
  
  // Pagination page size might need to be dynamic on responsive page layouts
  // Current design has max 3 columns 12 is divisible by 2 and 3
  // So, there's enough cards to fill all columns on full pagination pages
  const RESULT_PAGE_SIZE = 24;
  const MODAL_BREAKPOINT = 768; // Search is in modal on mobile layout
  const SEARCH_WITH_MAP_DEBOUNCE = 300; // Little bit of debounce before search is initiated.
  
  export class SearchPageComponent extends Component {
    constructor(props) {
      super(props);
  
      this.state = {
        isSearchMapOpenOnMobile: props.tab === 'map',
        isMobileModalOpen: false,
      };
  
      this.searchMapListingsInProgress = false;
  
      this.filters = this.filters.bind(this);
      this.onMapMoveEnd = debounce(this.onMapMoveEnd.bind(this), SEARCH_WITH_MAP_DEBOUNCE);
      this.onOpenMobileModal = this.onOpenMobileModal.bind(this);
      this.onCloseMobileModal = this.onCloseMobileModal.bind(this);
    }
  
    filters() {
      const { amenities, regularlyOpenOn, groupSize, listingTypes } = this.props;
  
      return {
        amenitiesFilter: {
          paramName: 'pub_amenities',
          options: amenities,
        },
        regularlyOpenOnFilter: {
          paramName: 'pub_regularlyOpenOn',
          options: regularlyOpenOn,
        },
        groupSizeFilter: {
          paramName: 'pub_groupSize',
          options: groupSize,
        },
        listingTypeFilter: {
          paramName: 'pub_type',
          options: listingTypes,
        },
      };
    }
  
    // Callback to determine if new search is needed
    // when map is moved by user or viewport has changed
    onMapMoveEnd(viewportBoundsChanged, data) {
      const { viewportBounds, viewportCenter } = data;
  
      const routes = routeConfiguration();
      const searchPagePath = pathByRouteName('SearchPage', routes);
      const currentPath =
        typeof window !== 'undefined' && window.location && window.location.pathname;
  
      // When using the ReusableMapContainer onMapMoveEnd can fire from other pages than SearchPage too
      const isSearchPage = currentPath === searchPagePath;
  
      // If mapSearch url param is given
      // or original location search is rendered once,
      // we start to react to "mapmoveend" events by generating new searches
      // (i.e. 'moveend' event in Mapbox and 'bounds_changed' in Google Maps)
      if (viewportBoundsChanged && isSearchPage) {
        const { history, location } = this.props;
  
        // parse query parameters, including a custom attribute named category
        const { address, bounds, mapSearch, ...rest } = parse(location.search, {
          latlng: ['origin'],
          latlngBounds: ['bounds'],
        });
  
        //const viewportMapCenter = SearchMap.getMapCenter(map);
        const originMaybe = config.sortSearchByDistance ? { origin: viewportCenter } : {};
  
        const searchParams = {
          address,
          ...originMaybe,
          bounds: viewportBounds,
          mapSearch: true,
          ...validFilterParams(rest, this.filters()),
        };
  
        history.push(createResourceLocatorString('SearchPage', routes, {}, searchParams));
      }
    }
  
    // Invoked when a modal is opened from a child component,
    // for example when a filter modal is opened in mobile view
    onOpenMobileModal() {
      this.setState({ isMobileModalOpen: true });
    }
  
    // Invoked when a modal is closed from a child component,
    // for example when a filter modal is opened in mobile view
    onCloseMobileModal() {
      this.setState({ isMobileModalOpen: false });
    }
  
    render() {
      const {
        intl,
        listings,
        location,
        mapListings,
        onManageDisableScrolling,
        pagination,
        scrollingDisabled,
        searchInProgress,
        searchListingsError,
        searchParams,
        activeListingId,
        onActivateListing,
      } = this.props;
      // eslint-disable-next-line no-unused-vars
      const { mapSearch, page, ...searchInURL } = parse(location.search, {
        latlng: ['origin'],
        latlngBounds: ['bounds'],
      });
  
      const filters = this.filters();
  
      // urlQueryParams doesn't contain page specific url params
      // like mapSearch, page or origin (origin depends on config.sortSearchByDistance)
      const urlQueryParams = pickSearchParamsOnly(searchInURL, filters);
  
      // Page transition might initially use values from previous search
      const urlQueryString = stringify(urlQueryParams);
      const paramsQueryString = stringify(pickSearchParamsOnly(searchParams, filters));
      const searchParamsAreInSync = urlQueryString === paramsQueryString;
  
      const validQueryParams = validURLParamsForExtendedData(searchInURL, filters);
  
      const isWindowDefined = typeof window !== 'undefined';
      const isMobileLayout = isWindowDefined && window.innerWidth < MODAL_BREAKPOINT;
      const shouldShowSearchMap =
        !isMobileLayout || (isMobileLayout && this.state.isSearchMapOpenOnMobile);
  
      const onMapIconClick = () => {
        this.useLocationSearchBounds = true;
        this.setState({ isSearchMapOpenOnMobile: true });
      };
  
      const { address, bounds, origin } = searchInURL || {};
      const { title, description, schema } = createSearchResultSchema(listings, address, intl);
  
      // Set topbar class based on if a modal is open in
      // a child component
      const topbarClasses = this.state.isMobileModalOpen
        ? classNames(css.topbarBehindModal, css.topbar)
        : css.topbar;
  
      // N.B. openMobileMap button is sticky.
      // For some reason, stickyness doesn't work on Safari, if the element is <button>
      /* eslint-disable jsx-a11y/no-static-element-interactions */
      return (
        <Page
          scrollingDisabled={scrollingDisabled}
          description={description}
          title={title}
          schema={schema}
        >
          <TopbarContainer
            className={topbarClasses}
            currentPage="SearchPage"
            currentSearchParams={urlQueryParams}
          />
          <div className={css.container}>
            <MainPanel
              urlQueryParams={validQueryParams}
              listings={listings}
              searchInProgress={searchInProgress}
              searchListingsError={searchListingsError}
              searchParamsAreInSync={searchParamsAreInSync}
              onActivateListing={onActivateListing}
              onManageDisableScrolling={onManageDisableScrolling}
              onOpenModal={this.onOpenMobileModal}
              onCloseModal={this.onCloseMobileModal}
              onMapIconClick={onMapIconClick}
              pagination={pagination}
              searchParamsForPagination={parse(location.search)}
              showAsModalMaxWidth={MODAL_BREAKPOINT}
              primaryFilters={{
                amenitiesFilter: filters.amenitiesFilter,
                regularlyOpenOnFilter: filters.regularlyOpenOnFilter,
                groupSizeFilter: filters.groupSizeFilter,
                listingTypeFilter: filters.listingTypeFilter,
              }}
            />
            <ModalInMobile
              className={css.mapPanel}
              id="SearchPage.map"
              isModalOpenOnMobile={this.state.isSearchMapOpenOnMobile}
              onClose={() => this.setState({ isSearchMapOpenOnMobile: false })}
              showAsModalMaxWidth={MODAL_BREAKPOINT}
              onManageDisableScrolling={onManageDisableScrolling}
            >
              <div className={css.mapWrapper}>
                {shouldShowSearchMap ? (
                  <SearchMap
                    reusableContainerClassName={css.map}
                    activeListingId={activeListingId}
                    bounds={bounds}
                    center={origin}
                    isSearchMapOpenOnMobile={this.state.isSearchMapOpenOnMobile}
                    location={location}
                    listings={mapListings || []}
                    onMapMoveEnd={this.onMapMoveEnd}
                    onCloseAsModal={() => {
                      onManageDisableScrolling('SearchPage.map', false);
                    }}
                    messages={intl.messages}
                  />
                ) : null}
              </div>
            </ModalInMobile>
          </div>
        </Page>
      );
      /* eslint-enable jsx-a11y/no-static-element-interactions */
    }
  }
  
  SearchPageComponent.defaultProps = {
    listings: [],
    mapListings: [],
    pagination: null,
    searchListingsError: null,
    searchParams: {},
    tab: 'listings',
    groupSize: config.custom.groupSize,
    amenities: config.custom.amenities,
    regularlyOpenOn: config.custom.regularlyOpenOn,
    listingTypes: config.custom.listingTypes,
    activeListingId: null,
  };
  
  SearchPageComponent.propTypes = {
    listings: array,
    mapListings: array,
    onActivateListing: func.isRequired,
    onManageDisableScrolling: func.isRequired,
    onSearchMapListings: func.isRequired,
    pagination: propTypes.pagination,
    scrollingDisabled: bool.isRequired,
    searchInProgress: bool.isRequired,
    searchListingsError: propTypes.error,
    searchParams: object,
    tab: oneOf(['filters', 'listings', 'map']).isRequired,
  
    amenities: array,
    regularlyOpenOn: array,
    groupSize: array,
    listingTypes: array,
  
    // from withRouter
    history: shape({
      push: func.isRequired,
    }).isRequired,
    location: shape({
      search: string.isRequired,
    }).isRequired,
  
    // from injectIntl
    intl: intlShape.isRequired,
  };
  
  const mapStateToProps = state => {
    const {
      currentPageResultIds,
      pagination,
      searchInProgress,
      searchListingsError,
      searchParams,
      searchMapListingIds,
      activeListingId,
    } = state.SearchPage;
    const pageListings = getListingsById(state, currentPageResultIds);
    const mapListings = getListingsById(
      state,
      unionWith(currentPageResultIds, searchMapListingIds, (id1, id2) => id1.uuid === id2.uuid)
    );
  
    return {
      listings: pageListings,
      mapListings,
      pagination,
      scrollingDisabled: isScrollingDisabled(state),
      searchInProgress,
      searchListingsError,
      searchParams,
      activeListingId,
    };
  };
  
  const mapDispatchToProps = dispatch => ({
    onManageDisableScrolling: (componentId, disableScrolling) =>
      dispatch(manageDisableScrolling(componentId, disableScrolling)),
    onSearchMapListings: searchParams => dispatch(searchMapListings(searchParams)),
    onActivateListing: listingId => dispatch(setActiveListing(listingId)),
  });
  
  // Note: it is important that the withRouter HOC is **outside** the
  // connect HOC, otherwise React Router won't rerender any Route
  // components since connect implements a shouldComponentUpdate
  // lifecycle hook.
  //
  // See: https://github.com/ReactTraining/react-router/issues/4671
  const SearchPage = compose(
    withRouter,
    connect(
      mapStateToProps,
      mapDispatchToProps
    ),
    injectIntl
  )(SearchPageComponent);
  
  SearchPage.loadData = (params, search) => {
    const queryParams = parse(search, {
      latlng: ['origin'],
      latlngBounds: ['bounds'],
    });
    const { page = 1, address, origin, ...rest } = queryParams;
    const originMaybe = config.sortSearchByDistance && origin ? { origin } : {};
    return searchListings({
      ...rest,
      ...originMaybe,
      page,
      perPage: RESULT_PAGE_SIZE,
      include: ['author', 'images'],
      'fields.image': ['variants.landscape-crop', 'variants.landscape-crop2x'],
      'limit.images': 1,
    });
  };
  
  export default SearchPage;
  