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

{div, ul, li} = React.DOM

###&
  @general
  Filter select options component. This component lives on the overlay layer, and requires integrated context methods closeOverlay and openOverlay within the application.

  @props.filter - [String] - Optional
  Initialize the component overlay with a filter value. This will start filtering option labels based on this value.

  @props.placeholder - [String] - Optional
  Placeholder value for the filter input

  @props.onChange - [Function] - Required
  Function that is fired when a selection change is made
  
  @props.options - [Array] - Optional
  Array of options to render in the select

  @props.optionHeight - [Number] - Optional
  The fixed height of each menu option

  @props.value - [String|Number] - Optional
  The value of the currently selected option object

  @props.noResultsText - [String] - Optional
  Text displayed in the menu when no results match the filter input value

  @props.SelectEl - [Function] - Optional
  Reference to the select menu component that opens this overlay. If provided, focus will be directed back to the input when closing the overlay

  @props.onChangeFilter - [Function] - Optional
  Function fired when the filter input changes
&###

SelectInputCustomOptions = createClass

  displayName: 'SelectInputCustomOptions'

  contextTypes:
    closeOverlay: PropTypes.func

  propTypes:
    filter: PropTypes.string
    placeholder: PropTypes.string
    onChange: PropTypes.func.isRequired
    options: PropTypes.array
    optionHeight: PropTypes.number
    value: PropTypes.string
    noResultsText: PropTypes.string
    SelectEl: PropTypes.object
    valueField: PropTypes.string
    labelField: PropTypes.string
    isFilter: PropTypes.bool

  getDefaultProps: ->
    filter: ''
    optionHeight: 20
    noResultsText: 'No results found'
    isFilter: no

  getInitialState: ->
    filterValue: ''
    focusedOptionIndex: 0
    options: @props.options

  componentDidMount: ->
    { filter } = @props

    # If using a filter, focus that on mount
    if filter
      @setState
        filterValue: filter

    if @textInput?
      @textInput.focus()

    # Add arrow key handlers
    window.addEventListener('keydown', @handleKeyDown)

  componentWillUnmount: ->
    window.removeEventListener('keydown', @handleKeyDown)

  componentWillUpdate: (nextProps, nextState) ->
    { filterValue } = nextState
    { options, labelField } = nextProps
    if filterValue? and @state.filterValue isnt filterValue
      opts = _.filter options, (o) =>
        return o[labelField].toLowerCase().search(filterValue.toLowerCase()) > -1

      @setState
        options: opts

  render: ->
    { filterValue, options } = @state
    { placeholder, value, noResultsText, isFilter } = @props
    selectOptions = []

    optionListClass = if isFilter then 'options-list' else 'options-list no-filter'

    # Render options elements
    options.forEach (o, i) =>
      selectOptions.push(@processOption(o, i))

    div {
      className: "select-options"
      onClick: @handleOptionsClick
    }, [
      TextInput {
        key: 'input'
        ref: (input) => @textInput = input
        id: "filter"
        value: filterValue
        onChange: @handleFilterChange
        placeholder: placeholder
      } if isFilter
      if selectOptions.length
        ul {
          key: 'options'
          className: optionListClass
          ref: (optionsList) => @optionsList = optionsList
          onScroll: @handleScroll
        }, selectOptions
      else
        div {
          key: 'no-results'
          className: 'no-results'
        }, noResultsText
    ]

  processOption: (opt, index) ->
    { options, focusedOptionIndex } = @state
    { value, optionHeight, labelField, valueField } = @props
    optionClass = "option"

    if options[focusedOptionIndex] is opt then optionClass += " is-focused"
    if value is opt[valueField] then optionClass += " is-selected"

    return li {
      key: index
      onClick: @handleClick.bind(@, opt)
      className: optionClass
      title: opt[labelField]
      style:
        height: optionHeight
        lineHeight: "#{optionHeight}px"
    }, opt[labelField]


  handleKeyDown: (e) ->
    { options, focusedOptionIndex, isFilter } = @state
    { SelectEl, labelField, valueField } = @props
    adjust = 0

    switch e.keyCode
      when UP_ARROW
        e.preventDefault()
        adjust = -1
        break
      when DOWN_ARROW
        e.preventDefault()
        adjust = 1
        break
      when TAB
        e.preventDefault()
        return @context.closeOverlay(SelectEl)
      when ESCAPE
        e.preventDefault()
        return @context.closeOverlay(SelectEl)
      when ENTER
        e.preventDefault()
        currentOption = options[focusedOptionIndex]
        if currentOption?
          return @props.onChange(currentOption[valueField], @context.closeOverlay)
        return @context.closeOverlay(SelectEl)
      else
        e.stopPropagation()
        break

    newIndex = focusedOptionIndex + adjust
    if newIndex < 0
      newIndex = 0
    else if newIndex >= options.length - 1
      newIndex = options.length - 1

    @setState
      focusedOptionIndex: newIndex
    , =>
      @textInput.focus() if isFilter
      @adjustScrollPosition(adjust) if @optionsList?
    
  adjustScrollPosition: (adjust) ->
    { optionHeight } = @props
    { options } = @state

    # Adjust the scroll top
    scrollAdjust = adjust * optionHeight
    { scrollTop } = @optionsList
    adjustTop = @optionsList.scrollTop + scrollAdjust
    maxHeight = optionHeight * options.length

    # Don't allow the final value to be above the max or below 0
    if adjustTop < 0
      adjustTop = 0
    else if adjustTop > maxHeight
      adjustTop = maxHeight

    # Adjust the scrollTop position
    @optionsList.scrollTop = adjustTop

  handleFilterChange: (e) ->
    @setState
      filterValue: e
    , =>
      @props.onChangeFilter?(e)
    
  handleClick: (option, e) ->
    {valueField, onChange} = @props
    onChange(option[valueField], @context.closeOverlay)

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

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


module.exports = SelectInputCustomOptions