import { appendIfMissing, selectionChanged }  from '@zambezi/d3-utils'
import { findIndex } from 'underscore'
import { select } from 'd3-selection'

import './highlight-selected-cells.css'

const highlightContainer = appendIfMissing('div.selected-cells-highlight.zambezi-grid-overlay')

export function createHighlightSelectedCells() {
  let selectedCells = []
    , borderCache

  function highlightSelectedCells(s) {
    s.each(highlightSelectedCellsEach)
  }

  highlightSelectedCells.selectedCells = function(value) {
    if (!arguments.length) return selectedCells
    selectedCells = value
    borderCache = null
    return highlightSelectedCells
  }

  return highlightSelectedCells

  function highlightSelectedCellsEach(bundle) {
    const { columns, rows, scroll, rowHeight } = bundle
        , container = select(this)
              .on(
                'data-dirty.highlight-selected-cells',
                () => borderCache = null
              )
            .select('.zambezi-grid-body')
            .select(highlightContainer)
              .text('HIGHLIGHTCONTAINER')
              .style('transform', `translate(${-scroll.left}px, ${-scroll.top}px)`)

    if (!borderCache)  {
      borderCache = compileBorderLayout()
    }

    container.call(renderCellHighlights)

    function renderCellHighlights(s) {
      const update = s.selectAll('.zambezi-grid-cell-highlight')
                .data(borderCache)
          , enter = update.enter()
                .append('span')
                  .classed('zambezi-grid-cell-highlight', true)
                  .text((d, i) => i)

          , exit = update.exit().remove()
          , merged = update.merge(enter)
                .each(configureHighlightCell)
    }

    function configureHighlightCell(d, i) {
      const { column
            , columnIndex
            , hasBottomBorder
            , hasLeftBorder
            , hasRightBorder
            , hasTopBorder
            , row
            , rowIndex
            } = d

      select(this)
              .style('top', `${rowIndex * rowHeight}px`)
              .style('height', `${rowHeight}px`)
              .classed(`c-${column.id}`, true)
              .classed('has-bottom-border', hasBottomBorder)
              .classed('has-left-border', hasLeftBorder)
              .classed('has-top-border', hasTopBorder)
              .classed('has-right-border', hasRightBorder)
    }

    function compileBorderLayout() {
      const rowToIndex = new Map()
          , columnToIndex = new Map()
          , selectedCellGrid = []

      return selectedCells
          .reduce(toCoords, [])
          .reduce(toBorderCells, [])
          .filter(hasAnyBorder)

      function toCoords(acc, { row, column }) {
        let columnIndex = columnToIndex.has(column) ? 
                columnToIndex.get(column) : findIndex(columns, column)
          , rowIndex = rowToIndex.has(row) ?
                rowToIndex.get(row) : findIndex(rows, row)
          , gridRow = selectedCellGrid[rowIndex] || []
          , cell = { row, column, rowIndex, columnIndex }

        gridRow[columnIndex] = cell

        columnToIndex.set(column, columnIndex)
        rowToIndex.set(row, rowIndex)
        selectedCellGrid[rowIndex] = gridRow
        acc.push(cell)
        return acc
      }

      function toBorderCells(acc, cell) {
        const { rowIndex, columnIndex } = cell
            , hasLeftBorder = !selectedCellGrid[rowIndex][columnIndex - 1]
            , hasRightBorder = !selectedCellGrid[rowIndex][columnIndex + 1]
            , hasTopBorder = !selectedCellGrid[rowIndex - 1] 
                  || !selectedCellGrid[rowIndex -1][columnIndex]
            , hasBottomBorder = !selectedCellGrid[rowIndex + 1] 
                  || !selectedCellGrid[rowIndex + 1][columnIndex]
            , borderCell = Object.assign(
                cell
              , { hasLeftBorder, hasRightBorder, hasTopBorder, hasBottomBorder }
              )

        if (hasAnyBorder(borderCell)) acc.push(borderCell)
        return acc
      }

      function hasAnyBorder({ hasLeftBorder, hasRightBorder, hasTopBorder, hasBottomBorder }) {
        return hasLeftBorder || hasRightBorder || hasTopBorder || hasBottomBorder
      }
    }
  }
}

@@ -278,16 +278,55 @@
 verlay')
+%0A    , borderDirty = selectionChanged()
 %0A%0Aexport
@@ -410,16 +410,42 @@
 derCache
+%0A    , borderRedrawGen = 0
 %0A%0A  func
@@ -1105,50 +1105,8 @@
 er)%0A
-              .text('HIGHLIGHTCONTAINER')%0A
     
@@ -1261,24 +1261,104 @@
     
-%7D%0A%0A    container
+  borderRedrawGen++%0A    %7D%0A%0A    container.select(borderDirty.key(() =%3E borderRedrawGen))%0A        
 .cal
@@ -1555,33 +1555,16 @@
 .enter()
-%0A                
 .append(
@@ -1585,181 +1585,178 @@
     
-        .classed('zambezi-grid-cell-highlight', true)%0A                  .text((d, i) =%3E i)%0A%0A          , exit = update.exit().remove()%0A          , merged = update.merge(enter
+, exit = update.exit().remove()%0A          , merged = update.merge(enter)%0A                .attr('class', null)%0A                .classed('zambezi-grid-cell-highlight', true
 )%0A  
@@ -1875,34 +1875,8 @@
 umn%0A
-            , columnIndex%0A
     
@@ -1989,26 +1989,8 @@
 der%0A
-            , row%0A
     

Implement cell border with selection changed guard