import { dispatch  as createDispatch }  from 'd3-dispatch'
import { rebind, forward } from '@zambezi/d3-utils'
import { reduce, indexBy, findWhere } from 'underscore'
import { select } from 'd3-selection'
import { unwrap } from '@zambezi/grid'

import './cell-selection.css'

export function createCellSelection() {

  const dispatch = createDispatch('cell-selected-change')

  let gesture = 'click'
    , selected = []
    , selectedCandidates
    , selectedRowsByColumnId = {}
    , columnById = {}
    , active

  function cellSelection(s) {
    s.each(cellSelectionEach)
  }

  cellSelection.selected = function(value) {
    if (!arguments.length) return selected
    selected = value
    selectedCandidates = value
    return cellSelection
  }

  cellSelection.active = function(value) {
    if (!arguments.length) return active
    active = value
    return cellSelection
  }

  return rebind().from(dispatch, 'on')(cellSelection)

  function cellSelectionEach(rows, i) {
    const target = select(this)

    if (selectedCandidates) updateFromCandidates()

    rows.dispatcher
        .on('cell-enter.cell-selection', onCellEnter)
        .on('cell-update.cell-selection', onCellUpdate)

    function updateFromCandidates() {
      selectedRowsByColumnId = selectedCandidates.reduce(toRealSelection, {})
      selected = compileSelected()
      selectedCandidates = null
    }

    function toRealSelection(acc, { row, column }) {
      const columnFound = typeof column == 'string' ? columnById[column] : column
          , rowFound = typeof row == 'number' ? rows[row] : row

      if (!columnFound || !rowFound) {
        console.warn("Couldn't find cell for", { row, column })
        return acc
      }

      const columnId = columnFound.id
          , set = acc[columnId] || new Set()

      set.add(rowFound)
      acc[columnId] = set
      return acc
    }

    function compileSelected() {
      columnById = indexBy(rows.columns, 'id')
      return reduce(selectedRowsByColumnId, toCells, [])
    }

    function onCellEnter(d, i) {
      select(this).on('click.cell-selection', onClick)
    }

    function onClick(d, i) {
      const column = d.column
          , columnId = column.id
          , set = selectedRowsByColumnId[columnId] || new Set()
          , row = unwrap(d.row)

      // Here'd be fancier Shift+Click all that jazz
      if (set.has(row)) {
        set.delete(row)
      } else {
        set.add(row)
        active = { row, column }
      }

      selectedRowsByColumnId[columnId] = set
      selected = compileSelected()
      dispatch.call('cell-selected-change', this, selected, active)
      select(this).dispatch('redraw', { bubbles: true })
    }

    function toCells(acc, set, columnId) {
      const column = columnById[columnId]
      return acc.concat(Array.from(set.values()).map(row => ({ row, column })))
    }
  }

  function onCellUpdate(d, i){
    select(this)
        .classed('is-selected', isCellSelected)
        .classed('is-active', isCellActive)
  }

  function isCellSelected(d, i) {
    const rowSetForColumn = selectedRowsByColumnId[d.column.id]
    return rowSetForColumn && rowSetForColumn.has(unwrap(d.row))
  }

  function isCellActive(d) {
    if (!active) return false
    return areSameCell(d, active)
  }
}

function areSameCell(a, b) {
  return a && b && unwrap(a.row) == unwrap(b.row) && a.column == b.column
}

@@ -175,16 +175,23 @@
 %7B select
+, event
  %7D from 
@@ -2254,29 +2254,16 @@
 olumnId%5D
- %7C%7C new Set()
 %0A       
@@ -2291,232 +2291,423 @@
 ow)%0A
-%0A
       
-// Here'd be fancier Shift+Click all that jazz%0A      if (set.has(row)) %7B%0A        set.d
+    , shift = event.shiftKey%0A          , ctrl = event.ctrlKey%0A          , target = %7B row, column %7D%0A          , isAlreadySelected = set && set.has(row)%0A%0A      switch(true) %7B%0A        case ctrl && isAlreadySelected: %0A          removeFromS
 ele
+c
 te
-(row)%0A      %7D else %7B%0A        set.add(row)%0A        active = %7B row, column %7D%0A      %7D%0A%0A      selectedRowsByColumnId%5BcolumnId%5D = s
+d(target)%0A          break%0A        case ctrl: %0A          addToSelected(target)%0A          break%0A        default:%0A          selectOnly(target)%0A      %7D%0A%0A      active = targ
 et%0A 
@@ -2736,24 +2736,25 @@
 eSelected()%0A
+%0A
       dispat
@@ -2865,32 +2865,525 @@
  true %7D)%0A    %7D%0A%0A
+    function selectOnly(target) %7B%0A      selectedRowsByColumnId = %7B%7D%0A      addToSelected(target)%0A    %7D%0A%0A    function removeFromSelected(%7B row, column %7D) %7B%0A      const columnId = column.id%0A          , set = selectedRowsByColumnId%5BcolumnId%5D %0A%0A      if (set) set.delete(row)%0A    %7D%0A%0A    function addToSelected(%7B row, column %7D) %7B%0A      const columnId = column.id%0A          , set = selectedRowsByColumnId%5BcolumnId%5D %7C%7C new Set()%0A%0A      set.add(row)%0A      selectedRowsByColumnId%5BcolumnId%5D = set%0A    %7D%0A%0A
     function toC

Implement "add to selection" and "remove from selection"