import React, { useCallback, useEffect, useMemo, useState } from 'react'
import {
  type ColumnDef,
  type ColumnDefBase,
  flexRender,
  getCoreRowModel,
  getFilteredRowModel,
  useReactTable,
} from '@tanstack/react-table'
import { Cell, Row, StickyTable } from 'react-sticky-table'
import { notUndefined, useVirtualizer } from '@tanstack/react-virtual'

import { type SpreadsheetProps } from './spreadsheet-types-new'
import SpreadsheetCell from './SpreadsheetCell/SpreadsheetCellNew'
import EmptyState from '../../EmptyState/EmptyState'
import { hasValue } from '../../../services/HelperServiceTyped'
import styles from './_spreadsheet.module.scss'
import { Skeleton } from '../../Skeleton/Skeleton'
import ColumnHeaderNew, {
  type ColumnHeaderBaseHeader,
  type ColumnHeaderBasesubHeader,
} from './ColumnHeader/ColumnHeaderNew'
import DeleteCell from './SpreadsheetCell/DeleteCell'
import { c } from '../../../translations/LibraryTranslationService'

interface CustomColumn {
  /** The id of the column */
  id?: string
  /** The meta data of the column */
  meta?: {
    /** Whether to enable the delete cell */
    enableDelete?: boolean
    /** Whether the column is the primary column */
    isPrimaryColumn?: boolean
  }
}

interface ExtendedTData {
  /** The data of the row */
  [key: string]: Record<string, unknown>
}

interface ExtendedHeader extends ColumnDefBase<ExtendedTData, unknown> {
  /** Whether the header is disabled */
  disabled?: boolean
  /** The error count of the header */
  errorCount?: number
  /** The header of the column */
  header?: ColumnHeaderBaseHeader
  /** The sub header of the column */
  subHeader?: ColumnHeaderBasesubHeader
  /** Whether to show the count */
  showCount?: boolean
  /** The children of the header */
  headerChildren?: React.ReactNode
}

