React = require 'react'
PropTypes = require 'prop-types'
createClass = require 'create-react-class'
assign = require 'lodash/assign'
cloneDeep = require 'lodash/cloneDeep'
filter = require 'lodash/filter'
isEqual = require 'lodash/isEqual'

Pvr = React.createFactory(require './pvr')
SelectPvrOption = React.createFactory(require './select_pvr_option')

{div} = require 'react-dom-factories'

###&
@general
Select popover menu with sub-option capability

@props.options - [Array] - Required
  array of objects containing at minimum a label and value attribute
  optionally a subLabel property can be passed

@props.defaultSelected - [Object|String] - Optional
  value of the option selected by default

@props.close - [Function] - Required
  func that closes the popover

@props.styleMixin - [Object] - Optional
  object containing any style properties to mixin with and/or overrride the defaults
  note that width height are passed separately so they can have defaults and auto settings
  passing widt/height in this object could cause issues

@props.onChange - [Function] - Required
  method to call when the non selected option is clicked

@props.hideSelected - [Boolean] - Optional
  when on, the defaultSelected option will be removed from the list

@props.headerTitle - [String] - Optional
  optional title String for popover header

@props.headerClass - [String] - Optional
  optional class for popover header

@props.maxHeight - [Number] - Optional
  the maximum height the popover should be. used to set height on the pvr if this is 
  lower than the computed height.

@props.pvrProps - [Object] - Optional
  properties germane to PVR wrapper: width, height, anchor, hAdjust, vAdjust, direction

@props.multiSelect - [Boolean] - Optional
  allows the component to have multiple selections before firing the onChange handler.
  when multiSelect is enabled, onChange will be deferred to componentWillUnmount

@props.multiSelectKey - [String|Number] - Optional
  `default: 'id'`
  a custom property name can be defined for keying multiSelect options.
  must be a unique identifier.

@props.multiSelectGroupId - [String|Number] - Optional
  when `multiSelect = true`, selection scoping can be achieved by providing a group ID to separate selection groups.

@props.footerComponent - [Component] - Optional
  Create a footer space at the bottom of the SelectPvr with a passed component

@props.footerHeight - [Number] - Optional
  Set a fixed height to pass as calculated to the Pvr parent component. Used to size the footer space.
&###

