import React from 'react'
import { render } from '../utils/test-utils'
import type { GridCellFocusHealProps } from './grid-cell-focus-heal'
import { GridCellFocusHeal } from './grid-cell-focus-heal'
import { GridCellDefault } from '../components'
import type { CellParams } from '../types'
import userEvent from '@testing-library/user-event'

const ParentWrapper = (props: Omit<GridCellFocusHealProps, 'parentRef'>) => {
    const ref = React.useRef<HTMLDivElement>(null)

    return (
        <div ref={ref}>
            <GridCellFocusHeal {...props} parentRef={ref} />
        </div>
    )
}

const generateCellRenderer = () =>
    function Renderer(props: CellParams<any>) {
        return <GridCellDefault {...props} />
    }

const cellRendererProps: CellParams<any> = {
    columnId: 'name',
    rowId: '1',
    label: 'Value',
    tabIndex: 0,
    value: 'value',
}

const originalFocus = Object.getOwnPropertyDescriptor(
    global.HTMLElement.prototype,
    'focus'
)!

describe('GridCellFocusHeal', () => {
    let focusSpy: jest.SpyInstance

    beforeEach(() => {
        Object.defineProperty(
            global.HTMLElement.prototype,
            'focus',
            originalFocus
        )

        focusSpy = jest.spyOn(HTMLElement.prototype, 'focus')
    })

    it('should retain focus if focused before render', async () => {
        const { getByText, rerender } = render(
            <ParentWrapper
                hasFocus
                CellRenderer={generateCellRenderer()}
                cellRendererProps={cellRendererProps}
            />
        )

        await userEvent.keyboard('{Tab}')

        expect(getByText('Value')).toHaveFocus()

        focusSpy.mockClear()

        rerender(
            <ParentWrapper
                hasFocus
                CellRenderer={generateCellRenderer()}
                cellRendererProps={cellRendererProps}
            />
        )

        expect(getByText('Value')).toHaveFocus()
        expect(focusSpy).toHaveBeenCalledTimes(1)
        expect(focusSpy).toHaveBeenCalledWith({ preventScroll: true })
    })

    it('should not steal focus if not focused before render even if hasFocus is true', async () => {
        const { getByText, rerender } = render(
            <ParentWrapper
                hasFocus
                CellRenderer={generateCellRenderer()}
                cellRendererProps={cellRendererProps}
            />
        )

        expect(getByText('Value')).not.toHaveFocus()

        rerender(
            <ParentWrapper
                hasFocus
                CellRenderer={generateCellRenderer()}
                cellRendererProps={cellRendererProps}
            />
        )

        expect(getByText('Value')).not.toHaveFocus()
        expect(focusSpy).not.toHaveBeenCalled()
    })

    it('should not steal focus if focused before render but hasFocus is false', async () => {
        const { getByText, rerender } = render(
            <ParentWrapper
                hasFocus={false}
                CellRenderer={generateCellRenderer()}
                cellRendererProps={cellRendererProps}
            />
        )

        await userEvent.keyboard('{Tab}')

        expect(getByText('Value')).toHaveFocus()

        focusSpy.mockClear()

        rerender(
            <ParentWrapper
                hasFocus={false}
                CellRenderer={generateCellRenderer()}
                cellRendererProps={cellRendererProps}
            />
        )

        expect(getByText('Value')).not.toHaveFocus()
        expect(focusSpy).not.toHaveBeenCalled()
    })
})
