import React, { ElementType, forwardRef, ReactNode, useMemo, useRef } from 'react'
import PropTypes from 'prop-types'

import { CFormCheck, CFormInput } from '../form'
import { CTableHeaderCell, CTableRow } from '../table'
import { CTableHead, CTableHeadProps } from '../table/CTableHead'

import { PolymorphicRefForwardingComponent } from '../../helpers'
import type { ColumnFilter, ColumnFilterValue, Column, Item, Sorter, SorterValue } from './types'
import {
  getColumnKey,
  getColumnLabel,
  getColumnGroups,
  getColumns,
  getColumnSorterState,
  getColumnValues,
  getTableHeaderCellProps,
  getTableHeaderCellStyles,
} from './utils'

export interface CSmartTableHeadProps extends CTableHeadProps {
  as?: ElementType
  columnFilter?: boolean | ColumnFilter
  columnFilterState?: ColumnFilterValue
  columnSorter?: boolean | Sorter
  columns: (Column | string)[]
  handleOnCustomFilterChange?: (key: string, value: any) => void
  handleFilterOnChange?: (key: string, value: string) => void
  handleFilterOnInput?: (key: string, value: string) => void
  handleSelectAllChecked?: () => void
  handleSort?: (key: string, index: number, order?: 'asc' | 'desc') => void
  items: Item[]
  selectable?: boolean
  selectAll?: boolean | { external?: boolean }
  selectedAll?: boolean | string
  showGroups?: boolean
  sorterState?: SorterValue[]
  sortingIcon?: ReactNode
  sortingIconAscending?: ReactNode
  sortingIconDescending?: ReactNode
}

