import type {
  Column,
  ColumnFilter,
  ColumnFilterValue,
  Group,
  Item,
  Sorter,
  SorterValue,
  TableFilter,
} from './types'

export const filterColumns = (
  items: Item[],
  columnFilter: boolean | ColumnFilter | undefined,
  columnFilterState: ColumnFilterValue,
  itemsDataColumns: string[],
): Item[] => {
  if (columnFilter && typeof columnFilter === 'object' && columnFilter.external) {
    return items
  }

  if (Object.entries(columnFilterState).length === 0) {
    return items
  }

  let filteredItems = [...items]

  for (const [key, value] of Object.entries(columnFilterState)) {
    if (typeof value === 'function') {
      filteredItems = filteredItems.filter((item) => value(item[key]))
    } else {
      const columnFilterValue = String(value).toLowerCase()
      if (columnFilterValue && itemsDataColumns.includes(key)) {
        filteredItems = filteredItems.filter((item) =>
          String(item[key]).toLowerCase().includes(columnFilterValue),
        )
      }
    }
  }

  return filteredItems
}

export const filterTable = (
  items: Item[],
  tableFilter: boolean | TableFilter | undefined,
  tableFilterState: string,
  itemsDataColumns: string[],
): Item[] => {
  if (
    !tableFilterState ||
    (tableFilter && typeof tableFilter === 'object' && tableFilter.external)
  ) {
    return items
  }

  const filter = tableFilterState.toLowerCase()

  return items.filter((item) =>
    itemsDataColumns.some((key) => String(item[key]).toLowerCase().includes(filter)),
  )
}

export const getClickedColumnName = (
  target: HTMLElement,
  columnNames: string[],
  selectable?: boolean,
): string => {
  const clickedCell = target.closest('td')
  if (!clickedCell) {
    return ''
  }

  const row = clickedCell.closest('tr')
  if (!row) {
    return ''
  }

  const cells = Array.from(row.children)
  let index = cells.indexOf(clickedCell)

  if (selectable) {
    index -= 1
  }

  return columnNames[index] || ''
}

export const getColumnKey = (column: Column | string): string =>
  typeof column === 'object' ? column.key : column

export const getColumnLabel = (column: Column | string): string =>
  typeof column === 'object' ? (column.label ?? pretifyName(column.key)) : pretifyName(column)

export const getColumnName = (column: Column | string): string =>
  typeof column === 'object' ? column.key : column

export const getColumnNames = (
  columns: (string | Column)[] | undefined,
  items: Item[],
): string[] => {
  if (columns) {
    const _columns = []

    for (const column of columns) {
      if (typeof column === 'object' && column.children) {
        _columns.push(...getColumnNames(column.children, []))
        continue
      }

      typeof column === 'object' ? _columns.push(column.key) : _columns.push(column)
    }

    return _columns
  }

  return getColumnNamesFromItems(items)
}

export const getColumns = (_columns: (Column | Group | string)[]): (Column | string)[] => {
  const columns = []

  for (const column of _columns) {
    if (typeof column === 'object' && column.group && column.children) {
      columns.push(...getColumns(column.children))
      continue
    }

    if (typeof column === 'object' && column.children) {
      columns.push(...getColumns(column.children))
    }

    columns.push(column)
  }

  return columns
}

export const countColumns = (columns: Column[]): number => {
  let count = 0

  for (const column of columns) {
    if (!column.children || column.children.length === 0) {
      count++
    } else {
      count += countColumns(column.children)
    }
  }

  return count
}

export const getColumnGroups = (columns: (string | Column)[] | undefined): Group[][] => {
  const groups: Group[][] = []

  const traverseColumns = (column: Column, deep = 0, colSpan = 0): Group[] => {
    const groups = []

    if (column.children) {
      for (const _column of column.children) {
        if (!_column.group) {
          colSpan++
        }
        groups.push(...traverseColumns(_column, deep + 1, colSpan))
      }
    }

    if (typeof column === 'object' && column.group) {
      const { children, group, ...rest } = column
      groups.push({
        deep: deep,
        label: group,
        ...(children && { colspan: countColumns(children) }),
        ...rest,
      })
    }

    return groups
  }

  if (columns) {
    for (const column of columns) {
      if (typeof column === 'object' && column.group) {
        const objects = traverseColumns(column)

        if (objects) {
          for (const object of objects) {
            const { deep, ...rest } = object

            if (deep === undefined) {
              continue
            }

            for (let i = 0; i < deep; i++) {
              if (groups[i]) {
                continue
              }

              groups.push([])
            }

            if (groups[deep]) {
              groups[deep].push(rest)
            } else {
              groups.push([rest])
            }
          }
        }
      }
    }
  }
  return groups
}