SelectPvr = createClass
  
  displayName: 'SelectPvr'

  propTypes:
    options: PropTypes.array.isRequired
    styleMixin: PropTypes.object
    headerClass: PropTypes.string
    close: PropTypes.func.isRequired   
    optionHeight: PropTypes.number
    onChange: PropTypes.func.isRequired
    maxHeight: PropTypes.number
    pvrProps: PropTypes.object
    canDeselect: PropTypes.bool
    noWrapOptions: PropTypes.bool
    defaultSelected: PropTypes.oneOfType [
      PropTypes.string
      PropTypes.object
    ]
    multiSelect: PropTypes.bool
    multiSelectKey: PropTypes.oneOfType [
      PropTypes.string
      PropTypes.number
    ]
    multiSelectGroupId: PropTypes.oneOfType [
      PropTypes.string
      PropTypes.number
    ]
    footerComponent: PropTypes.object
    footerHeight: PropTypes.number

  getInitialState: ->
    {defaultSelected, options, optionHeight} = @props

    {
      selectedOptions: defaultSelected
      aggregateOptionsHeight: options.length * optionHeight + options.length * 1
      openSubOptions: null
    }

  getDefaultProps: ->
    {
      options: []
      styleMixin: {}
      headerTitle: null
      headerClass: ''
      hideSelected: no
      optionHeight: 36
      noWrapOptions: no
      pvrProps: {}
      canDeselect: no
      multiSelect: no
      multiSelectKey: 'id'
      footerComponent: null
      footerHeight: 0
      defaultSelected: null
    }

  componentWillMount: ->
    {multiSelect, options} = @props
    @multiSelectGroupIds = new Map()

    if multiSelect
      options.forEach (o) =>
        {multiSelectGroupId} = o
        if @multiSelectGroupIds.has(multiSelectGroupId)
          @multiSelectGroupIds.set(multiSelectGroupId, @multiSelectGroupIds.get(multiSelectGroupId) + 1)
        else 
          @multiSelectGroupIds.set(multiSelectGroupId, 1)

  componentWillUnmount: ->
    {onChange} = @props
    {selectedOptions} = @state
    if @changeOnUnmount then onChange(selectedOptions)

  render: ->
    {styleMixin, options, hideSelected, optionHeight, headerTitle, headerClass, noWrapOptions, disabled, className, maxHeight, close, canDeselect, multiSelect, footerComponent, footerHeight} = @props
    {selectedOptions, scale, aggregateOptionsHeight, openSubOptions} = @state
    style = {}

    pvrProps = cloneDeep @props.pvrProps

    @hasHeader = headerTitle?

    unless pvrProps.height?
      pvrProps.height = aggregateOptionsHeight - (if hideSelected then optionHeight else 0) 
      
    if @hasHeader
      pvrProps.height += 34

    if footerComponent?
      pvrProps.height += footerHeight

    if maxHeight? and pvrProps.height > maxHeight
      pvrProps.height = maxHeight

    assign style, styleMixin

    if pvrProps.styleMixin?.maxHeight?
      style.maxHeight = pvrProps.styleMixin.maxHeight 
    style.height = pvrProps.height

    if pvrProps.width
      style.width = pvrProps.width

    optionEls = []
    for option in options
      {children, label, id, value, subLabel, multiSelectGroupId} = option
      optionsEqual = @compareOptions(option)

      continue if hideSelected and optionsEqual

      childItems = []
      subOptionsHeight = 0

      if children?
        for chld in children when chld?
          opth = chld.optionHeight or optionHeight
          subOptionsHeight += opth
          childItems.push SelectPvrOption {
            key: chld.id or chld.value
            hasSubLabel: chld.subLabel?
            option: chld
            optionHeight: opth
            isSelected: @compareOptions(chld)
            multiSelect: multiSelect
            multiSelectGroupId: chld.multiSelectGroupId
            canDeselect: chld.canDeselect
            handleChange: chld.handleChange or @handleChange
            noWrapOptions: chld.noWrapOptions
            customClass: chld.customClass
          }
      
      optionEls.push SelectPvrOption {
        hasSubLabel: subLabel?
        option: option
        optionHeight: optionHeight
        key: id or value
        isSelected: optionsEqual
        canDeselect: canDeselect
        handleChange: @handleChange
        noWrapOptions: noWrapOptions
        subOptionsHeight: subOptionsHeight
        setOpenSubOptions: @setOpenSubOptions
        multiSelect: multiSelect
        multiSelectGroupId: multiSelectGroupId
        isOpen: childItems.length and openSubOptions is option
      }, childItems

    pvrProps.scale = scale
    pvrProps.close = close

    pvrProps.element = div {
      key: 'select-pvr'
      className: 'select-pvr'
      style: style
    }, [
        div {
          key: 'header'
          className: "header plain-pvr-content-item #{headerClass}"
        }, headerTitle if @hasHeader
        div {
          key: 'inner-rows'
        }, optionEls
        footerComponent if footerComponent?
      ]

    Pvr(pvrProps)

  handleChange: (option, multiSelectGroupId) ->
    {multiSelect, multiSelectKey} = @props
    {selectedOptions} = @state
    @changeOnUnmount = @multiSelectGroupIds.get(multiSelectGroupId) > 1
    opt = {}

    # Multi-select
    if multiSelect
      assign opt, selectedOptions
      # Maintain selection by group ids
      if multiSelectGroupId? and multiSelectGroupId isnt selectedOptions[Object.keys(selectedOptions)[0]]?.multiSelectGroupId
        opt = {}

      # De-select it
      if opt[option[multiSelectKey]]?
        delete opt[option[multiSelectKey]]
        
      # Select it
      else
        opt[option[multiSelectKey]] = option

    # No multi selection
    else
      opt = option
    
    @setState
      selectedOptions: opt

    unless @changeOnUnmount
      @props.onChange opt
      @props.close()

  setOpenSubOptions: (option, adjust) ->
    {options, optionHeight} = @props
    agg = options.length * optionHeight + (options.length * 1) + adjust
    @setState
      openSubOptions: option
      aggregateOptionsHeight: agg

  compareOptions: (option) ->
    {selectedOptions} = @state
    {multiSelect} = @props
    {label} = option

    if typeof selectedOptions is 'string'
      return selectedOptions is label
    else if typeof selectedOptions is 'object'
      if multiSelect
        for k, o of selectedOptions
          if isEqual(o, option)
            return true
        return false
      else
        return isEqual(selectedOptions, option)

    
module.exports = SelectPvr