import { appendFromTemplate, selectionChanged } from '@zambezi/d3-utils'
import { property, batch, identity } 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()
    , rowIndexChanged = selectionChanged().key(property('index'))
    , firstLastChanged = selectionChanged().key(firstAndLast)
    , id = property('id')
    , isFirst = property('isFirst')
    , isLast = property('isLast')
    , rowIndexMatch = /(\bgrid-row-index-\d+\b|$)/

export function createCells() {
  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 cells

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

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

        , rowsExit = rows.exit()
              .remove()
              // .each(dispatcher['row-exit'])

        , rowsEnter = rows.enter()
            .select(appendRow)
              // .each(dispatcher['row-enter'])

        , rowChanged = rows
              // .each(dispatcher['row-update'])
            .merge(rowsEnter)
            .select(rowIndexChanged)
              .each(updateRow)
            .select(
              changed.key(
                orderAndKey(rowChangedKey, visibleCellsHash)
              )
            )
              // .each(dispatcher['row-changed'])

    const cellsUpdate = rowChanged.selectAll('.zambezi-grid-cell')
            .data(identity, id)

        , cellsExit = cellsUpdate.exit()
              .remove()
              // .each(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.dispatcher = dispatcher
    d.column.components.forEach(runColumnComponent)
    function runColumnComponent(component) {
      component.call(cell, d, i, j)
    }
  }

  function updateRow(d) {
    var index = d.index
      , className = this.className
      , rowClass = 'grid-row-index-' + index
      , newClassName = className.replace(rowIndexMatch, useClass(rowClass))
      , selector = `#${ gridId } .zambezi-grid-body .${ rowClass }`

    if (className == newClassName) return

    this.className = newClassName

    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
}

@@ -438,74 +438,8 @@
 d()%0A
-    , rowIndexChanged = selectionChanged().key(property('index'))%0A
     
@@ -2001,16 +2001,46 @@
  = rows%0A
+            .merge(rowsEnter)%0A
         
@@ -2084,106 +2084,8 @@
 '%5D)%0A
-            .merge(rowsEnter)%0A            .select(rowIndexChanged)%0A              .each(updateRow)%0A
     
@@ -2211,32 +2211,63 @@
 )%0A            )%0A
+              .each(updateRow)%0A
               //
@@ -2301,29 +2301,29 @@
 ged'%5D)%0A%0A    
-const
+    ,
  cellsUpdate
@@ -2586,16 +2586,16 @@
 append)%0A
-
         
@@ -2629,24 +2629,25 @@
 otifyEnter)%0A
+%0A
         , ce

fix incorrect stale rows.