React = require 'react'
PropTypes = require 'prop-types'
createClass = require 'create-react-class'
TextInput = React.createFactory(require './text_input_2')
{UP_ARROW, DOWN_ARROW, ENTER, PAGE_UP, PAGE_DOWN} = require('../constants/keyboard')

{div, ul, button} = require 'react-dom-factories'

###&
  @props.results - REQUIRED - [Array]
  Array of items that are printed in the results list

  @props.value - REQUIRED - [String | Number]
  Value of input field

  @props.searchInterval - OPTIONAL - [Number]
  Number in milliseconds before firing onSearch, after user finishes typing

  @props.onChange - REQUIRED - [Function]
  Function that is fired when the value of the input changes

  @props.onSearch - REQUIRED - [Function]
  Function that is fired when the timer triggers after the set searchInterval

  @props.className - OPTIONAL - [String]
  CSS class applied to form wrapper

  @props.placeholder - OPTIONAL - [String | Number]
  Default placeholder value of text input

  @props.resultConfig - REQUIRED - [Object]
  component: React component that's rendered for each result item
  height: Height for each result item - should be evenly divisible into maxContainerHeight
  onResultSelect: Function that is fired when a list item is selected - should be used to alter state and pass new values to 'value' prop. This passes the result object and close method callback

  @props.maxContainerHeight - OPTIONAL - [Number | String]
  The maximum height of the typeahead results container. The resultConfig.height property should be evenly divisible into maxContainerHeight

  @props.validation - OPTIONAL - [Function]
  a method that takes the value and returns an arry of validation objects
  always return an empty array for a valid value
  see the validation store for more documentation on validation objects

  @props.zIndex - OPTIONAL - [Number]
  Default style is 10. Optionally pass higher number to cover typeaheads on the same page.

  @props.disabled - OPTIONAL - [Boolean]

  @props.clearInput - OPTIONAL - [Function]
  a method that will clear out the input 
  if passed display the clear btn in the input field
  


&###