export const CSmartTableHead: PolymorphicRefForwardingComponent<'thead', CSmartTableHeadProps> =
  forwardRef<HTMLTableSectionElement, CSmartTableHeadProps>(
    (
      {
        as: Component = CTableHead,
        columnFilter,
        columnFilterState,
        columnSorter,
        columns,
        handleOnCustomFilterChange,
        handleFilterOnChange,
        handleFilterOnInput,
        handleSelectAllChecked,
        handleSort,
        items,
        selectable,
        selectAll,
        selectedAll,
        showGroups = true,
        sorterState,
        sortingIcon,
        sortingIconAscending,
        sortingIconDescending,
        ...rest
      },
      ref,
    ) => {
      const selectAllcheckboxRef = useRef<HTMLInputElement>(null)
      const _columns = useMemo(() => getColumns(columns), [columns])
      const groups = useMemo(() => getColumnGroups(columns), [columns])

      const columnSorterIcon = (column: Column | string) => {
        if (getColumnSorterState(getColumnKey(column), sorterState) === 0) {
          return <span className="opacity-25 float-end me-1">{sortingIcon}</span>
        }

        if (getColumnSorterState(getColumnKey(column), sorterState) === 'asc') {
          return <span className="float-end me-1">{sortingIconAscending}</span>
        }

        if (getColumnSorterState(getColumnKey(column), sorterState) === 'desc') {
          return <span className="float-end me-1">{sortingIconDescending}</span>
        }

        return
      }

      return (
        <Component {...rest} ref={ref}>
          {showGroups &&
            groups &&
            groups.length > 0 &&
            groups.map((row, index) => (
              <CTableRow key={index}>
                {selectable && <CTableHeaderCell></CTableHeaderCell>}
                {row.map((cell, index) => (
                  <CTableHeaderCell
                    colSpan={cell.colspan}
                    {...getTableHeaderCellProps(cell)}
                    key={index}
                  >
                    {cell.label}
                  </CTableHeaderCell>
                ))}
              </CTableRow>
            ))}
          <CTableRow>
            {selectable && (
              <CTableHeaderCell>
                {selectAll && (
                  <CFormCheck
                    checked={typeof selectedAll === 'boolean' ? selectedAll : false}
                    indeterminate={selectedAll === 'indeterminate' ? true : false}
                    onChange={() => handleSelectAllChecked && handleSelectAllChecked()}
                    ref={selectAllcheckboxRef}
                  />
                )}
              </CTableHeaderCell>
            )}
            {_columns.map((column: Column | string, index: number) => {
              const isSortable =
                columnSorter &&
                (typeof column === 'object'
                  ? column.sorter === undefined
                    ? true
                    : column.sorter
                  : true)
              return (
                <CTableHeaderCell
                  {...getTableHeaderCellProps(column)}
                  style={getTableHeaderCellStyles(column, columnSorter)}
                  key={index}
                  {...(isSortable && {
                    onClick: () => handleSort && handleSort(getColumnKey(column), index),
                    onKeyDown: (event) => {
                      if (event.key === 'Enter' && handleSort) {
                        handleSort(getColumnKey(column), index)
                      }

                      if (event.key === 'ArrowUp' && handleSort) {
                        event.preventDefault()
                        handleSort(getColumnKey(column), index, 'asc')
                      }

                      if (event.key === 'ArrowDown' && handleSort) {
                        event.preventDefault()
                        handleSort(getColumnKey(column), index, 'desc')
                      }
                    },
                    tabIndex: 0,
                  })}
                >
                  <div className="d-inline">{getColumnLabel(column)}</div>
                  {isSortable && columnSorterIcon(column)}
                </CTableHeaderCell>
              )
            })}
          </CTableRow>
          {columnFilter && (
            <CTableRow>
              {selectable && <CTableHeaderCell></CTableHeaderCell>}
              {_columns.map((column: Column | string, index: number) => {
                return (
                  <CTableHeaderCell {...getTableHeaderCellProps(column)} key={index}>
                    {(
                      typeof column === 'object'
                        ? column.filter === undefined
                          ? true
                          : column.filter
                        : true
                    ) ? (
                      typeof column !== 'string' && typeof column.filter === 'function' ? (
                        column.filter(
                          getColumnValues(items, getColumnKey(column)),
                          (value: any) =>
                            handleOnCustomFilterChange &&
                            handleOnCustomFilterChange(getColumnKey(column), value),
                          columnFilterState && columnFilterState[getColumnKey(column)]
                            ? columnFilterState[getColumnKey(column)]
                            : undefined
                        )
                      ) : (
                        <CFormInput
                          size="sm"
                          onInput={(event) =>
                            handleFilterOnInput &&
                            handleFilterOnInput(
                              getColumnKey(column),
                              (event.target as HTMLInputElement).value,
                            )
                          }
                          onChange={(event) =>
                            handleFilterOnChange &&
                            handleFilterOnChange(
                              getColumnKey(column),
                              (event.target as HTMLInputElement).value,
                            )
                          }
                          value={
                            columnFilterState && columnFilterState[getColumnKey(column)]
                              ? columnFilterState[getColumnKey(column)]
                              : ''
                          }
                          aria-label={`column name: '${getColumnLabel(column)}' filter input`}
                        />
                      )
                    ) : (
                      ''
                    )}
                  </CTableHeaderCell>
                )
              })}
            </CTableRow>
          )}
        </Component>
      )
    },
  )

CSmartTableHead.propTypes = {
  as: PropTypes.elementType,
  columnFilter: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
  columnFilterState: PropTypes.object,
  columnSorter: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
  children: PropTypes.node,
  columns: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.any, PropTypes.string])).isRequired, // TODO: improve this Prop Type,
  handleOnCustomFilterChange: PropTypes.func,
  handleFilterOnChange: PropTypes.func,
  handleFilterOnInput: PropTypes.func,
  handleSelectAllChecked: PropTypes.func,
  handleSort: PropTypes.func,
  items: PropTypes.array,
  selectable: PropTypes.bool,
  selectAll: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
  selectedAll: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
  showGroups: PropTypes.bool,
  sorterState: PropTypes.array,
  sortingIcon: PropTypes.node,
  sortingIconAscending: PropTypes.node,
  sortingIconDescending: PropTypes.node,
}

CSmartTableHead.displayName = 'CSmartTableHead'