export const getColumnNamesFromItems = (items: Item[]): string[] =>
  Object.keys(items[0] || {}).filter((el) => el.charAt(0) !== '_')

export const getColumnSorterFunction = (column: Column | undefined) => {
  if (
    column &&
    typeof column === 'object' &&
    column.sorter &&
    typeof column.sorter === 'function'
  ) {
    return column.sorter
  }

  return
}

export const getColumnSorterState = (key: string, sorterState?: SorterValue[]): string | number => {
  const state = sorterState && sorterState.find((el) => el.column === key)

  return state ? state.state : 0
}

export const getColumnValues = (items: Item[], key: string): any[] => items.map((item) => item[key])

export const getTableDataCellProps = (
  column: Column | string,
  item: Item,
  colName: string,
): Record<string, any> => {
  const props = {
    ...(typeof column === 'object' && column._colProps),
    ...item._cellProps?.all,
    ...item._cellProps?.[colName],
  }

  return props
}

export const getTableDataCellStyles = (
  column: Column | string,
  item: Item,
  colName: string,
): Record<string, any> => {
  const styles = {
    ...(typeof column === 'object' && column._colStyle),
    ...item._cellStyle?.all,
    ...item._cellStyle?.[colName],
  }

  return styles
}

export const getTableHeaderCellProps = (column: Column | string) =>
  typeof column === 'object' && column._props ? column._props : {}

export const getTableHeaderCellStyles = (
  column: Column | string,
  columnSorter: boolean | Sorter | undefined,
): Record<string, any> => {
  const style: Record<string, any> = {}

  const isSortable =
    columnSorter &&
    (typeof column !== 'object' ||
      (typeof column === 'object' && (column.sorter === undefined || column.sorter)))

  if (isSortable) {
    style.cursor = 'pointer'
  }

  if (typeof column === 'object' && column._style) {
    Object.assign(style, column._style)
  }

  return style
}

export const isSortable = (
  index: number,
  columns: (string | Column)[] | undefined,
  columnSorter: boolean | Sorter | undefined,
  itemsDataColumns: string[],
  columnNames: string[],
): boolean => {
  const key = columnNames[index]
  const isDataColumn = itemsDataColumns.includes(key)
  const column = columns ? columns[index] : undefined

  return (
    !!columnSorter &&
    (!columns ||
      typeof column !== 'object' ||
      (typeof column === 'object' && (column.sorter === undefined || column.sorter))) &&
    isDataColumn
  )
}

export const pretifyName = (name: string): string => {
  return name
    .replace(/[-_.]/g, ' ')
    .replace(/ +/g, ' ')
    .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
    .split(' ')
    .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
    .join(' ')
}

export const sortBy = (
  column: Column | undefined,
  items: Item[],
  key: string,
  order: SorterValue['state'],
): Item[] => {
  const sorterFunction = getColumnSorterFunction(column)

  const sortedItems = [...items].sort(
    sorterFunction ||
      ((a, b) => {
        const valA = a[key]
        const valB = b[key]
        const aValue = typeof valA === 'number' ? valA : String(valA).toLowerCase()
        const bValue = typeof valB === 'number' ? valB : String(valB).toLowerCase()
        return aValue > bValue ? 1 : bValue > aValue ? -1 : 0
      }),
  )

  return order === 'desc' ? sortedItems.reverse() : sortedItems
}

export const sortItems = (
  columns: (Column | string)[] | undefined,
  columnSorter: boolean | Sorter | undefined,
  items: Item[],
  itemsDataColumns: string[],
  sorterState: SorterValue[],
): Item[] => {
  if (
    (columnSorter && typeof columnSorter === 'object' && columnSorter.external) ||
    sorterState.length === 0
  ) {
    return items
  }

  let sortedItems = [...items]

  for (const sorter of [...sorterState].reverse()) {
    const key = sorter.column
    const order = sorter.state
    const column =
      columns &&
      (columns.find((column) => typeof column === 'object' && column.key === key) as
        | Column
        | undefined)

    if (!key || !itemsDataColumns.includes(key)) {
      continue
    }

    sortedItems = sortBy(column, sortedItems, key, order)
  }

  return sortedItems
}
