/** Helpers for managing a DetailsList. */

const R = require("ramda")
import {
    IColumn, ColumnActionsMode
} from 'office-ui-fabric-react/lib/DetailsList'

export const OTHER: string = "OTHER"
export type SortOrder = "asc" | "desc" | "OTHER"
export type SortOrderState = SortOrder | "FIRST"

/** Sorting state for a single column. */
export interface ColumnSortInfo {
    /** Sorting direction. */
    direction: SortOrder
    /** Position in column sorting order e.g. if firstname to be sorted *after* lastname, 
     *  pos=1 for firstname and pos=0 for lastname. 
     */
    position: number
}

/** Group of individual column sort infos. State key is column index or IColumn.key. */
export type SortingState = {
    [col: number]: ColumnSortInfo
    [col: string]: ColumnSortInfo
}

/** Transition table for sorting direction state machine. */
export interface SortOrderTransitions extends Partial<Record<SortOrder, SortOrder>> {
    /** Starting state is required. */
    FIRST: SortOrder
}

/** 
 * Default sort order state machine table. You can also store these as state in
 * in your component and provide them to the byColumns* functions.
 */
export const defaultOrder: SortOrderTransitions = {
    FIRST: "asc",
    OTHER: "asc",
    asc: "desc",
    desc: "OTHER"
}

/**
 * Previous SortingState => new SortingState (state machine). Only allows single
 * column sorting. Cycles through sorting order if its already sorted on that column.
 */
export function byColumn({ current,
                           transitions = defaultOrder,
                           selectedColumn = -1,
                           defaultPosition = 0}:{
                               current: SortingState
                               transitions: SortOrderTransitions
                               selectedColumn: number|string
                               defaultPosition?: number
                           }): SortingState
{

    if(typeof selectedColumn === "number" && selectedColumn < 0) return current
    let nextState: SortOrder | undefined = transitions.FIRST
    
    // find if current column is in current state, so we can cycle it
    const maybeCurCol = current[selectedColumn]
    if(maybeCurCol) {
        nextState = transitions[maybeCurCol.direction] // returns next state
        if(!nextState) return {}
    }
    return {
        [selectedColumn]: {
            direction: nextState,
            position: defaultPosition
        }
    }
}

/** 
 * Updates a multi column sorting state by updating the selectedColumn.
 * If selectedColumn is new to the sorting state, it is placed last in the
 * positions.
 */
export function byColumns({ current,
                            transitions = defaultOrder,
                            selectedColumn = -1}:{
                                current: SortingState,
                                transitions: SortOrderTransitions,
                                selectedColumn: number|string
                            }): SortingState
{
    // get last position, assume sorting state is "short"
    const max = Math.max(...Object.keys(current ? current :{}).map(k => current[k].position)) + 1
    const alreadyExists = current ? (current[selectedColumn] ? true : false): false
    let newOrUsed =  alreadyExists ?
                     byColumn({current, transitions, selectedColumn, defaultPosition: current[selectedColumn].position}):
                     byColumn({current, transitions, selectedColumn, defaultPosition: max})
    return Object.assign({}, current, newOrUsed)
}

/** Sort data. */
export type Sorter<T> = (a: Array<T>) => Array<T>

/** Create Sorter functions given sorting information. */
export type SortFunctionFactory<T> = (info: Array<{property: string, direction: SortOrder}>) => Sorter<T>

/** Get the property name of the data accessor used for sorting from an IColumn. */
function getSortAttribute(c: IColumn): string {
    if(c.data && c.data.sortAttribute) return c.data.sortAttribute
    else return c.fieldName
}

/** 
 * Create a Sorter given columns, sorting state and a sort function factory. 
 * Uses IColumn.key to lookup fieldname|data.sortAttribute if sorting state has a property name as a key,
 * or IColumn[index] to lookup the column.
 */
export const sorter = <T>({ columns,
                            state,
                            factory = ramdaSortFunctionFactory}:
                          { columns: Array<IColumn>,
                            factory?: SortFunctionFactory<T>,
                            state: SortingState
                          }): Sorter<T> =>
                              {
                                  // flatten sorting state
                                    const x = Object.keys(state).map(k => {
                                        const sortInfo = state[k]
                                        let p: string = (typeof k === "number") ?
                                                        getSortAttribute(columns[k]):
                                                        getSortAttribute(columns.find(c => c.key === k)!)
                                        return { property: p,
                                                 direction: sortInfo.direction,
                                                 order: sortInfo.position }
                                    })
                                  return factory ?
                                         factory(R.sort(i => i.order, x)) :
                                         ramdaSortFunctionFactory(R.sort(i => i.order, x))
                                }

/** Sort function factory that uses ramda sortWith. */
export function ramdaSortFunctionFactory<T>(info: Array<{property: string,
                                                         direction: SortOrder}>): Sorter<T>
{

    const comparators = info.filter(i => i.direction !== OTHER).
                             map(i => {
                                 if(i.direction === "asc") return R.ascend(R.prop(i.property))
                                 else return R.descend(R.prop(i.property))
                             })
    return (comparators.length === 0 ?
           R.identity:
           R.sortWith(comparators)) as Sorter<T>
}

/** Update column definitios based on the column key and the map of updates. */
export function updateColumns(columns: IColumn[],
                            updates: { [key:string]: Partial<IColumn>}): IColumn[]
{
    return columns.map(c => {
        const update = { ...c , ...updates[c.key]}
        return update
    })
}

/**
 * Enhance a list of column-like information based on the sortState. All columns are
 * are sortable unless data.isSortable = false.
 * You can set a DetailsList.onColumnHeaderClick instead of passing in onSortColumn.
 */
export function augmentColumns(cols: any[], sortState: SortingState,
                               onSortColumn?: (c: IColumn) => void,
                               getMoreProps?: (c: IColumn, idx: number) => Record<string,any>): IColumn[]
{
    return cols.map((c, idx) => {
        if(c.data && typeof c.data.isSortable === "boolean" && !c.data.isSortable)
            return { ...c }
        
        const sortInfo: ColumnSortInfo | undefined = sortState[c.key]
        const isSorted = sortInfo ? (sortInfo.direction !== OTHER) : undefined
        const isSortedDescending = isSorted ?
                                   (sortInfo.direction === "desc" ? true : false) : undefined
        const moreProps = getMoreProps ? getMoreProps(c, idx) : {}
        const onCC = onSortColumn ?
                     {onColumnClick: (x, col) => { if (col) onSortColumn(col) }}:
                     {}
        return {
            ...c,
            ...moreProps,
            isSorted,
            isSortedDescending,
            ...onCC,
        }
    })
}

/**
 * Given a request to sort, update critical key parts of the sorting infrastructure.
 * You will need to sort your data with the returned sorter function. The returned
 * columns are updated with the new sort state (e.g. isSorted, isSortedDescending).
 */
export function onSortColumn<T>(c: IColumn, columns: IColumn[],
                                sortState: SortingState,
                                transitions: SortOrderTransitions,
                                factory: SortFunctionFactory<T> = ramdaSortFunctionFactory) {
    // create the new sort state
    const state = byColumns({
        current: sortState,
        transitions,
        selectedColumn: c.key,
    })
    
    // augment the columns with the new sort state
    const newColumns = augmentColumns(columns, state)
    
    // create the new sorter function
    const newSorter = sorter<T>({
        columns: newColumns,
        state,
        factory,
    })
    
    return {
        columns: newColumns,
        sortState: state,
        sorter: newSorter,
    }
}
