React = require 'react'
SearchInput = React.createFactory(require './search_input')
TextInput = React.createFactory(require './text_input')
{UP_ARROW, DOWN_ARROW, ENTER, PAGE_UP, PAGE_DOWN} = require('../constants/keyboard')

{div, ul} = React.DOM

###
Input Type Ahead Props

@results - REQUIRED - Array
  Array of items that are printed in the results list

@value - OPTIONAL - String | Number
  Value of input field

@searchInterval - OPTIONAL - Number
  Number in milliseconds before firing onSearch, after user finishes typing

@onChange - REQUIRED - Function
  Function that is fired when the value of the input changes

@onSearch - REQUIRED - Function
  Function that is fired when the timer triggers after the set searchInterval

@className - OPTIONAL - String
  CSS class applied to form wrapper

@placeholder - OPTIONAL - String | Number
  Default placeholder value of text input

@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

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

###

InputTypeAhead = React.createClass

  displayName: 'InputTypeAhead'

  propTypes:
    resultConfig: React.PropTypes.shape {
      component: React.PropTypes.func.isRequired
      height: React.PropTypes.number.isRequired
      onResultSelect: React.PropTypes.func.isRequired
    }
    onSearch: React.PropTypes.func.isRequired
    onChange: React.PropTypes.func.isRequired
    className: React.PropTypes.string
    results: React.PropTypes.array.isRequired
    minLength: React.PropTypes.number
    value: React.PropTypes.oneOfType [
      React.PropTypes.number
      React.PropTypes.string
    ]
    maxContainerHeight: React.PropTypes.oneOfType [
      React.PropTypes.number
      React.PropTypes.string
    ]
    placeholder:  React.PropTypes.oneOfType [
      React.PropTypes.number
      React.PropTypes.string
    ]
    searchInterval: React.PropTypes.number

  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

  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) ->
    if @isMounted()
      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: ->
    @props.onChange()
    @executeSearch(@refs.input.getValue())

  executeSearch: (simpleTerm) ->
    {onSearch, minLength, searchInterval} = @props
    {length} = simpleTerm
    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?(simpleTerm)
      , 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

  getValue: ->
    @refs.input.getValue()

  render: ->
    {results, className, headerComponent, placeholder, showNoResults, resultConfig} = @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
    }, [
      TextInput {
        key: 'input'
        ref: 'input'
        autoComplete: false
        value: value
        placeholder: placeholder
        loading: loading
        onChange: @handleChange
        className: if resultItems.length and not loading then 'no-radius'
      }
      headerComponent {
        key: 'header'
      } if resultItems.length and not loading and headerComponent?
      div {
        key: 'blah'
        className: 'blah'
      } 
      div {
        key: 'results'
        ref: 'results'
        className: 'type-ahead-results'
        style:
          height: @calculateMaxVisibleResults() * resultConfig.height + 2
      }, [
        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