import { LeftOutlined, RightOutlined } from '@ant-design/icons'
import { Table, Button, Space, Row, Col } from 'antd'
import type { TableProps } from 'antd'
import React, { useEffect, useState } from 'react'

interface CursorPaginationProps {
  startPageToken?: string
  lastPageToken?: string
  onChange: (
    key: string,
    filters?: any,
    sorter?: any,
    extra?: { action: string }
  ) => void
  size?: 'small' | 'middle' | 'large' | undefined
}

interface CursorPaginationConfig {
  position?:
    | 'topLeft'
    | 'topCenter'
    | 'topRight'
    | 'bottomLeft'
    | 'bottomCenter'
    | 'bottomRight'
  size?: 'small' | 'middle' | 'large'
}

interface CursorTableProps<T extends object>
  extends Omit<TableProps<T>, 'pagination'> {
  pagination?: CursorPaginationConfig
  dataSource?: (T & { lastPageToken?: string })[] // Extend data source type to include lastPageToken
}

const CursorPagination: React.FC<CursorPaginationProps> = ({
  startPageToken,
  lastPageToken,
  onChange,
  size
}) => {
  /* Use stack to keep track of which evaluated keys have been previously seen */
  const [prevPageTokens, setPrevPageTokens] = useState<string[]>([])
  /* Keep track of the current evaluated key */
  const [currentPageToken, setCurrentPageToken] = useState<string>()
  /* Button style changes based on size */
  const [buttonStyle, setButtonStyle] = useState<React.CSSProperties>({})

  const popPageToken = (): string => {
    const newPrevKeys = [...prevPageTokens]
    const prevPageToken = newPrevKeys.pop()
    setPrevPageTokens(newPrevKeys)
    setCurrentPageToken(prevPageToken)
    return prevPageToken ?? ''
  }

  const pushPageToken = (newPageToken: string | undefined): string => {
    if (newPageToken === undefined) {
      return ''
    }

    setPrevPageTokens(
      currentPageToken !== undefined
        ? [...prevPageTokens, currentPageToken]
        : [...prevPageTokens]
    )
    setCurrentPageToken(newPageToken)
    return newPageToken
  }

  const extra = { action: 'paginate' }

  useEffect(() => {
    /* Reset stack of keys when pagination is reset back to the beginning */
    if (!startPageToken) {
      setPrevPageTokens([])
      setCurrentPageToken(undefined)
    }
  }, [startPageToken])

  /* Display forward and back buttons */
  return (
    <Space direction="horizontal">
      <Button
        tabIndex={-1}
        size={size}
        disabled={prevPageTokens.length === 0}
        style={buttonStyle}
        onClick={() => onChange(popPageToken(), undefined, undefined, extra)}
      >
        <LeftOutlined />
      </Button>
      <Button
        tabIndex={-1}
        size={size}
        disabled={!lastPageToken}
        style={buttonStyle}
        onClick={() =>
          onChange(pushPageToken(lastPageToken), undefined, undefined, extra)
        }
      >
        <RightOutlined />
      </Button>
    </Space>
  )
}

export default function CursorTable<T extends object>(
  props: CursorTableProps<T>
) {
  const { children, pagination, dataSource, onChange } = props
  const { position = 'bottomRight', size = 'middle' } = pagination || {}
  const [lastPageToken, setLastPageToken] = useState<string>()

  const [verticalPosition, alignmentPosition]: string[] = position.match(
    /[A-Z]?[a-z]+/g
  ) || ['bottom', 'right']

  const justifyPosition: 'start' | 'center' | 'end' = {
    left: 'start',
    center: 'center',
    right: 'end'
  }[alignmentPosition.toLowerCase()] as 'start' | 'center' | 'end'

  useEffect(() => {
    /* Get last evaluated key from last item in data source */
    if (dataSource && dataSource.length > 0) {
      const lastDataItem = dataSource[dataSource.length - 1] as T & {
        lastPageToken?: string
      }
      setLastPageToken(lastDataItem?.lastPageToken || undefined)
    } else {
      setLastPageToken(undefined)
    }
  }, [dataSource])

  return (
    <Space direction="vertical" style={{ width: '100%' }}>
      {verticalPosition === 'top' && (
        <Row justify={justifyPosition}>
          <Col>
            <CursorPagination
              lastPageToken={lastPageToken}
              onChange={onChange as any} // Cast because onChange from TableProps has a different signature
              size={size}
            />
          </Col>
        </Row>
      )}
      <Table {...props} pagination={false}>
        {children}
      </Table>
      {verticalPosition === 'bottom' && (
        <Row justify={justifyPosition}>
          <Col>
            <CursorPagination
              lastPageToken={lastPageToken}
              onChange={onChange as any} // Cast because onChange from TableProps has a different signature
              size={size}
            />
          </Col>
        </Row>
      )}
    </Space>
  )
}
