//////////////////////////////////////////////////////////////
// Copyright (c) 2017 Ben Jackman
// All Rights Reserved
// please contact ben@jackman.biz
// for licensing inquiries
//////////////////////////////////////////////////////////////
//MIT license granted to ESGI

//Quickly make a table with bells and whistles

// import { Grid, Data, Formatters } from 'slickgrid-es6';
// require('slickgrid-es6')
//console.log(slickGrid as Slick.SlickData)

import * as React from "react"
import * as _ from "lodash"

import {CreateSlickGrid, CreateSlickAutoTootips} from "./SlickWrap"
import {CreateSlickDataView} from "./SlickWrap"
import * as Papa from "papaparse"
import {HeaderFilters} from "./HeaderFilter"
import {Cls} from "h-cls"
import {Global} from "h-ts"

export type QIDS = {id: any; idx: number}

interface QColProps<T> {
  getFilterValue?(item: T): {} | null
}
export type QCol<T> = QColProps<T> & Slick.Column<T>

export type QTableProps<T extends QIDS> = {
  cols: QCol<T>[]
  rows: T[]
  ready?: (qtable: QTable<T>) => void
  className?: string
}

// type Props<T  extends {id : any, idx: number}> = React.HTMLProps<HTMLDivElement> & QTableProps<T>
type Props<T extends QIDS> = QTableProps<T>

export class TableFilters<T extends QIDS> {
  filterFn = (item: T, args: any): boolean => true

  // updated() {
  //   this.dataview.refresh()
  // }
}

export class QTable<T extends QIDS> extends React.Component<Props<T>, {}> {
  tableEl = React.createRef<HTMLDivElement>()
  dataView: Slick.Data.DataView<T> = CreateSlickDataView<T>()
  headerFilters = new HeaderFilters<T>(this.dataView)
  tableFilters = new TableFilters<T>()

  grid!: Slick.Grid<T>

  constructor(props: Props<T>) {
    super(props)
  }

  enableAutoTooltips() {
    this.grid.registerPlugin(CreateSlickAutoTootips())
  }


  componentWillReceiveProps(
    nextProps: Readonly<Props<T>>,
    nextContext: any,
  ): void {
    //Todo do this in a much better way, right now this will probably just
    //end up splashing items at the end of the list-o-reeno

    //Allow the initial update of rows
    if (this.props.rows.length == 0) {
      this.dataView.beginUpdate()
      nextProps.rows.forEach(row => this.dataView.addItem(row))
      this.dataView.endUpdate()
      this.dataView.refresh()
    }

    // this.dataView.beginUpdate()
    // this.dataView.getItems().length = 0
    // this.dataView.endUpdate()
    // this.dataView.beginUpdate()
    // nextProps.rows.forEach(row => this.dataView.addItem(row))
    // this.dataView.endUpdate()
    // this.grid.invalidateAllRows()
    // this.grid.render()

    //Someday i'll make this work (Sheyal right!)
  }

  refresh() {
    this.dataView.beginUpdate()
    this.dataView.getItems().length = 0
    this.props.rows.forEach(row => this.dataView.addItem(row))
    this.dataView.endUpdate()
    this.dataView.refresh()
    this.grid.invalidateAllRows()
    this.grid.render()
  }


  getVisibleItems(): T[] {
    const grid = this.grid
    //Get the headers as an array
    return _.range(grid.getDataLength()).map(i => grid.getDataItem(i))
  }

  getCsv(opts: {fieldToHeaderName?: (field: string) => string}): string {
    const grid = this.grid
    const cols = grid.getColumns().map(c => c.field!)
    const headers =
      opts.fieldToHeaderName != null
        ? cols.map(f => opts.fieldToHeaderName!(f))
        : cols
    const items = this.getVisibleItems()
    const csvHdr = Papa.unparse([headers])
    const csvBody = Papa.unparse(
      {
        fields: cols,
        data: items,
      },
      {
        header: false,
        quotes: false,
        delimiter: ",",
        newline: "\n",
      } as any,
    )
    return csvHdr.trim() + "\n" + csvBody
  }

