import React, { useCallback, useEffect, useMemo } from 'react'
import { getStyleWidthValue } from './utils'
import { type CellStyle } from './TableTypes'
import { MIN_HEADER_HEIGHT } from './styles'

// tableObjectId => rowId => height
type RowHeightsMap = Record<string, Record<string, number>>

// tableObjectId => width
type FixedColumnWidthsMap = Record<string, number>

type TableContextState = {
  hoveringRowKey: string | null
  rowHeights: RowHeightsMap
  fixedColumnWidths: FixedColumnWidthsMap
}

type TableContextValue = {
  tableState: TableContextState
  setTableState: React.Dispatch<React.SetStateAction<TableContextState>>
}

const defaultTableContextState: TableContextState = {
  hoveringRowKey: null,
  rowHeights: {},
  fixedColumnWidths: {},
}

const defaultTableContextValue = {
  tableState: defaultTableContextState,
  setTableState: () => {},
}

const TableContext = React.createContext<TableContextValue>(
  defaultTableContextValue
)

const useTableContext = () => React.useContext(TableContext)

export const TableContextProvider: React.FC = props => {
  const { children } = props
  const [tableState, setTableState] = React.useState<TableContextState>(
    defaultTableContextState
  )

  const tableContextValue: TableContextValue = { tableState, setTableState }

  return (
    <TableContext.Provider value={tableContextValue}>
      {children}
    </TableContext.Provider>
  )
}

const getTableRowKey = (tableObjectId: string, rowId: string | number) =>
  `${tableObjectId}--${rowId}`

export const useContextRowHovering = (
  tableObjectId: string,
  rowId: string | number
) => {
  const { tableState, setTableState } = useTableContext()

  const hoveringRowKey = getTableRowKey(tableObjectId, rowId)
  const isHovering = tableState.hoveringRowKey === hoveringRowKey

  const setHovering = useCallback(
    () => setTableState(state => ({ ...state, hoveringRowKey })),
    [hoveringRowKey]
  )

  const clearHovering = useCallback(
    () => setTableState(state => ({ ...state, hoveringRowKey: null })),
    []
  )

  return { isHovering, setHovering, clearHovering }
}

export const useContextTableRowHeight = (
  tableObjectId: string,
  rowId: string | number,
  minHeight: number
) => {
  const { tableState, setTableState } = useTableContext()

  const rowHeight = tableState.rowHeights?.[tableObjectId]?.[rowId] ?? minHeight

  const setRowHeight = useCallback(
    (newHeight: number) => {
      setTableState(state => ({
        ...state,
        rowHeights: {
          ...state.rowHeights,
          [tableObjectId]: {
            ...state.rowHeights[tableObjectId],
            [rowId]: newHeight,
          },
        },
      }))
    },
    [tableObjectId, rowId]
  )

  const onRowHeightChanged = useCallback(
    (newHeight: number) => {
      if (newHeight > minHeight) {
        setRowHeight(newHeight)
      }
    },
    [tableObjectId, rowId, rowHeight]
  )

  const clearHeightFromContext = useCallback(() => {
    setTableState(state => {
      delete state.rowHeights?.[tableObjectId]?.[rowId]
      return state
    })
  }, [])

  // Clean up row height when component unmounts
  useEffect(() => () => clearHeightFromContext(), [tableObjectId, rowId])

  return { rowHeight, onRowHeightChanged }
}

const HEADER_ROW_ID = 'header'

export const useContextHeaderHeight = (tableObjectId: string) => {
  const { rowHeight, onRowHeightChanged } = useContextTableRowHeight(
    tableObjectId,
    HEADER_ROW_ID,
    MIN_HEADER_HEIGHT
  )

  return { headerHeight: rowHeight, onHeaderHeightChanged: onRowHeightChanged }
}

export const useContextFixedColumnWidthUpdate = (tableObjectId: string) => {
  const { tableState, setTableState } = useTableContext()

  const fixedColumnWidth = tableState.fixedColumnWidths?.[tableObjectId] ?? 0

  const setFixedColumnWidth = useCallback(
    (newWidth: number) => {
      setTableState(state => ({
        ...state,
        fixedColumnWidths: {
          ...state.fixedColumnWidths,
          [tableObjectId]: newWidth,
        },
      }))
    },
    [tableObjectId]
  )

  const clearFixedColumnWidth = useCallback(() => {
    setTableState(state => {
      delete state.fixedColumnWidths?.[tableObjectId]
      return state
    })
  }, [])

  // Clean up state on unmount
  useEffect(() => () => clearFixedColumnWidth(), [])

  return { fixedColumnWidth, setFixedColumnWidth }
}

export const useContextFixedColumnWidth = (tableObjectId: string) => {
  const { tableState } = useTableContext()

  const fixedColumnWidth = tableState.fixedColumnWidths?.[tableObjectId] ?? 0

  return fixedColumnWidth
}

export const useContextTableMinWidth = <T extends { style: CellStyle }>(
  tableObjectId: string,
  tableWidth: number,
  columns: T[]
) => {
  const fixedContentWidth = useContextFixedColumnWidth(tableObjectId)
  const tableMinWidthWithFixed: number = useMemo(
    () =>
      columns.reduce((acc, col, index) => {
        if (index === 0 && fixedContentWidth) {
          return acc
        }
        const width = getStyleWidthValue(col.style)

        return acc + width
      }, fixedContentWidth ?? 0),
    [columns, tableObjectId, fixedContentWidth]
  )

  return Math.max(tableWidth, tableMinWidthWithFixed)
}