const Spreadsheet = <TData extends object & ExtendedTData>({
  data,
  onRowDelete,
  columns,
  loading,
  noDataFields,
  stickyTableConfig,
  isReviewMode,
  isLastRowDeleted,
  hasMore,
  inputRules,
  customHeight,
  customWidth,
  selectedColumns,
  searchTerm,
  onStickyTableClassChange,
  noOutsideBorder,
  rowHeight = 75,
  overscan,
  noVirtualization = false,
  onRowClick,
  onHeaderCellClick,
  headerCancelCallout,
  qaTestId = 'spreadsheet',
  columnOrder,
}: SpreadsheetProps<TData>) => {
  const [displayColumns, setDisplayColumns] = useState(columns)
  const [columnVisibility, setColumnVisibility] = useState({})
  const [globalFilter, setGlobalFilter] = React.useState('')

  useEffect(() => {
    if (columns) {
      setDisplayColumns(columns)
    }
  }, [columns])

  useEffect(() => {
    if (hasValue(searchTerm)) {
      setGlobalFilter(String(searchTerm))
    } else {
      setGlobalFilter('')
    }
  }, [searchTerm])

  const memoizedColumns = useMemo(() => {
    return displayColumns.map(
      (columnCell: CustomColumn & ColumnDef<TData, unknown>) => {
        return {
          enableSorting: false,
          ...columnCell,
          ...(columnCell.cell
            ? { cell: columnCell.cell }
            : {
                cell: columnCell?.meta?.enableDelete
                  ? DeleteCell
                  : SpreadsheetCell,
              }),
        }
      },
    )
  }, [displayColumns])

  const table = useReactTable({
    data: data,
    columns: memoizedColumns as ColumnDef<TData, unknown>[],
    getCoreRowModel: getCoreRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    state: {
      columnVisibility,
      globalFilter,
      ...(columnOrder && { columnOrder }),
    },
    onColumnVisibilityChange: setColumnVisibility,
    onGlobalFilterChange: setGlobalFilter,
    meta: {
      inputRules,
      isReviewMode,
      isLastRowDeleted,

      removeRow: (rowIndex: number) => {
        const deletedRow = data[rowIndex]
        if (onRowDelete) {
          onRowDelete(deletedRow, rowIndex)
        }
      },
    },
  })

  useEffect(() => {
    if (selectedColumns) {
      const allTableColumns = table.getAllLeafColumns().map((cell) => cell.id)
      const updatedDisplayColumns = columns.map((cell) => ({ ...cell }))

      setColumnVisibility((prevColumnVisibility) => {
        const updatedVisibility: { [key: string]: boolean } = {
          ...prevColumnVisibility,
        }

        allTableColumns.forEach((columnId: string) => {
          if (selectedColumns?.includes(columnId)) {
            updatedVisibility[columnId] = true
            // Enable column filter only for the selected column
            const columnIndex = updatedDisplayColumns.findIndex(
              (col) => col.id === columnId,
            )
            if (columnIndex !== -1) {
              updatedDisplayColumns[columnIndex].enableGlobalFilter = true
            }
          } else {
            updatedVisibility[columnId] = false
            // Disable column filter for all other columns
            const columnIndex = updatedDisplayColumns.findIndex(
              (col) => col.id === columnId,
            )
            if (columnIndex !== -1) {
              updatedDisplayColumns[columnIndex].enableGlobalFilter = false
            }
          }
        })
        return updatedVisibility
      })
      setDisplayColumns(updatedDisplayColumns)
    } else {
      setColumnVisibility({})
    }
  }, [columns, selectedColumns, table])

  const tableContainerRef = React.useRef<HTMLDivElement>(null)
  const { getHeaderGroups, getRowModel } = table

  const { rows } = table.getRowModel()
  const virtualizer = useVirtualizer({
    count: rows.length,
    getScrollElement: () => tableContainerRef.current,
    estimateSize: () => rowHeight,
    overscan: overscan ?? 5,
  })

  // Function to adjust the height of each cell in a row to match the height of the tallest cell in that row
  const updateRowHeight = useCallback(
    (rowEl: HTMLElement | null) => {
      if (!rowEl) return

      const cells = rowEl?.getElementsByClassName('sticky-table-cell')
      if (cells?.length === 0) return

      // Use requestAnimationFrame for smooth updates
      requestAnimationFrame(() => {
        let maxHeight = 0
        const cellArray = Array.from(cells)

        // First pass: Reset all heights to auto
        cellArray?.forEach((cell: Element) => {
          const cellElement = cell as HTMLElement
          // Reset both the cell and its immediate children's heights
          cellElement.style.height = 'auto'
          Array.from(cellElement?.children ?? [])?.forEach((child) => {
            ;(child as HTMLElement).style.height = 'auto'
          })
        })

        // Force a reflow to ensure heights are recalculated
        void rowEl.offsetHeight

        // Second pass: Calculate max height
        cellArray?.forEach((cell: Element) => {
          const cellElement = cell as HTMLElement
          // Get the scrollHeight which includes all content
          const height = Math.max(
            cellElement?.scrollHeight ?? 0,
            ...Array.from(cellElement?.children ?? []).map(
              (child) => (child as HTMLElement).scrollHeight,
            ),
          )
          maxHeight = Math.max(maxHeight, height)
        })

        // Third pass: Apply the new height
        cellArray?.forEach((cell: Element) => {
          const cellElement = cell as HTMLElement
          cellElement.style.height = `${maxHeight}px`
          // Set children back to 100% height
          Array.from(cellElement?.children ?? []).forEach((child) => {
            ;(child as HTMLElement).style.height = '100%'
          })
        })
      })

      virtualizer?.measureElement(rowEl)
    },
    [virtualizer],
  )

  const rowVirtualizer = virtualizer.getVirtualItems()

  const [before, after] =
    rowVirtualizer.length > 0
      ? [
          notUndefined(rowVirtualizer[0]).start -
            virtualizer.options.scrollMargin,
          virtualizer.getTotalSize() -
            notUndefined(rowVirtualizer[rowVirtualizer.length - 1]).end,
        ]
      : [0, 0]

  const findColumnsWithFixedWidths = () => {
    let hasDelete = false
    let rowCountColumn = null
    let productNameColumn = null

    for (let i = 0; i < displayColumns.length; i++) {
      const column: CustomColumn & ColumnDef<TData, unknown> = displayColumns[i]
      if (column?.meta?.enableDelete === true) {
        hasDelete = true
      }

      if (column.id === 'row') {
        rowCountColumn = i
      }

      if (column?.meta?.isPrimaryColumn) {
        productNameColumn = i
      }
    }

    return { hasDelete, rowCountColumn, productNameColumn }
  }

  const { hasDelete, rowCountColumn, productNameColumn } =
    findColumnsWithFixedWidths()

  const equalWidthAmount = useMemo(
    () =>
      Number(customWidth) /
      ((selectedColumns?.length ?? columns.length) -
        (hasDelete ? 1 : 0) -
        (rowCountColumn ? 1 : 0) -
        (productNameColumn ? 1 : 0)),
    [
      customWidth,
      selectedColumns?.length,
      columns.length,
      hasDelete,
      rowCountColumn,
      productNameColumn,
    ],
  )

  const renderHeader = () =>
    getHeaderGroups().map((headerGroup) => {
      return (
        <Row key={headerGroup.id}>
          {headerGroup.headers.map((header, i) => {
            const rowCount = headerGroup.headers?.length
            const lastLeftStickyColumn = stickyTableConfig?.left
              ? stickyTableConfig?.left - 1
              : 0
            const firstRightStickyColumn =
              rowCount &&
              !!stickyTableConfig?.right &&
              rowCount - stickyTableConfig?.right

            let columnWidth = equalWidthAmount
            // if size is defined in header column definition, use that
            if (header?.column?.columnDef?.size) {
              columnWidth = header.column.columnDef.size
            } else if (hasDelete && i === 0) {
              columnWidth = 50
            } else if (rowCountColumn && i === rowCountColumn) {
              columnWidth = 100
            } else if (productNameColumn && i === productNameColumn) {
              columnWidth = 300
            }

            const headerNew = header.column
              .columnDef as unknown as ExtendedHeader
            return (
              <ColumnHeaderNew
                headerText={c('bulkUpdates')}
                key={header.id}
                columnWidth={columnWidth}
                disabled={headerNew?.disabled}
                containerClassName={`${
                  lastLeftStickyColumn === i ? styles.lastStickyCellLeft : ''
                } ${
                  firstRightStickyColumn === i
                    ? styles.firstStickyCellRight
                    : ''
                } ${showLeftShadow ? styles.showLastStickyCellLeft : ''} ${
                  showRightShadow ? styles.showFirstStickyCellRight : ''
                }
              `}
                header={headerNew.header}
                subHeader={headerNew?.subHeader}
                onHeaderCellClick={() =>
                  onHeaderCellClick &&
                  onHeaderCellClick({
                    header: headerNew,
                  } as unknown as ColumnDef<TData>)
                }
                headerChildren={headerNew?.headerChildren}
                headerCancelCallout={headerCancelCallout}
                {...(headerNew?.showCount &&
                typeof headerNew.errorCount === 'number'
                  ? { showCount: true, errorCount: headerNew.errorCount }
                  : { showCount: undefined, errorCount: undefined })}
              />
            )
          })}
        </Row>
      )
    })

  const noDataToDisplay = !loading && getRowModel().rows.length === 0

  const renderTableBody = () =>
    !noDataToDisplay
      ? rowVirtualizer.map((virtualRow) => {
          const row = rows[virtualRow.index]
          const rowCount = row.getVisibleCells()?.length
          return (
            <Row
              key={`row.id-${virtualRow.index}`}
              data-index={virtualRow.index}
              ref={(rowEl: HTMLElement | null) => {
                if (
                  typeof noVirtualization === 'object' &&
                  noVirtualization?.dynamicRowHeight &&
                  rowEl
                ) {
                  // Initial height calculation
                  updateRowHeight(rowEl)
                }
              }}
            >
              {row.getVisibleCells().map((cell, i) => {
                const columnId = cell.column.id
                const lastLeftStickyColumn = stickyTableConfig?.left
                  ? stickyTableConfig?.left - 1
                  : 0
                const firstRightStickyColumn =
                  rowCount &&
                  !!stickyTableConfig?.right &&
                  rowCount - stickyTableConfig?.right
                const cellColor = row.original?.[columnId]?.color ?? ''

                const lightCellColor = () => {
                  switch (cellColor) {
                    case 'dark-red':
                      return '--light-red'
                    case 'dark-blue':
                      return '--light-blue'
                    case 'dark-green':
                      return '--light-green'
                    case 'dark-yellow':
                      return '--light-yellow'
                    case 'dark-purple':
                      return '--light-purple'
                    case 'medium-purple':
                      return '--light-purple'
                    case 'purple':
                      return '--light-purple'
                    case 'teal':
                      return '--light-teal'
                    case 'gray':
                      return '--light-gray'
                    default:
                      return ''
                  }
                }

                const onClickButton = () => {
                  onRowClick?.({ data: row.original, selectedCell: columnId })
                }

                return (
                  <Cell
                    key={cell.id}
                    style={{
                      backgroundColor: isReviewMode
                        ? ''
                        : `${cellColor ? `var(${lightCellColor()})` : ''}`,
                    }}
                    className={`${
                      lastLeftStickyColumn === i
                        ? styles.lastStickyCellLeft
                        : ''
                    } ${
                      firstRightStickyColumn === i
                        ? styles.firstStickyCellRight
                        : ''
                    } ${showLeftShadow ? styles.showLastStickyCellLeft : ''} ${
                      showRightShadow ? styles.showFirstStickyCellRight : ''
                    } ${virtualRow.index % 2 ? styles.evenRow : ''} ${row.original.rowClassName ?? ''}`}
                    onClick={() => onClickButton()}
                  >
                    {flexRender(cell.column.columnDef.cell, cell.getContext())}
                  </Cell>
                )
              })}
            </Row>
          )
        })
      : null

  const LoadingRow = () => (
    <Row className={styles.borderBottom}>
      {columns.map((_, i) => (
        <Cell key={i} className={styles.skeleton}>
          <Skeleton />
        </Cell>
      ))}
    </Row>
  )

  const renderTableLoading = () => {
    return Array.from(Array(5)).map((_, index) => {
      return <LoadingRow key={`loading-row-${index}`} />
    })
  }

  //// Sticky Table logic ////
  const [showStickyClasses, setShowStickyClasses] = useState(false)
  const [innerTableWidth, setInnerTableWidth] = useState(0)
  const tableWidth = tableContainerRef.current?.clientWidth ?? 0
  const [showLeftShadow, setShowLeftShadow] = useState(false)
  const [showRightShadow, setShowRightShadow] = useState(true)

  useEffect(() => {
    setInnerTableWidth(
      tableContainerRef.current?.querySelector('.sticky-table-table')
        ?.clientWidth ?? 0,
    )
  }, [columnVisibility])

  const isShowStickyClasses = useMemo(
    () => innerTableWidth > tableWidth,
    [innerTableWidth, tableWidth],
  )

  useEffect(() => {
    setShowStickyClasses(isShowStickyClasses)
    onStickyTableClassChange?.(isShowStickyClasses && showRightShadow)
  }, [
    isShowStickyClasses,
    onStickyTableClassChange,
    showLeftShadow,
    showRightShadow,
  ])

  useEffect(() => {
    const ref = tableContainerRef.current
    const handleScroll = () => {
      const isScrolledLeft = (ref?.scrollLeft ?? 0) > 0
      const isNotScrolledRight =
        (ref?.scrollLeft ?? 0) <
        (ref?.scrollWidth ?? 0) - (ref?.clientWidth ?? 0)
      // Show/hide shadows based on conditions
      setShowLeftShadow(isScrolledLeft)
      setShowRightShadow(isNotScrolledRight)
      onStickyTableClassChange?.(isNotScrolledRight)
    }
    ref?.addEventListener('scroll', handleScroll)
    return () => {
      ref?.removeEventListener('scroll', handleScroll)
    }
  }, [columnVisibility, onStickyTableClassChange])
  //// End Sticky Table logic ////

  return (
    <div
      className={`${styles.tableContainer} ${
        showStickyClasses ? styles.stickyClassesSpreadsheet : ''
      } ${noOutsideBorder ? styles.noOutsideBorder : ''}`}
      style={{
        height: noDataToDisplay ? 'auto' : customHeight,
        width: customWidth,
      }}
      qa-test-id={qaTestId}
    >
      <StickyTable
        stickyHeaderCount={stickyTableConfig?.header ?? 1}
        leftStickyColumnCount={stickyTableConfig?.left ?? 0}
        rightStickyColumnCount={stickyTableConfig?.right ?? 0}
        borderWidth={0}
        wrapperRef={tableContainerRef}
        className={styles.stickyTable}
      >
        {renderHeader()}

        {loading ? (
          renderTableLoading()
        ) : (
          <>
            {before > 0 && (
              <Row>
                <Cell style={{ height: before }} />
              </Row>
            )}
            {renderTableBody()}
            {hasMore ? <LoadingRow /> : null}
            {after > 0 && (
              <Row>
                <Cell style={{ height: after }} />
              </Row>
            )}
          </>
        )}
      </StickyTable>
      {noDataToDisplay ? (
        <EmptyState
          {...{
            ...noDataFields,
          }}
        />
      ) : null}
    </div>
  )
}

export default Spreadsheet
