React = require 'react'
createClass = require 'create-react-class'
PropTypes = require 'prop-types'
_ = require 'lodash'
{ DOWN_ARROW, RIGHT_ARROW, LEFT_ARROW, UP_ARROW, ENTER, SPACE, ESCAPE, TAB } = require '../constants/keyboard'
{alphaNumericKeyCode} = require '../utils'
FormValidation = require '../mixins/form_validation'
CircleXButton = React.createFactory(require './circle_x_button')
SelectInputCustomOptions = React.createFactory(require './select_input_custom_options')

{div, select, option} = React.DOM

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

  @props.options - [Array] - Required
  Full list of options to display on component

  @props.value - [String|Object] - Optional
  The value that corresponds to the option object with the matching value

  @props.selectText - [String] - Optional
  Text displayed as the default value

  @props.onChange - [Function] - Required
  Function fired when a change is made to the selection

  @props.tabIndex - [Number] - Optional
  Tab order index

  @props.disabled - [Boolean] - Optional
  Disabled state of the component

  @props.isFilter - [Boolean] - Optional
  Show/hide the filter typeahead input. Default is no

  @props.returnFullObjects - [Boolean] - Optional
  Determine whether `getValue` returns the full option object, or just the default value string

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

  @props.valueField - [String] - Optional
  The name of the key used to reference the value on the option object

  @props.labelField - [String] - Optional
  The name of the key used to reference the label on the option object

  @props.width - [Number] - Optional
  The width of the menu popover

  @props.height - [Number] - Optional
  The height of the menu popover

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

SelectInputCustom = createClass

  displayName: 'SelectInputCustom'
  mixins: [FormValidation]

  contextTypes:
    openOverlay: PropTypes.func
    closeOverlay: PropTypes.func

  propTypes:
    options: PropTypes.array.isRequired
    selectText: PropTypes.string
    onChange: PropTypes.func.isRequired
    tabIndex: PropTypes.number
    disabled: PropTypes.bool
    isFilter: PropTypes.bool
    returnFullObjects: PropTypes.bool
    placeholder: PropTypes.string
    valueField: PropTypes.string
    labelField: PropTypes.string
    width: PropTypes.number
    height: PropTypes.number
    optionHeight: PropTypes.number
    value: PropTypes.oneOfType [
      PropTypes.string
      PropTypes.object
    ]
    nibColor: PropTypes.string


  getDefaultProps: ->
    options: []
    selectText: 'Select from list...'
    placeholder: 'Filter options'
    valueField: 'value'
    labelField: 'title'
    width: 250
    height: 400
    isFilter: no
    nibColor: 'white'
    optionHeight: 20

  getInitialState: ->
    {value, valueField} = @props
    val = ''

    if typeof value is 'object' then val = value[valueField]
    else val = value

    return {
      value: val
    }

  render: ->
    { options, id, selectText, valueField, labelField, tabIndex, disabled, isFilter } = @props
    { value } = @state
    optionItems = []

    # Populate an initial value
    optionItems.push option {
      key: "none"
      value: ""
    }, selectText

    options.forEach (o, i) =>
      optionItems.push option {
        key: i
        value: o[valueField]
      }, o[labelField]

    @getErrors()

    roomForCircleXStyle = if value and not disabled then "x-room" else ""

    div {
      className: "field-wrap filter-select #{@invalidClass} #{roomForCircleXStyle}"
    }, [
      select {
        key: 'select'
        ref: (control) => @control = control
        tabIndex: tabIndex
        id: id
        disabled: disabled
        value: value
        onMouseDown: @handleNativeEvent
        onKeyDown: @handleNativeEvent
      }, optionItems
      CircleXButton {
        key: 'clear'
        tabIndex: tabIndex
        ref: (clearBtn) => @clearBtn = clearBtn
        onClick: @clear
      } if value and not disabled
      div({
        className: 'field-errors-show'
        key: 'errors'
      }, [
        div {
          className: 'field-errors'
          key: 'err'
        }, 
          ul {
            className: 'field-errors-list'
          }, @validationErrors
      ]) if @validationErrors.length
    ]

  handleNativeEvent: (e) ->
    { keyCode } = e
    { options, selectText, labelField, valueField, height, width, placeholder, isFilter, nibColor, optionHeight } = @props
    { value } = @state
    openKeys = [DOWN_ARROW, RIGHT_ARROW, LEFT_ARROW, UP_ARROW, ENTER, SPACE]
    closeKeys = [ESCAPE, TAB]

    if ((options.length + 1) * optionHeight) < height
      height = (options.length + 2) * optionHeight

    # Construct the options component
    overlay =
      activeComponent: 'pvr'
      close: @context.closeOverlay
      direction: 'below'
      element:
        key: 'fso'
        options: options
        onChange: @handleChange
        labelField: labelField
        valueField: valueField
        placeholder: placeholder
        value: value
        SelectEl: @
        isFilter: isFilter
      width: width
      height: height
      anchor: e.currentTarget
      noBackdrop: true
      nibColor: nibColor
      optionHeight: optionHeight

    # Handle the keyboard
    if keyCode?
      isOpenKey = openKeys.indexOf(keyCode) > -1
      isCloseKey = closeKeys.indexOf(keyCode) > -1
      alphaNum = alphaNumericKeyCode(keyCode)

      if isOpenKey or alphaNum
        e.preventDefault()
        e.stopPropagation()

        # For alphanumeric input, start filtering the element
        if alphaNum then overlay.element.filter = e.key

        # Set the element to the component, replacing the pojo
        overlay.element = SelectInputCustomOptions(overlay.element)

        # Open the overlay
        @context.openOverlay(overlay)

      else if isCloseKey
        if keyCode isnt TAB
          e.preventDefault()
        @context.closeOverlay()

      return

    # Set the element to the component, replacing the pojo
    overlay.element = SelectInputCustomOptions(overlay.element)

    # Handle mouse clicks
    e.preventDefault()
    @context.openOverlay(overlay)

  handleChange: (value, cb) ->
    @setState
      value: value
    , =>
      @props.onChange(value)
      @focus()
      cb?()

  clear: ->
    @handleChange('')

  focus: ->
    @control.focus();

  getValue: ->
    {returnFullObjects, valueField} = @props
    if returnFullObjects
      return _.find @props.options, (o) =>
        v = o[valueField]
        if typeof v is 'number' then v = v.toString()
        return v is @control.value
    else return @control.value


module.exports = SelectInputCustom