<commit_before>import { Box, Flex } from "@artsy/palette"
import { SearchBar_viewer } from "__generated__/SearchBar_viewer.graphql"
import { SearchBarSuggestQuery } from "__generated__/SearchBarSuggestQuery.graphql"
import { ContextConsumer, ContextProps } from "Artsy"
import { track } from "Artsy/Analytics"
import * as Schema from "Artsy/Analytics/Schema"
import colors from "Assets/Colors"
import Input from "Components/Input"
import { SearchPreview } from "Components/Search/Previews"
import {
  EmptySuggestion,
  PLACEHOLDER,
  SuggestionItem,
} from "Components/Search/Suggestions/SuggestionItem"
import { throttle } from "lodash"
import React, { Component } from "react"
import Autosuggest from "react-autosuggest"
import {
  createRefetchContainer,
  graphql,
  QueryRenderer,
  RelayRefetchProp,
} from "react-relay"
import styled from "styled-components"
import { get } from "Utils/get"
import createLogger from "Utils/logger"
import { Media } from "Utils/Responsive"

const logger = createLogger("Components/Search/SearchBar")

const AutosuggestContainer = styled(Box)`
  div[role="combobox"] {
    div[role="listbox"] {
      ul {
        list-style-type: none;
        padding: 0;
        margin: 0;
      }
    }
  }
`

export interface Props extends ContextProps {
  relay: RelayRefetchProp
  viewer: SearchBar_viewer
}

interface State {
  /* Holds current input */
  term: string
  /* For preview generation of selected items */
  entityID: string
  entityType: string
  focused: boolean
}

const AutosuggestWrapper = styled(Box)`
  position: relative;
`

const ResultsWrapper = styled(Box)`
  background-color: ${colors.white};
  display: flex;
  border: 1px solid ${colors.grayRegular};
  position: absolute;
`

const SuggestionsWrapper = styled(Box)`
  border-right: 1px solid ${colors.grayRegular};
`

const SuggestionContainer = ({ children, containerProps, preview }) => {
  return (
    <AutosuggestWrapper
      width="100%"
      flexDirection={["column", "row"]}
      {...containerProps}
    >
      <ResultsWrapper
        width={[
          "100%",
          "calc(100% + 250px)",
          "calc(100% + 250px)",
          "calc(100% + 450px)",
        ]}
        mt={0.5}
      >
        <SuggestionsWrapper
          width={[
            "100%",
            "calc(100% - 250px)",
            "calc(100% - 250px)",
            "calc(100% - 450px)",
          ]}
        >
          <Flex flexDirection="column" width="100%">
            {children}
          </Flex>
        </SuggestionsWrapper>
        <Box width={["0px", "240px", "240px", "450px"]} pl={[0, 3]} py={[0, 2]}>
          {preview}
        </Box>
      </ResultsWrapper>
    </AutosuggestWrapper>
  )
}

@track()
export class SearchBar extends Component<Props, State> {
  public input: HTMLInputElement

  state = {
    term: "",
    entityID: null,
    entityType: null,
    focused: false,
  }

  // Throttled method to toggle previews.
  throttledOnSuggestionHighlighted = ({ suggestion }) => {
    if (!suggestion) return

    const {
      node: { searchableType: entityType, id: entityID },
    } = suggestion

    if (entityType === "FirstItem") return

    this.setState({ entityType, entityID })
  }

  @track((_props, _state, [query, hasResults]) => ({
    action_type: hasResults
      ? Schema.ActionType.SearchedAutosuggestWithResults
      : Schema.ActionType.SearchedAutosuggestWithoutResults,
    query,
  }))
  trackSearch(_term, _hasResults) {
    /* no-op */
  }

  // Throttled method to perform refetch for new suggest query.
  throttledFetch = ({ value: term }) => {
    const { relay, viewer } = this.props
    relay.refetch(
      {
        term,
        hasTerm: true,
      },
      null,
      error => {
        if (error) {
          logger.error(error)
          return
        }
        const edges = get(viewer, v => v.search.edges, [])
        this.trackSearch(term, edges.length > 0)
      }
    )
  }

