import {
  createContext,
  Dispatch,
  memo,
  MutableRefObject,
  SetStateAction,
  useContext,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from 'react'
import { Field, Fields, ItemActions, Select, Sort } from '../../utils/common.js'
import { Text } from '../Text/index.js'
import { deepEqual } from '@saulx/utils'
import { styled } from 'inlines'
import { colors } from '../../utils/colors.js'
import { Menu } from '../Menu/index.js'
import { IconButton } from '../IconButton/index.js'
import { CheckboxInput } from '../CheckboxInput/index.js'
import { radius } from '../../utils/radius.js'
import { Icon } from '../Icon/index.js'

type TableRowHeight = 'regular' | 'large'
type TableLayout = 'regular' | 'showAllColumns'

type TableProps = {
  fields: Fields
  data: any[]
  total?: number
  sort?: Sort
  select?: Select
  rowHeight?: TableRowHeight
  layout?: TableLayout
  itemActions?: ItemActions
  highlightReRenders?: boolean
  headerTransparentBackground?: boolean
  onSelectChange?: (select: Select) => void
  onSortChange?: Dispatch<SetStateAction<Sort>>
  onScroll?: (
    first: number,
    last: number,
    firstWithoutBuffer: number,
    lastWithoutBuffer: number,
  ) => void
  onItemClick?: (item: any) => void
  onHideField?: (key: string) => void
}

const TableContext = createContext<
  | (Pick<
      TableProps,
      | 'fields'
      | 'rowHeight'
      | 'layout'
      | 'itemActions'
      | 'highlightReRenders'
      | 'sort'
      | 'select'
      | 'onSelectChange'
      | 'onSortChange'
      | 'onItemClick'
      | 'onHideField'
    > & { hiddenColumns: string[] })
  | null
>(null)

const HEADER_HEIGHT: Record<TableProps['rowHeight'], number> = {
  regular: 40,
  large: 60,
}
const ROW_HEIGHT: Record<TableProps['rowHeight'], number> = {
  regular: 40,
  large: 60,
}

function Table({
  data,
  fields,
  rowHeight = 'regular',
  layout = 'regular',
  total = 0,
  itemActions,
  highlightReRenders,
  select,
  sort,
  onScroll,
  onSelectChange,
  onSortChange,
  onItemClick,
  onHideField,
}: TableProps) {
  const [first, setFirst] = useState<number>(0)
  const [hiddenColumns, setHiddenColumns] = useState([])
  const ref = useRef<HTMLDivElement>(null)
  const context = useMemo(
    () => ({
      fields,
      rowHeight,
      layout,
      itemActions,
      highlightReRenders,
      sort,
      select,
      hiddenColumns,
      onSelectChange,
      onSortChange,
      onItemClick,
      onHideField,
    }),
    [
      fields,
      rowHeight,
      layout,
      itemActions,
      highlightReRenders,
      select,
      sort,
      hiddenColumns,
      onSelectChange,
      onSortChange,
      onItemClick,
      onHideField,
    ],
  )

  useLayoutEffect(() => {
    const div = ref.current

    function handleScroll() {
      const { scrollTop, clientHeight } = div

      const count = Math.ceil(clientHeight / ROW_HEIGHT[rowHeight])
      const actualFirst = Math.floor(scrollTop / ROW_HEIGHT[rowHeight])
      const actualLast = Math.min(total, actualFirst + count)
      const buffer = Math.floor(count / 2)
      const first = Math.max(0, actualFirst - buffer)
      const last = Math.min(total, actualLast + buffer)

      setFirst(first)
      onScroll?.(first, last, actualFirst, actualLast)
    }

    handleScroll()
    div.addEventListener('scroll', handleScroll)
    const resizeObserver = new ResizeObserver(handleScroll)
    resizeObserver.observe(div)

    return () => {
      div.removeEventListener('scroll', handleScroll)
      resizeObserver.disconnect()
    }
  }, [rowHeight, total])

  useLayoutEffect(() => {
    const div = ref.current

    function calculateWidthOfASingleFlexibleColumn(
      clientWidth: number,
      fields: Field[],
    ) {
      let numberOfFlexibleColumns = 0
      let sumOfWidthOfFixedColumns = 0
      let sumOfWidthOfExtraColumns = 0

      if (onSelectChange) {
        sumOfWidthOfExtraColumns += 40
      }
      if (itemActions) {
        sumOfWidthOfExtraColumns += 44
      }

      for (const field of fields) {
        if (field.table?.width) {
          sumOfWidthOfFixedColumns += field.table.width + 2 * 6
        } else {
          numberOfFlexibleColumns += 1
        }
      }

      return (
        (clientWidth - sumOfWidthOfFixedColumns - sumOfWidthOfExtraColumns) /
        numberOfFlexibleColumns
      )
    }

    function handleResize() {
      const { clientWidth } = div
      const fieldsArray = Object.values(fields)
      let widthOfASingleFlexibleColum = calculateWidthOfASingleFlexibleColumn(
        clientWidth,
        fieldsArray,
      )

      const columsnToHide: string[] = []

      while (widthOfASingleFlexibleColum < 180 && fieldsArray.length > 2) {
        const field = fieldsArray.pop()
        columsnToHide.push(field.key)

        widthOfASingleFlexibleColum = calculateWidthOfASingleFlexibleColumn(
          clientWidth,
          fieldsArray.filter((e) => !columsnToHide.includes(e.key)),
        )
      }

      setHiddenColumns((prev) =>
        prev.length === columsnToHide.length ? prev : columsnToHide,
      )
    }

    handleResize()
    const resizeObserver = new ResizeObserver(handleResize)
    resizeObserver.observe(div)

    return () => {
      resizeObserver.disconnect()
    }
  }, [fields, itemActions, onSelectChange])

  return (
    <TableContext.Provider value={context}>
      <div
        style={{
          height: '100%',
          width: '100%',
          overflow: 'auto',
        }}
        ref={ref}
      >
        <table
          style={{
            borderSpacing: 0,
            tableLayout: 'fixed',
            width: layout === 'showAllColumns' ? undefined : '100%',
            minWidth: '100%',
          }}
        >
          <MemoizedTableHeader />
          <tbody>
            <tr>
              <td
                style={{
                  padding: 0,
                  height: first * ROW_HEIGHT[rowHeight],
                }}
              />
            </tr>
            {data.map((row, index) => (
              <MemoizedTableRow key={index} row={row} />
            ))}
            <tr>
              <td
                style={{
                  padding: 0,
                  height: (total - first - data.length) * ROW_HEIGHT[rowHeight],
                }}
              />
            </tr>
          </tbody>
        </table>
      </div>
    </TableContext.Provider>
  )
}
const MemoizedTable = memo(Table)

function TableHeader() {
  const {
    fields,
    rowHeight,
    layout,
    sort,
    itemActions,
    hiddenColumns,
    onSortChange,
    onHideField,
    onSelectChange,
  } = useContext(TableContext)
  return (
    <thead>
      <tr>
        <th
          style={{
            width: onSelectChange ? 20 + 14 + 6 : 8,
            padding: 0,
            borderBottom: `1px solid ${colors.neutral20Adjusted}`,
            zIndex: 3,
            position: 'sticky',
            top: 0,
            background: colors.neutralInverted100,
          }}
        >
          <div
            style={{
              display: 'flex',
              justifyContent: 'center',
              alignItems: 'center',
              width: onSelectChange ? 20 + 14 + 6 : 8,
              padding: onSelectChange ? '0 6px 0 14px' : 'unset',
            }}
          />
        </th>
        {Object.values(fields)
          .filter(
            (e) =>
              layout === 'showAllColumns' || !hiddenColumns.includes(e.key),
          )
          .map((field) => (
            <th
              key={field.key}
              style={{
                padding: 0,
                height: HEADER_HEIGHT[rowHeight],
                margin: 0,
                borderBottom: `1px solid ${colors.neutral20Adjusted}`,
                zIndex: 3,
                ...(field?.table?.width &&
                  layout !== 'showAllColumns' && {
                    width: field.table.width + 2 * 6,
                  }),
                position: 'sticky',
                top: 0,
                background: colors.neutralInverted100,
              }}
            >
              <div
                style={{
                  height: '100%',
                  width: '100%',
                  padding: '4px 0',
                }}
              >
                {(onSortChange || onHideField) && (
                  <Menu>
                    <Menu.Trigger>
                      {({ open }) => (
                        <styled.div
                          style={{
                            display: 'flex',
                            alignItems: 'center',
                            gap: 4,
                            minHeight: 24,
                            userSelect: 'none',
                            position: 'relative',
                            height: '100%',
                            padding: '0 6px',
                            borderRadius: radius[8],
                            ...(open && {
                              background: colors.neutral10Adjusted,
                            }),
                            '&:hover': {
                              background: colors.neutral10Adjusted,
                            },
                          }}
                        >
                          <>
                            {sort?.key === field.key && (
                              <Icon
                                variant={
                                  sort.direction === 'desc'
                                    ? 'arrow-down'
                                    : 'arrow-up'
                                }
                              />
                            )}
                            <Text singleLine variant="display-bold">
                              {field?.table?.title}
                            </Text>
                          </>
                        </styled.div>
                      )}
                    </Menu.Trigger>
                    <Menu.Items>
                      {onSortChange && (
                        <>
                          <Menu.Header>Sort...</Menu.Header>
                          <Menu.Item
                            onClick={() => {
                              onSortChange({
                                key: field.key,
                                direction: 'desc',
                              })
                            }}
                            leadIcon="arrow-down"
                          >
                            Descending
                          </Menu.Item>
                          <Menu.Item
                            onClick={() => {
                              onSortChange({
                                key: field.key,
                                direction: 'asc',
                              })
                            }}
                            leadIcon="arrow-up"
                          >
                            Ascending
                          </Menu.Item>
                        </>
                      )}
                      {onSortChange && onHideField && <Menu.Separator />}
                      {onHideField && (
                        <Menu.Item
                          onClick={() => {
                            onHideField(field.key)
                          }}
                          leadIcon="show"
                        >
                          Hide field
                        </Menu.Item>
                      )}
                    </Menu.Items>
                  </Menu>
                )}
                {!onSortChange && !onHideField && (
                  <div
                    style={{
                      display: 'flex',
                      alignItems: 'center',
                      gap: 4,
                      minHeight: 24,
                      userSelect: 'none',
                      position: 'relative',
                      height: '100%',
                      padding: '0 6px',
                    }}
                  >
                    <Text singleLine variant="display-bold">
                      {field?.table?.title}
                    </Text>
                  </div>
                )}
              </div>
            </th>
          ))}
        {itemActions && (
          <th
            style={{
              padding: '0 14px 0 6px',
              height: HEADER_HEIGHT[rowHeight],
              margin: 0,
              borderBottom: `1px solid ${colors.neutral20Adjusted}`,
              zIndex: 3,
              width: 24 + 14 + 6,
              position: 'sticky',
              top: 0,
              background: colors.neutralInverted100,
            }}
          >
            <div
              style={{
                display: 'flex',
                alignItems: 'center',
                gap: 4,
                minHeight: 24,
                userSelect: 'none',
              }}
            />
          </th>
        )}
      </tr>
    </thead>
  )
}
const MemoizedTableHeader = memo(TableHeader)

type TableRowProps = {
  row: any
}
function TableRow({ row }: TableRowProps) {
  const {
    fields,
    rowHeight,
    itemActions,
    select,
    layout,
    hiddenColumns,
    onSelectChange,
    onItemClick,
  } = useContext(TableContext)
  const [forceHover, setForceHover] = useState(false)

  return (
    <styled.tr
      data-selected={select?.includes(row.id) ? true : undefined}
      data-forcehover={forceHover ? true : undefined}
      style={{
        '&:hover, &[data-forcehover], &[data-selected]': {
          background: colors.neutral10Adjusted,
        },
        '&:hover .checkbox, &:hover .itemaction, &[data-forcehover] .checkbox, &[data-selected] .checkbox, &[data-forcehover] .itemaction':
          {
            display: 'flex !important',
          },
      }}
    >
      <td
        style={{
          padding: 0,
        }}
      >
        {onSelectChange && (
          <div
            className="checkbox"
            style={{
              display: 'none',
              justifyContent: 'center',
              alignItems: 'center',
              padding: '0 6px 0 14px',
            }}
          >
            <CheckboxInput
              value={select?.includes(row.id)}
              onChange={() => {
                if (!select) {
                  onSelectChange([row.id])
                  return
                }

                if (select.includes(row.id)) {
                  onSelectChange(select.filter((e) => e !== row.id))
                  return
                }

                onSelectChange([...select, row.id])
              }}
            />
          </div>
        )}
      </td>
      {Object.values(fields)
        .filter(
          (e) => layout === 'showAllColumns' || !hiddenColumns.includes(e.key),
        )
        .map((field) => (
          <td
            key={field.key}
            style={{
              position: 'relative',
              height: ROW_HEIGHT[rowHeight],
              padding: '0 6px',
              margin: 0,
              ...(onItemClick && {
                cursor: 'pointer',
              }),
            }}
            onClick={() => {
              onItemClick?.(row)
            }}
          >
            {field.render ? (
              <MemoizedCustomTableCell
                row={field.renderAccessorFn ? field.renderAccessorFn(row) : row}
                field={field}
              />
            ) : (
              <MemoizedTableCell content={row[field.key]} />
            )}
          </td>
        ))}
      {itemActions && (
        <td
          style={{
            height: ROW_HEIGHT[rowHeight],
            padding: '0 14px 0 6px',
            margin: 0,
          }}
        >
          <div
            className="itemaction"
            style={{ display: 'none', justifyContent: 'flex-end' }}
          >
            <Menu onOpenChange={setForceHover}>
              <Menu.Trigger>
                <IconButton icon="more-vertical" size="small" />
              </Menu.Trigger>
              <Menu.Items>
                {itemActions.map((action) => (
                  <Menu.Item
                    key={action.label}
                    leadIcon={action.icon}
                    onClick={() => {
                      action?.onClick(row)
                    }}
                    color={action.color}
                  >
                    {action.label}
                  </Menu.Item>
                ))}
              </Menu.Items>
            </Menu>
          </div>
        </td>
      )}
    </styled.tr>
  )
}
const MemoizedTableRow = memo(TableRow, deepEqual)

type TableCellProps = { content: any | undefined }
function TableCell({ content = '' }: TableCellProps) {
  const { highlightReRenders } = useContext(TableContext)

  return (
    <styled.div
      key={highlightReRenders ? Math.random() : undefined}
      style={{
        height: '100%',
        display: 'flex',
        alignItems: 'center',
      }}
    >
      {highlightReRenders && (
        <styled.div
          style={{
            position: 'absolute',
            inset: 2,
            borderRadius: radius[8],
            '@keyframes': {
              from: {
                background: colors.neutral10Adjusted,
              },
              to: {
                background: 'transparent',
              },
            },
            animationDuration: '400ms',
            animationTimingFunction: 'linear',
          }}
        />
      )}
      <Text singleLine color="neutral80">
        {content}
      </Text>
    </styled.div>
  )
}
const MemoizedTableCell = memo(TableCell)

type CustomTableCellProps = { row: any; field: Field }
function CustomTableCell({ row, field }: CustomTableCellProps) {
  const { highlightReRenders } = useContext(TableContext)

  return (
    <styled.div
      key={highlightReRenders ? Math.random() : undefined}
      style={{
        height: '100%',
        display: 'flex',
        alignItems: 'center',
      }}
    >
      {highlightReRenders && (
        <styled.div
          style={{
            position: 'absolute',
            inset: 2,
            borderRadius: radius[8],
            '@keyframes': {
              from: {
                background: colors.neutral10Adjusted,
              },
              to: {
                background: 'transparent',
              },
            },
            animationDuration: '400ms',
            animationTimingFunction: 'linear',
          }}
        />
      )}
      {field.render(row, 'table') ?? (
        <Text singleLine color="neutral80">
          -
        </Text>
      )}
    </styled.div>
  )
}
const MemoizedCustomTableCell = memo(CustomTableCell)

export { MemoizedTable as Table }
export type { TableProps, TableLayout, TableRowHeight }
