import { appendFromTemplate, selectionChanged, rebind, forward } from '@zambezi/d3-utils'
import { dataset } from './dataset'
import { dispatch as createDispatch } from 'd3-dispatch'
import { property, batch } from '@zambezi/fun'
import { select } from 'd3-selection'

const appendDefaultCell = appendFromTemplate(
        '<span class="zambezi-grid-cell">'
      + '<span class="formatted-text"></span>'
      + '</span>'
      )
    , appendRow = appendFromTemplate('<li class="zambezi-grid-row"></li>')
    , changed = selectionChanged()
    , firstLastChanged = selectionChanged().key(firstAndLast)
    , id = property('id')
    , isFirst = property('isFirst')
    , isLast = property('isLast')

export function createCells() {

  const dispatcher = createDispatch(
          'cell-enter'
        , 'cell-exit'
        , 'cell-update'
        , 'row-changed'
        , 'row-enter'
        , 'row-exit'
        , 'row-update'
        )
      , api = rebind().from(dispatcher, 'on')
      , appendByTemplate = {}

  let rowKey
    , rowChangedKey
    , sheet
    , gridId

  function cells(s) {
    s.each(cellsEach)
  }

  cells.rowKey = function(value) {
    if (!arguments.length) return rowKey
    rowKey = value
    return cells
  }

  cells.rowChangedKey = function(value) {
    if (!arguments.length) return rowChangedKey
    rowChangedKey = value
    return cells
  }

  cells.sheet = function(value) {
    if (!arguments.length) return sheet
    sheet = value
    return cells
  }

  cells.gridId = function(value) {
    if (!arguments.length) return gridId
    gridId = value
    return cells
  }

  return api(cells)

  function cellsEach(d, i) {
    const block = this
        , list = select(this)
        , visibleCellsHash = d.visibleCellsHash
        , columnComponentsAndNotifyUpdate = batch(
            runColumnComponents
          , forward(dispatcher, 'cell-update')
          )
        , columnClassAndNotifyEnter = batch(
            columnClass
          , forward(dispatcher, 'cell-enter')
          )

        , rows = list.selectAll('.zambezi-grid-row')
            .data(d, dataKey(rowKey))

        , rowsExit = rows.exit()
              .remove()
              .each(forward(dispatcher, 'row-exit'))

        , rowsEnter = rows.enter()
            .select(appendRow)
              .each(forward(dispatcher, 'row-enter'))

        , rowChanged = rows
            .merge(rowsEnter)
              .each(forward(dispatcher, 'row-update'))
              .each(updateRow)
              .call(dataset, 'gridRowIndex', d => d.index)
            .select(
              changed.key(
                orderAndKey(rowChangedKey, visibleCellsHash)
              )
            )
              .each(forward(dispatcher, 'row-changed'))

        , cellsUpdate = rowChanged.selectAll('.zambezi-grid-cell')
            .data(d => d, id)

        , cellsExit = cellsUpdate.exit()
              .remove()
              .each(forward(dispatcher, 'cell-exit'))

        , cellsEnter = cellsUpdate.enter()
            .select(append)
              .each(columnClassAndNotifyEnter)

        , cellsMerged = cellsUpdate.merge(cellsEnter)

    cellsMerged.select(firstLastChanged).each(updateFirstLast)
    cellsMerged.each(columnComponentsAndNotifyUpdate)
  }

  function updateFirstLast() {
    select(this)
        .classed('is-first', isFirst)
        .classed('is-last', isLast)
  }

  function append(d, i, j) {
    let template = d.column.template
      , templateAppend
      , cell

    if (!template) return appendDefaultCell.call(this, d, i, j)

    templateAppend = appendByTemplate[template]

    if (!templateAppend) {
      templateAppend
        = appendByTemplate[template]
        = appendFromTemplate(template)
    }

    cell =  templateAppend.call(this, d, i, j)
    select(cell).classed('zambezi-grid-cell', true)
    return cell
  }

  function runColumnComponents(d, i, j) {
    const cell = this
    if (!d.column.components) return
    d.column.components.forEach(runColumnComponent)
    function runColumnComponent(component) {
      component.call(cell, d, i, j)
    }
  }

  function updateRow(d) {
    const index = d.index
        , selector = `#${ gridId } [data-grid-row-index="${index}"]`

    sheet(selector, { top: top(d) })
  }
}

function columnClass(d, i) {
  select(this).classed('c-' + d.column.id , true)
}

function useClass(rowClass) {
  return function oldClass(c) {
    return (c ? '' : ' ') +  rowClass
  }
}

function top(d) {
  return d.top + 'px'
}

function orderAndKey(rowChangedKey, visibleCellsHash) {
  if (!rowChangedKey) return null
  return function keyPlusHash(blockRow, i) {
    if (!blockRow.length) return ''
    return (
      visibleCellsHash
    + '|'
    + rowChangedKey.call(this, blockRow[0].row, i)
    )
  }
}

function dataKey(rowKey) {
  if (!rowKey) return null
  return function rowDataKey(blockRow, i) {
    return rowKey.call(this, blockRow[0].row, i)
  }
}

function firstAndLast(d) {
  return d.isFirst + '★' + d.isLast
}


@@ -87,44 +87,8 @@
 ls'%0A
-import %7B dataset %7D from './dataset'%0A
 impo
@@ -556,24 +556,94 @@
 rstAndLast)%0A
+    , indexChanged = selectionChanged().key(d =%3E d.index).debug(true)%0A
     , id = p
@@ -2499,39 +2499,8 @@
 '))%0A
-              .each(updateRow)%0A
     
@@ -2519,45 +2519,17 @@
 all(
+up
 dat
-aset, 'gridRowIndex', d =%3E d.index
+eRow
 )%0A  
@@ -4097,25 +4097,25 @@
 dateRow(
-d
+s
 ) %7B%0A    
 const in
@@ -4110,135 +4110,111 @@
     
-const index = d.index%0A        , selector = %60#$%7B gridId %7D %5Bdata-grid-row-index=%22$%7Bindex%7D%22%5D%60%0A%0A    sheet(selector, %7B top: top(d) %7D
+s.select(indexChanged).each(updateTop)%0A  %7D%0A%0A  function updateTop(d) %7B%0A    select(this).style('top', top
 )%0A  

Use inline styles for rows