  componentDidMount() {
    this.throttledFetch = throttle(this.throttledFetch, 500, {
      leading: true,
    })
    this.throttledOnSuggestionHighlighted = throttle(
      this.throttledOnSuggestionHighlighted,
      500,
      { leading: true }
    )
  }

  searchTextChanged = (_e, { newValue: term }) => {
    this.setState({ term })
  }

  @track({
    action_type: Schema.ActionType.FocusedOnAutosuggestInput,
  })
  onFocus() {
    this.setState({ focused: true })
  }

  onBlur = () => {
    this.setState({ focused: false })
  }

  onSuggestionsClearRequested = () => {
    this.setState({ term: "", entityID: null, entityType: null })
  }

  // Navigate to selected search item.
  @track(
    (
      _props,
      state: State,
      [
        {
          suggestion: {
            node: { href, searchableType, id },
          },
          suggestionIndex,
        },
      ]
    ) => ({
      action_type: Schema.ActionType.SelectedItemFromSearch,
      query: state.term,
      destination_path: href,
      item_type: searchableType,
      item_id: id,
      item_number: suggestionIndex,
    })
  )
  onSuggestionSelected({
    suggestion: {
      node: { href },
    },
  }) {
    window.location.assign(href)
  }

  renderPreview() {
    const { entityID, entityType, focused } = this.state
    if (!focused) return

    return <SearchPreview entityID={entityID} entityType={entityType} />
  }

