import React from 'react'
import { useGridComponents } from '../context/grid-components-context/hook'
import { useGridContext, useGridSelector } from '../context/grid-context/hook'
import { useCellStickySizing, useColumnIndex, useFocusHandler } from '../hooks'
import type {
    GridRowId,
    EditorConfirmPayload,
    GridConfirmPayload,
    GridRowMeta,
} from '../types'
import { useIsFocusVisible } from '@planview/pv-utilities'
import {
    SubNavigationContext,
    useSubFocusHandler,
} from '../context/grid-sub-navigation-context'
import type { GridEditorContextType } from '../context/grid-editor-context'
import { GridEditorContext } from '../context/grid-editor-context'
import { GridCellFocusHeal } from './grid-cell-focus-heal'
import { useColumnBorders } from '../hooks/use-column-borders'
import { ACTIONS_COLUMN_ID, SELECTION_COLUMN_ID } from '../constants'

export type GridCellProps = {
    rowId: GridRowId
    columnId: string
    renderColumnId?: string
    spanColumns?: string[]
    virtualStart?: number
}

const NO_SPAN: never[] = []

export const GridCell = ({
    rowId,
    columnId,
    renderColumnId,
    virtualStart,
    spanColumns = NO_SPAN,
}: GridCellProps) => {
    const { GridCellLayout, GridCellTree, GridCellFocusLayout } =
        useGridComponents()
    const grid = useGridContext()

    const { selectColumn, selectRow } = grid.selectors

    const actingColumnId = renderColumnId ?? columnId
    const { actualWidth: baseWidth, data: column } = useGridSelector((state) =>
        selectColumn(state, actingColumnId)
    )

    const extendedWidth = useGridSelector((state) => {
        if (spanColumns.length === 0) {
            return 0
        }
        return spanColumns.reduce((acc, colId) => {
            const col = selectColumn(state, colId)
            return acc + col.actualWidth
        }, 0)
    })

    const actualWidth = extendedWidth || baseWidth

    const columnIndex = useColumnIndex(columnId)
    const data = useGridSelector((state) => selectRow(state, rowId))
    const [hasFocus, handleGridFocus] = useFocusHandler(columnId, 'body', rowId)

    const spreadsheetMode = useGridSelector((state) =>
        grid.selectors.selectIsSpreadsheet(state)
    )
    const isInRange = useGridSelector((state) =>
        grid.selectors.selectIsInRangeSelection(state, columnId, rowId)
    )
    const rangeBorders = useGridSelector((state) =>
        grid.selectors.selectCellRangeBorders(state, columnId, rowId)
    )
    const isTreeColumn = !!column.tree
    const meta: GridRowMeta = useGridSelector((state) =>
        grid.selectors.selectRowMeta(state, rowId)
    )
    const level = useGridSelector((state) =>
        isTreeColumn ? grid.selectors.selectRowLevel(state, rowId) : 0
    )
    const isExpanded = useGridSelector((state) =>
        isTreeColumn ? grid.selectors.selectIsRowExpanded(state, rowId) : false
    )

    const treeIndentSize = useGridSelector(grid.selectors.selectTreeIndentSize)

    const { showLeftBorder, showRightBorder } = useColumnBorders(
        spanColumns && spanColumns.length >= 2
            ? [spanColumns[0], spanColumns[spanColumns.length - 1]]
            : columnId,
        'body'
    )

    const isExpandable = meta.type === 'group' || meta.type === 'tree'

    const {
        onKeyDown,
        value: subFocusValue,
        rendererWantsToDisplayFocusStyles,
        registeredIndexes,
        subFocus,
    } = useSubFocusHandler(
        columnId,
        'body',
        rowId,
        isTreeColumn && isExpandable ? 1 : 0
    )

    const CellComponent = column.cell?.Renderer
    const EditorComponent = column.cell?.Editor

    const elRef = React.useRef<HTMLTableCellElement>(null)
    const hasFocusRef = React.useRef(hasFocus)

    const { onFocus, ...focusProps } = useIsFocusVisible()

    const editable =
        typeof column.cell?.editable === 'function'
            ? column.cell.editable({
                  columnId: actingColumnId,
                  row: data,
                  rowMeta: meta,
              })
            : !!column.cell?.editable

    const inEditMode = useGridSelector((state) =>
        grid.selectors.selectIsEditingCell(state, {
            rowId,
            columnId: actingColumnId,
        })
    )

    const hybridEditor = column.cell?.hybridEditor
    const isEditing =
        (editable && (hybridEditor || inEditMode)) || hybridEditor === 'always'
    const gridSupportsEdit = useGridSelector(
        grid.selectors.selectGridSupportsEdit
    )
    const readOnly = gridSupportsEdit && !editable ? true : undefined

    const onClickHandler = React.useCallback<React.MouseEventHandler>(
        (e) => {
            if (editable && !hybridEditor && !isEditing) {
                e.stopPropagation()
                grid.api.edit.start({ columnId: actingColumnId, rowId })
            }
        },
        [
            editable,
            hybridEditor,
            isEditing,
            grid.api.edit,
            actingColumnId,
            rowId,
        ]
    )

    React.useEffect(() => {
        hasFocusRef.current = hasFocus
    }, [hasFocus])

    React.useLayoutEffect(
        () => () => {
            if (
                hasFocusRef.current &&
                elRef.current?.contains(document.activeElement)
            ) {
                grid.events.emit('onFocusUnmount')
            }
        },
        [grid]
    )

    const { sticky, style } = useCellStickySizing({
        columnId,
        column,
        virtualStart,
        actualWidth,
    })

    const value = column.cell?.value
        ? column.cell.value({
              columnId: actingColumnId,
              row: data,
              rowMeta: meta,
          })
        : data?.[actingColumnId]

    const label = column.cell?.label
        ? column.cell?.label({
              columnId: actingColumnId,
              row: data,
              rowMeta: meta,
              value,
          })
        : value == null
          ? ''
          : value.toString()

    const handleKeyDown = React.useCallback<React.KeyboardEventHandler>(
        (e) => {
            if (e.key === 'Enter') {
                e.preventDefault()
                e.stopPropagation()
                grid.api.edit.start({ columnId: actingColumnId, rowId })
            }
        },
        [grid, actingColumnId, rowId]
    )

    const handleConfirm = React.useCallback(
        (val: unknown, settings: EditorConfirmPayload = {}) => {
            const confirm: GridConfirmPayload = {
                rowId,
                columnId: actingColumnId,
                previousValue: value,
                nextValue: val,
            }

            if (!settings.continueEditing) {
                grid.api.edit.stop()
            }

            /* Don't alert change events if value strict equals the same */
            if (val !== value) {
                grid.events.emit('onCellChange', confirm)
            }

            if (settings.continueEditing === 'previous') {
                grid.api.edit.previous()
            } else if (settings.continueEditing === 'next') {
                grid.api.edit.next()
            }
        },
        [grid, actingColumnId, rowId, value]
    )

    const handleCancel = React.useCallback(() => {
        grid.api.edit.stop()
    }, [grid])

    const handleFocus = React.useCallback<React.FocusEventHandler<HTMLElement>>(
        (e) => {
            handleGridFocus()
            onFocus(e)
            if (e.target === elRef.current) {
                const target =
                    elRef.current.querySelector<HTMLElement>("[tabIndex='0']")
                if (target) {
                    target.focus()
                }
            }
        },
        [handleGridFocus, onFocus]
    )

    const layoutKeyboardHandler: React.KeyboardEventHandler<HTMLElement> =
        React.useCallback(
            (e) => {
                onKeyDown(e)

                if (editable && !isEditing) {
                    handleKeyDown(e)
                }
            },
            [editable, handleKeyDown, isEditing, onKeyDown]
        )

    const functionalTabIndex =
        isTreeColumn &&
        (meta.type === 'group' || meta.type === 'tree') &&
        hasFocus &&
        !isEditing &&
        (subFocus === 'first' || subFocus === 0)
            ? 0
            : -1

    /** This means the renderer OR child of renderer has focus */
    const rendererHasFocus = hasFocus && functionalTabIndex === -1
    /** This is the tabIndex passed to the renderer, we dot use it if sub-navigation is enabled */
    const rendererTabIndex = rendererHasFocus && !registeredIndexes ? 0 : -1

    const editorContextValue = React.useMemo<GridEditorContextType>(
        () => ({ editable }),
        [editable]
    )

    const spreadsheetModeFocus =
        spreadsheetMode &&
        columnId !== SELECTION_COLUMN_ID &&
        columnId !== ACTIONS_COLUMN_ID

    const showRangeSelection = spreadsheetMode && isInRange && !inEditMode

    const content = (
        <GridCellFocusLayout
            $focusVisible={
                rendererHasFocus &&
                (focusProps.focusVisible || spreadsheetModeFocus) &&
                !inEditMode &&
                !rendererWantsToDisplayFocusStyles
            }
            $editable={editable}
            $editing={isEditing}
            onClick={() => {
                if (isTreeColumn && isExpandable) {
                    if (subFocus === 0 || subFocus === 'first') {
                        subFocusValue.setCurrentSubFocusIndex(1)
                    }
                }
            }}
            $leftBorder={showLeftBorder}
            $rightBorder={showRightBorder}
        >
            {isEditing && EditorComponent ? (
                <GridEditorContext.Provider value={editorContextValue}>
                    <EditorComponent
                        tabIndex={hasFocus ? 0 : -1}
                        mode={inEditMode ? 'editing' : 'navigation'}
                        rowId={rowId}
                        columnId={columnId}
                        label={label}
                        value={value}
                        onCancel={handleCancel}
                        onConfirm={handleConfirm}
                    />
                </GridEditorContext.Provider>
            ) : CellComponent ? (
                <SubNavigationContext.Provider value={subFocusValue}>
                    <GridCellFocusHeal
                        parentRef={elRef}
                        hasFocus={hasFocus}
                        CellRenderer={CellComponent}
                        cellRendererProps={{
                            tabIndex: rendererTabIndex,
                            rowId: rowId,
                            columnId: columnId,
                            label: label,
                            value: value,
                        }}
                    />
                </SubNavigationContext.Provider>
            ) : (
                label
            )}
        </GridCellFocusLayout>
    )

    return (
        <GridCellLayout
            ref={elRef}
            colSpan={spanColumns.length > 1 ? spanColumns.length : undefined}
            aria-colindex={columnIndex + 1}
            role="gridcell"
            aria-selected={spreadsheetMode ? isInRange : undefined}
            style={style}
            $sticky={!!sticky}
            onClick={
                isTreeColumn || spreadsheetMode ? undefined : onClickHandler
            }
            onDoubleClick={
                isTreeColumn || !spreadsheetMode ? undefined : onClickHandler
            }
            onPointerDown={handleGridFocus}
            onKeyDown={layoutKeyboardHandler}
            onFocus={handleFocus}
            onBlur={focusProps.onBlur}
            aria-readonly={readOnly}
            tabIndex={-1}
            data-current-cell={hasFocus ? 'true' : undefined}
            $inRange={showRangeSelection}
            $inRangeBorders={showRangeSelection ? rangeBorders : 0}
            $editable={editable}
        >
            {isTreeColumn ? (
                <GridCellTree
                    treeIndentSize={treeIndentSize}
                    expandable={isExpandable}
                    expanded={isExpanded}
                    level={level}
                    tabIndex={functionalTabIndex}
                    onContentClick={onClickHandler}
                    requireDoubleClick={spreadsheetMode}
                    onExpandClick={(e) => {
                        if (subFocus && subFocus !== 'first') {
                            subFocusValue.setCurrentSubFocusIndex(0)
                        }
                        grid.api.tree.expand(
                            rowId,
                            !isExpanded,
                            e.metaKey || e.ctrlKey
                        )
                    }}
                >
                    {content}
                </GridCellTree>
            ) : (
                content
            )}
        </GridCellLayout>
    )
}

export const GridCellMemo = React.memo(GridCell) as typeof GridCell