  getByRowCell = (args: {
    row: number
    cell: number
  }): {field?: string; item?: T; value?: any} => {
    const item = this.dataView.getItem(args.row)
    if (item == null) {
      return {}
    } else {
      const field = this.grid.getColumns()[args.cell].field
      if (field != null) {
        const value = (item as any)[field]
        return {field, item, value}
      } else {
        return {item}
      }
    }
  }

  resizeHandler = () => {
    setTimeout(() => {
      this.grid.resizeCanvas()
    }, 0)

    // console.log("RESIZE CANVAS0")
    // _.throttle(() => {
    //   console.log("RESIZE CANVAS1")
    //
    // }, 1000);
  }

  clearSort = () => {
    this.grid.setSortColumns([])
    this.dataView.sort((a: T, b: T) => a.idx - b.idx, true)
  }

  moveActiveCellBy = (by: {row?: number; col?: number}) => {
    const r = by.row || 0
    const c = by.col || 0
    const {row, cell} = this.grid.getActiveCell()
    this.grid.setActiveCell(row + r, cell + c)
  }

  moveActiveCellTo = (to: {row?: number; col?: number}) => {
    const {row: r, cell: c} = this.grid.getActiveCell()
    const row = to.row == null ? r : to.row
    const cell = to.col == null ? r : to.col

    this.grid.setActiveCell(row, cell)
  }

  componentDidMount() {
    const dv = this.dataView
    const cols = this.props.cols

    const grid = (this.grid = CreateSlickGrid<any>(
      this.tableEl.current!,
      this.dataView,
      cols,
      {
        showHeaderRow: true,
        editable: true,
        leaveSpaceForNewRows: true,
        enableCellNavigation: true,
        enableColumnReorder: false,
        explicitInitialization: true,
        multiColumnSort: true,
        autoEdit: false,
        // forceFitColumns: true,
      },
    ))

    Global.instance.DEBUG_GRID = grid

    dv.onRowCountChanged.subscribe(() => {
      grid.updateRowCount()
      grid.render()
    })

    dv.onRowsChanged.subscribe((e, args) => {
      grid.invalidateRows(args.rows)
      grid.render()
    })

    const filterFn = (item: T, args: any): boolean => {
      return (
        this.tableFilters.filterFn(item, args) &&
        this.headerFilters.filterFn(item, args)
      )
    }

    //set the
    dv.setFilter(filterFn)
    //Header filters
    grid.onHeaderRowCellRendered.subscribe((e, args) => {
      const myNode = args.node
      while (myNode.firstChild) {
        myNode.removeChild(myNode.firstChild)
      }

      const filter = this.headerFilters.createAndAdd(args.column)
      args.node.appendChild(filter.el)
    })

    grid.onColumnsResized.subscribe((e, args) => {
      console.log(grid.getColumns().map(c => c.width))
    })

    //Sorting
    grid.onSort.subscribe((e, args) => {
      console.log("ON SORT CALLED")
      const sortCols =
        args.sortCols && args.sortCols.length > 0
          ? args.sortCols
          : args.sortCol
            ? [
                {
                  sortCol: args.sortCol,
                  sortAsc: args.sortAsc,
                },
              ]
            : null
      if (sortCols != null) {
        ;(dv as any).sort((a: any, b: any) => {
          let i = 0
          let ret = 0
          while (i < sortCols.length && ret == 0) {
            let col = sortCols[i]
            let sign = sortCols[i].sortAsc ? 1 : -1
            let a1 = a[col.sortCol.field!]
            let b1 = b[col.sortCol.field!]
            const res = a1 - b1
            if (_.isNaN(res)) {
              const a2 = (a1 || "").toLowerCase()
              const b2 = (b1 || "").toLowerCase()
              const cmp = a2.localeCompare(b2)
              if (cmp != 0) {
                return cmp * sign
              }
            } else {
              return sign * res
            }
            i += 1
          }
          return ret
        })
      } else {
        //Clear any existing sort
        console.log("Clearing sort")
        dv.sort((a: any, b: any) => a.idx - b.idx, true)
      }
      grid.invalidate()
      grid.render()
    })
    grid.init()
    setTimeout(() => {
      grid.resizeCanvas()
    }, 0)

    dv.beginUpdate()
    this.props.rows.forEach(row => dv.addItem(row))
    dv.endUpdate()
    dv.refresh()

    window.addEventListener("resize", this.resizeHandler)

    if (this.props.ready) {
      this.props.ready(this)
    }
  }