  renderSuggestionsContainer = (
    { containerProps, children, query },
    { xs }
  ) => {
    const { focused } = this.state
    if (!focused) {
      return null
    }

    const showEmptyState = !xs && !query && focused
    const emptyState = showEmptyState && <EmptySuggestion />

    const props = {
      children,
      containerProps,
      focused,
      query,
      preview: this.renderPreview(),
    }

    return (
      <SuggestionContainer {...props}>
        {emptyState || children}
      </SuggestionContainer><commit_msg>Further simplify empty suggestion logic<commit_after>import { Box, Flex } from "@artsy/palette"
import { SearchBar_viewer } from "__generated__/SearchBar_viewer.graphql"
import { SearchBarSuggestQuery } from "__generated__/SearchBarSuggestQuery.graphql"
import { ContextConsumer, ContextProps } from "Artsy"
import { track } from "Artsy/Analytics"
import * as Schema from "Artsy/Analytics/Schema"
import colors from "Assets/Colors"
import Input from "Components/Input"
import { SearchPreview } from "Components/Search/Previews"
import {
  EmptySuggestion,
  PLACEHOLDER,
  SuggestionItem,
} from "Components/Search/Suggestions/SuggestionItem"
import { throttle } from "lodash"
import React, { Component } from "react"
import Autosuggest from "react-autosuggest"
import {
  createRefetchContainer,
  graphql,
  QueryRenderer,
  RelayRefetchProp,
} from "react-relay"
import styled from "styled-components"
import { get } from "Utils/get"
import createLogger from "Utils/logger"
import { Media } from "Utils/Responsive"

const logger = createLogger("Components/Search/SearchBar")

const AutosuggestContainer = styled(Box)`
  div[role="combobox"] {
    div[role="listbox"] {
      ul {
        list-style-type: none;
        padding: 0;
        margin: 0;
      }
    }
  }
`

export interface Props extends ContextProps {
  relay: RelayRefetchProp
  viewer: SearchBar_viewer
}

interface State {
  /* Holds current input */
  term: string
  /* For preview generation of selected items */
  entityID: string
  entityType: string
  focused: boolean
}

const AutosuggestWrapper = styled(Box)`
  position: relative;
`

const ResultsWrapper = styled(Box)`
  background-color: ${colors.white};
  display: flex;
  border: 1px solid ${colors.grayRegular};
  position: absolute;
`

const SuggestionsWrapper = styled(Box)`
  border-right: 1px solid ${colors.grayRegular};
`

const SuggestionContainer = ({ children, containerProps, preview }) => {
  return (
    <AutosuggestWrapper
      width="100%"
      flexDirection={["column", "row"]}
      {...containerProps}
    >
      <ResultsWrapper
        width={[
          "100%",
          "calc(100% + 250px)",
          "calc(100% + 250px)",
          "calc(100% + 450px)",
        ]}
        mt={0.5}
      >
        <SuggestionsWrapper
          width={[
            "100%",
            "calc(100% - 250px)",
            "calc(100% - 250px)",
            "calc(100% - 450px)",
          ]}
        >
          <Flex flexDirection="column" width="100%">
            {children}
          </Flex>
        </SuggestionsWrapper>
        <Box width={["0px", "240px", "240px", "450px"]} pl={[0, 3]} py={[0, 2]}>
          {preview}
        </Box>
      </ResultsWrapper>
    </AutosuggestWrapper>
  )
}

@track()
export class SearchBar extends Component<Props, State> {
  public input: HTMLInputElement

  state = {
    term: "",
    entityID: null,
    entityType: null,
    focused: false,
  }

  // Throttled method to toggle previews.
  throttledOnSuggestionHighlighted = ({ suggestion }) => {
    if (!suggestion) return

    const {
      node: { searchableType: entityType, id: entityID },
    } = suggestion

    if (entityType === "FirstItem") return

    this.setState({ entityType, entityID })
  }

  @track((_props, _state, [query, hasResults]) => ({
    action_type: hasResults
      ? Schema.ActionType.SearchedAutosuggestWithResults
      : Schema.ActionType.SearchedAutosuggestWithoutResults,
    query,
  }))
  trackSearch(_term, _hasResults) {
    /* no-op */
  }

  // Throttled method to perform refetch for new suggest query.
  throttledFetch = ({ value: term }) => {
    const { relay, viewer } = this.props
    relay.refetch(
      {
        term,
        hasTerm: true,
      },
      null,
      error => {
        if (error) {
          logger.error(error)
          return
        }
        const edges = get(viewer, v => v.search.edges, [])
        this.trackSearch(term, edges.length > 0)
      }
    )
  }

  componentDidMount() {
    this.throttledFetch = throttle(this.throttledFetch, 500, {
      leading: true,
    })
    this.throttledOnSuggestionHighlighted = throttle(
      this.throttledOnSuggestionHighlighted,
      500,
      { leading: true }
    )
  }

  searchTextChanged = (_e, { newValue: term }) => {
    this.setState({ term })
  }

  @track({
    action_type: Schema.ActionType.FocusedOnAutosuggestInput,
  })
  onFocus() {
    this.setState({ focused: true })
  }

  onBlur = () => {
    this.setState({ focused: false })
  }

  onSuggestionsClearRequested = () => {
    this.setState({ term: "", entityID: null, entityType: null })
  }

  // Navigate to selected search item.
  @track(
    (
      _props,
      state: State,
      [
        {
          suggestion: {
            node: { href, searchableType, id },
          },
          suggestionIndex,
        },
      ]
    ) => ({
      action_type: Schema.ActionType.SelectedItemFromSearch,
      query: state.term,
      destination_path: href,
      item_type: searchableType,
      item_id: id,
      item_number: suggestionIndex,
    })
  )
  onSuggestionSelected({
    suggestion: {
      node: { href },
    },
  }) {
    window.location.assign(href)
  }

  renderPreview() {
    const { entityID, entityType, focused } = this.state
    if (!focused) return

    return <SearchPreview entityID={entityID} entityType={entityType} />
  }

  renderSuggestionsContainer = (
    { containerProps, children, query },
    { xs }
  ) => {
    const { focused } = this.state
    if (!focused) {
      return null
    }

    const showEmptySuggestion = !xs && !query && focused

    const props = {
      children,
      containerProps,
      focused,
      query,
      preview: this.renderPreview(),
    }

    return (
      <SuggestionContainer {...props}>
        {showEmptySuggestion ? <EmptySuggestion /> : children}
      </SuggestionContainer>
{'fraction_non_alphanumeric': 0.12582725267266248, 'fraction_numerical': 0.012726964194807398, 'mean_word_length': 2.5881278538812786, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 57, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5813381', 'n_tokens_mistral': 3831, 'n_tokens_neox': 3613, 'n_words': 804}