InputTypeAhead = createClass

  displayName: 'InputTypeAhead'

  propTypes:
    resultConfig: PropTypes.shape {
      component: PropTypes.func.isRequired
      height: PropTypes.number.isRequired
      right: PropTypes.number
      left: PropTypes.number
      onResultSelect: PropTypes.func.isRequired
    }
    onSearch: PropTypes.func.isRequired
    onChange: PropTypes.func.isRequired
    className: PropTypes.string
    focusOnMount: PropTypes.bool
    results: PropTypes.array.isRequired
    minLength: PropTypes.number
    value: PropTypes.oneOfType([
      PropTypes.number
      PropTypes.string
    ]).isRequired
    maxContainerHeight: PropTypes.oneOfType [
      PropTypes.number
      PropTypes.string
    ]
    placeholder:  PropTypes.oneOfType [
      PropTypes.number
      PropTypes.string
    ]
    searchInterval: PropTypes.number
    zIndex: PropTypes.number
    disabled: PropTypes.bool

  getInitialState: ->
    loading: false
    selectedResultIndex: 0
    value: @props.value or ''
    showResults: false

  componentWillReceiveProps: (nextProps) ->
    showResults = nextProps.results.length > 0 and nextProps.value.length > 0
    selectedResultIndex = if showResults and nextProps.value isnt @props.value then 0 else @state.selectedResultIndex

    @setState
      showResults: showResults
      selectedResultIndex: selectedResultIndex
      value: nextProps.value

  getDefaultProps: ->
    className: ''
    results: []
    minLength: 1
    searchInterval: 300
    maxContainerHeight: 175
    focusOnMount: false
    disabled: no

  handleClick: (e) ->
    e.stopPropagation()

  componentWillMount: ->
    document.addEventListener('keydown', @handleKeyDown)
    window.addEventListener('hashchange', @close)
    document.addEventListener('click', @close)

  componentWillUnmount: ->
    document.removeEventListener('keydown', @handleKeyDown)
    window.removeEventListener('hashchange', @close)
    document.removeEventListener('click', @close)

  close: (e) ->
    e?.stopPropagation()

    @setState
      showResults: false
    , =>
      @props.onClose?()

  calculateMaxVisibleResults: ->
    {resultConfig, results, maxContainerHeight} = @props

    # Figure out how many results can fit in the container
    resultsHeight = resultConfig.height * results.length
    containerHeight = if resultsHeight > maxContainerHeight then maxContainerHeight else resultsHeight

    return containerHeight / resultConfig.height

  onResultSelect: (result) ->
    @props.resultConfig.onResultSelect(result, @close)

  handleKeyDown: (e) ->
    {results} = @props
    {selectedResultIndex, showResults, loading} = @state
    numResults = @calculateMaxVisibleResults()

    #! temporary
    loading = false

    # Only make these keystrokes work when the menu is open
    if loading or not showResults then return

    switch e.keyCode
      when DOWN_ARROW
        e.preventDefault()
        e.stopPropagation()
        @traverseResults(1)

      when UP_ARROW
        e.preventDefault()
        e.stopPropagation()
        @traverseResults(-1)

      when PAGE_DOWN
        e.preventDefault()
        e.stopPropagation()
        @traverseResults(numResults)

      when PAGE_UP
        e.preventDefault()
        e.stopPropagation()
        @traverseResults(-(numResults))

      when ENTER
        e.preventDefault()
        e.stopPropagation()

        result = results[selectedResultIndex]
        @onResultSelect(result)

  handleChange: (value, jsonPath = null) ->
    if jsonPath
      @props.onChange(value, jsonPath)
    else  
      @props.onChange(value)
    @executeSearch(value)

  executeSearch: (term) ->
    {onSearch, minLength, searchInterval} = @props
    {length} = term
    clearInterval(@searchTimer) if @searchTimer?

    if length is 0 then @setState { value: '' }
    else if length < minLength then return
    else 
      unless @state.loading then @setState { loading: true }
      @searchTimer = setTimeout =>
        onSearch?(term)
      , searchInterval

  traverseResults: (change) ->
    unless @refs.results? then return

    {results, resultConfig} = @props
    {selectedResultIndex} = @state
    newResult = selectedResultIndex + change
    resultsTop = @refs.results.getBoundingClientRect().top
    {scrollTop} = @refs.results

    # Adjust the change to make sure it will work
    if results.length and newResult >= results.length then newResult = results.length - 1
    if newResult <= 0 then newResult = 0

    itemTop = @refs.resultsList.children[newResult].getBoundingClientRect().top

    if itemTop + resultConfig.height > resultsTop + resultConfig.height or itemTop < resultsTop
      @refs.results.scrollTop = newResult * resultConfig.height

    @setState
      selectedResultIndex: newResult

  render: ->
    {results, className, headerComponent, placeholder, showNoResults, resultConfig, focusOnMount, onKeyDown, validation, isInPopover, zIndex, disabled, clearInput, maxLength, jsonPath, tabIndex} = @props
    {selectedResultIndex, loading, value, showResults} = @state
    resultItems = []
    noResults = not results.length and showNoResults

    #! temporary
    loading = false

    for result, index in results
      resultItems.push resultConfig.component {
        key: index
        selectedResultIndex: selectedResultIndex
        index: index
        height: resultConfig.height
        maxVisibleRows: @calculateMaxVisibleResults()
        result: result
        onResultSelect: resultConfig.onResultSelect
      }

    div {
      className: "input-type-ahead #{className}"
      onClick: @handleClick
      style:
        zIndex: zIndex if zIndex?
    }, [
      TextInput {
        key: 'input'
        ref: 'input'
        autoComplete: false
        value: value
        focusOnMount: focusOnMount
        placeholder: placeholder
        loading: loading
        onChange: @handleChange
        onKeyDown: onKeyDown
        className: if resultItems.length and not loading then 'no-radius'
        validation: validation
        isInPopover: isInPopover 
        disabled: disabled  
        maxLength: maxLength       
        jsonPath: jsonPath
        tabIndex: tabIndex
      }
      button {
        className: 'search-clear'
        title: 'Clear Input'
        key: 'inputClearBtn'
        onClick: clearInput
        tabIndex: -1
      }, [] if clearInput?
      headerComponent {
        key: 'header'
      } if resultItems.length and not loading and headerComponent?
      div {
        key: 'results'
        ref: 'results'
        className: 'type-ahead-results'
        style:
          height: @calculateMaxVisibleResults() * resultConfig.height + 2
          right: "#{resultConfig.right}px" if resultConfig.right?
          left: "#{resultConfig.left}px" if resultConfig.left?
      }, [
        if resultItems.length
          ul {
            key: 'results-list'
            ref: 'resultsList'
          }, resultItems
        else if noResults
          div {
            className: 'no-results'
            key: 'no-results'
          }, 'No Results'          
      ] if not loading and showResults
    ]


module.exports = InputTypeAhead