  componentWillUnmount() {
    window.removeEventListener("resize", this.resizeHandler)
  }

  render() {
    return (
      <div
        className={
          Cls(this.props.className, "qtable", "slickgrid-container").className
        }
      >
        <style>
          {//language=SCSS prefix="*{" suffix="}"
          `
            
            .qtable .slick-header-input-cell {
              width: calc(100% - 6px);
              margin: 1px;
            }

            .qtable .slick-cell {
              text-overflow: clip;
            }

            .qtable.slickgrid-container {
              height: 100%;
            }

            .qtable.slickgrid-container .slick-header-column {
              text-overflow: clip;
            }

            .qtable.slickgrid-container .slick-header-column .slick-column-name {
              margin: 0
            }

            .qtable {
              display: flex;
              flex-direction: column;
              flex: 1;
            }

            .qtable .slick-ref {
              display: flex;
              flex-direction: column;
              flex: 1;
            }
          `}
        </style>
        <div className="slick-ref" ref={this.tableEl} />
      </div>
    )
  }
}

//COMMON PATTERNS
//--------------
// Clicking
//----
// grid.onClick.subscribe((e: DOMEvent, args) => {
//   const item = dv.getItem(args.row)
//   const key = grid.getColumns()[args.cell].field!
//   //   if (e.shiftKey) {
//   //     refreshPane.refreshContainer(item.containerId)
//   //   }
//   // console.log("CLICKEROO", e, args, item, key)
//   if (this.props.clicked) {
//     this.props.clicked(e as MouseEvent, item, key)
//   }
//
// })

// MOUSE OVER
//----
//Add mouse over logic
// table.grid.onMouseEnter.subscribe((e: MouseEvent, args) => {
//   const cell = table.grid.getCellFromEvent(e)
//   if (cell != null) {
//     const item = table.dataView.getItem(cell.row)
//     if (this.refTooltip) {
//       console.log("Entered cell", cell, item)
//       // this.refTooltip.show(e.clientX, e.clientY)
//     }
//   }
// })
// table.grid.onMouseLeave.subscribe((e, args) => {
//   console.log("Left cell")
//   if (this.refTooltip) {
//     this.refTooltip.hide()
//   }
//   // itemDetailHover.hide()
// })

// export abstract class QData {
//   static forArray<R>(rows: Array<R>,
//                      colnames: Array<string>,
//                      valueFn: (cname: string, cidx: number, r: R) => string): QData {
//     return new class QDataForArray extends QData {
//       get rowCnt(): number {
//         return rows.length;
//       }
//
//       get colCnt(): number {
//         return colnames.length
//       }
//
//       getColname(ci: number): string {
//         return colnames[ci]
//       }
//
//       getRC(ri: number, ci: number): string {
//         return valueFn(colnames[ci], ci, rows[ri])
//       }
//     }
//   }
//
//   abstract getColname(c: number): any
//
//   abstract getRC(r: number, c: number): string
//
//   abstract get rowCnt(): number
//
//   abstract get colCnt(): number
//
//
// }

// class SlickHelp {
//
//   getTableInfo(colnames: ColInfo[], rows: Array<any>) {
//
//   }
//
//   createSlickColumns(): Slick.Column<any>[] {
//     function makeColumn(name: string, tooltip: string, width: number = -1, f: (row: any) => any) {
//       return {
//         id: name,
//         name: name,
//         field: name,
//         toolTip: tooltip,
//         sortable: true,
//         width: width === -1 ? 50 : width,
//         getter: f,
//       }
//     }
//   }
// }
