import { Box, Button, Card, Checkbox, CircularProgress, Collapse, darken, Divider, Grid2, IconButton, InputAdornment, ListItemButton, ListItemIcon, ListItemText, Menu, MenuItem, Paper, SxProps, Table, TableBody, TableCell, TableContainer, TableHead, TablePagination, TableRow, TextField, Theme, Typography } from "@mui/material";
import { makeStyles } from "@mui/styles";
import { ArrowDownward, ArrowUpward, ChevronRight, Close, Search, ViewColumn } from "@mui/icons-material"
import { useEffect, useRef, useState } from "react";
import React from "react";
import { useMobile } from "hooks";
import classNames from "classnames";
import { getThemeType } from "theme";

const useStyles = makeStyles((theme: Theme) => {
  //const isMobile = useMobile()
  return ({
    gutter: {
      backgroundColor: darken(theme.palette.background.paper, 0.05),
      border: "none"
    },
    tableContainer: {
      // margin: theme.spacing(1),
      // [theme.breakpoints.up("sm")]: {
      //   margin: theme.spacing(2)
      // },
      // width: "auto"
    },
    title: {
      padding: theme.spacing(1),
    },
    subtitle: {
      padding: theme.spacing(1),
      marginTop: theme.spacing(-2),
    },
    titleComp: {
      padding: theme.spacing(1),
      marginLeft: theme.spacing(-1)
    },
    subtitleComp: {
      padding: theme.spacing(1),
      marginTop: theme.spacing(-2),
      marginLeft: theme.spacing(-1),
    },
    titleContainer: {
      border: "none",
    },
    searchContainer: {
      border: "none",
      textAlign: "right",
    },
    mobileTitleBox: {
      margin: theme.spacing(1),
      marginBottom: theme.spacing(1.5),
    },
    card: {
      margin: theme.spacing(1),
    },
    rowHover: {
      cursor: "pointer",
      "&:hover": {
        backgroundColor: "rgba(150, 150, 150, 0.15)",
      }
    },
    stickyHeader: {
      position: "sticky",
      backgroundColor: getThemeType() === "light" ? "rgb(245, 245, 245)" : "rgb(40, 40, 40)",
      top: 0,
      zIndex: 300 //giving a really high z-index to make sure nothing is in front of it
    }
  })},
);

export interface Column<T> {
  title: string;
  cellStyle?: SxProps<Theme>;
  hidden?: boolean;
  render: (row: T) => JSX.Element;
  sortKey?: string;
  disableSort?: boolean;
}

interface Props<T> {
  rows: T[],
  columns?: Column<T>[] | (() => Column<T>[]),
  title?: string | JSX.Element | (() => string | JSX.Element);
  subtitle?: string | JSX.Element | (() => string | JSX.Element);
  total: number,
  pageSize: number,
  page: number,
  setPage: (value: React.SetStateAction<number>) => void,
  handleRowsPerPageChange: (event: any) => void,
  renderGutter?: (row: T) => JSX.Element,
  gutterPadding?: number,
  setSearchText?: React.Dispatch<React.SetStateAction<string>>,
  isLoading?: boolean;
  multiGutter?: boolean;
  rowsPerPageOptions?: number[];
  noDataMessage?: string;
  renderMobile?: (row: T, index: number) => JSX.Element;
  hideKeys?: boolean;
  onRowClick?: (row: T) => void;
  actions?: string | JSX.Element;
  rowSelect?: (row: T, index: number, checked: boolean) => void; //row select function that passes the the row back up, when this function is passed in render a column of checkboxes
  selectedRows?: number[]; //which rows are selected will need to be controlled by the parent in order to keep track between page changes
  customElement?: JSX.Element //element that will be placed in the table head between the table actions and the column header rows
  mobileView?: boolean //can be used to force the table to use its mobile view for narrow containing elements ie, drawer
  hidePagination?: boolean //when passed in will hide the pagination at the bottom of the table
  orderBy?: string;
  setOrderBy?: React.Dispatch<React.SetStateAction<string>>;
  order?: string;
  setOrder?: React.Dispatch<React.SetStateAction<"asc" | "desc">>;
  endTitleElement?: string | JSX.Element | (() => string | JSX.Element);
  loadMore?: () => void;
  resizeable?: boolean;
  stickyHeader?: boolean
}

export default function ResponsiveTable<T extends Object>(props: Props<T>) {
  const { 
    rows, 
    total, 
    page, 
    pageSize, 
    setPage, 
    handleRowsPerPageChange, 
    // title, 
    // subtitle,
    renderGutter,
    gutterPadding,
    setSearchText, 
    isLoading,
    multiGutter,
    // columns,
    rowsPerPageOptions,
    noDataMessage,
    renderMobile,
    hideKeys,
    onRowClick,
    actions,
    rowSelect,
    selectedRows,
    customElement,
    mobileView,
    hidePagination,
    orderBy,
    setOrderBy,
    order, 
    setOrder,
    loadMore,
    // resizeable,
    stickyHeader
  } = props
  const classes = useStyles();
  const columns = typeof(props.columns) === "function" ? props.columns() : props.columns
  const subtitle = typeof(props.subtitle) === "function" ? props.subtitle() : props.subtitle
  const title = typeof(props.title) === "function" ? props.title() : props.title
  const endTitleElement = typeof(props.endTitleElement) === "function" ? props.endTitleElement() : props.endTitleElement
  const resizeable = props.resizeable === undefined ? false : props.resizeable

  const isMobile = useMobile()

  const [inputSearchText, setInputSearchText] = useState("");
  const [openGutters, setOpenGutters] = useState<number[]>([])
  const [handler, setHandler] = useState<NodeJS.Timeout | undefined>(undefined);
  const [columnAnchor, setColumnAnchor] = useState<any>(null)
  
  const rowKey = window.location.pathname.split('/').pop() + "-table-row-widths";
  const [rowWidths, setRowWidths] = useState<number[]>(() => {
    const saved = localStorage.getItem(rowKey);
    return saved ? JSON.parse(saved) : [];
  })

  useEffect(() => {
    localStorage.setItem(rowKey, JSON.stringify(rowWidths));
  }, [rowWidths])

  const dragIndexRef = useRef<number | undefined>(undefined);
  const dragStartRef = useRef<number | undefined>(undefined);
  const rowWidthsRef = useRef<number[]>([]);

  const filterKey = window.location.pathname.split('/').pop() + "-table-filter-list";

  const [filterList, setFilterList] = useState<string[]>(() => {
    const saved = localStorage.getItem(filterKey);
    return saved ? JSON.parse(saved) : [];
  });

  useEffect(() => {
    localStorage.setItem(filterKey, JSON.stringify(filterList));
  }, [filterList])

  const openColumnMenu = (event: any) => {
    setColumnAnchor(event.currentTarget);
    // setUserMenuIsOpen(true);
  };

  const closeColumnMenu = () => {
    setColumnAnchor(null);
    // setUserMenuIsOpen(true);
  };

  useEffect(() => {
    if (!setSearchText) return;
    if (inputSearchText === undefined) return;
    setHandler(setTimeout(() => {
      setSearchText(inputSearchText);
    }, 750)); // Delay of 750ms

    return () => clearTimeout(handler); // Cleanup on change
  }, [inputSearchText]);

  const handleCollapse = (index: number, event: React.MouseEvent<HTMLDivElement | HTMLButtonElement, MouseEvent>) => {
    event.stopPropagation();
    if (multiGutter) {
      if (openGutters.includes(index)) {
        setOpenGutters(openGutters => openGutters.filter(num => num !== index));
      } else {
        setOpenGutters(openGutters => [...openGutters, index]);
      }
    } else {
      if (openGutters.includes(index)) {
        setOpenGutters([])
      } else {
        setOpenGutters([index])
      }
    }
  }

  const handleKeyDown = (e: any) => {
    if (e.key === 'Enter') {
      if (setSearchText) setSearchText(inputSearchText);
      clearTimeout(handler)
    }
  };

  const handlePageChange = (page: number) => {
    setOpenGutters([])
    setPage(page)
  }

  const clearSearch = () => {
    setInputSearchText("")
  }

  const searchBar = () => {
    return (
      <TextField 
        fullWidth
        variant="standard"
        placeholder="Search..."
        //className={classes.title}
        value={inputSearchText}
        onKeyDown={handleKeyDown}
        onChange={e => setInputSearchText(e.currentTarget.value)}
        InputProps={{
          startAdornment: (
            <InputAdornment position="start">
              <Search />
            </InputAdornment>
          ),
          endAdornment: (
            <InputAdornment position="end">
              <IconButton onClick={clearSearch}>
                <Close />
              </IconButton>
            </InputAdornment>
          ),
        }}
      />
    )
  }

  const renderTitle = () => {
    if (typeof(title) === "string" ) return (
      <Typography variant="h5" className={classes.title}>
        {title}
      </Typography>
    )
    if (title) return (
      <Box className={classes.titleComp}>
        {title}
      </Box>
    )
    return null
  }

  const renderSubtitle = () => {
    if (typeof(subtitle) === "string" ) return (
      <Typography variant="body2" color="textSecondary" className={classes.subtitle}>
        {subtitle}
      </Typography>
    )
    if (subtitle) return (
      <Box className={classes.subtitleComp}>
        {subtitle}
      </Box>
    )
    return null
  }

  function capitalize(val: any) {
    return String(val).charAt(0).toUpperCase() + String(val).slice(1);
  }

  const renderMobileRow = (row: T, index: number) => {
    if (renderMobile) return renderMobile(row, index)
    if (columns) {
      return (
        <Grid2 container direction={"row"} spacing={1} >
          {columns.map((column, index) => {
            return (
              <React.Fragment key={"mobile-row-column-"+index}>
                { !hideKeys && <Grid2 size={{ xs: 3 }} alignContent={"center"}>
                  <Typography fontWeight={"bold"} >
                    {column.title+": "}
                  </Typography>
                </Grid2>}
                <Grid2 size={{ xs: hideKeys ? 12 : 9 }} alignContent={"center"} >
                  {column.render(row)}
                </Grid2>
              </React.Fragment>
            )
          })}
        </Grid2>
      )
    }
    return (
      Object.keys(row).map((key, index) => {
        const value = row[key as keyof typeof row]
        return (
          <Grid2 container direction={"row"} key={"mobile-list-"+index} spacing={1} padding={1}>
            <Grid2 size={{ xs: 4 }} alignContent={"center"} >
              <Typography fontWeight={"bold"} >
                {capitalize(key)+": "}
              </Typography>
            </Grid2>
            <Grid2 size={{ xs: 8 }}>
              <Typography>
                {value+""}
              </Typography>
            </Grid2>
          </Grid2>
        )
      })
    )
  }


  if (isMobile || mobileView) return (
    <Box>
      <Box className={classes.mobileTitleBox}>
        {renderTitle()}
        {renderSubtitle()}
        {setSearchText && searchBar()}
      </Box>
      {rows.map((row, index) => {
        return (
          <Card className={classNames(classes.card)} key={"mobile-card-"+index} onClick={() => onRowClick&&onRowClick(row)}>
            {renderMobileRow(row, index)}
            {renderGutter &&
              <>
              <Divider/>
              <Collapse className={classes.gutter} in={openGutters.includes(index)} >
                {renderGutter(row)}
              </Collapse>
              {openGutters.includes(index) && <Divider/>}
              <ListItemButton 
                sx={{ 
                  width: "100%", 
                  display: "flex", 
                  justifyContent: "center",
                  padding: gutterPadding
                }} 
                onClick={(event) => handleCollapse(index, event)} 
              >
                <IconButton 
                  sx={{
                    transform: openGutters.includes(index) ? 
                                'rotate(-90deg)' : 'rotate(90deg)',
                    transition: 'transform 0.3s ease',
                    alignContent: "center"
                  }}
                >
                  <ChevronRight/>
                </IconButton>
              </ListItemButton>
             </>
            }
          </Card>
        )
      })}
      {(rows && rows.length < 1) &&
        (!isLoading ?
          <Typography sx={{ textAlign: "center" }} color="textSecondary">
            { noDataMessage ? noDataMessage : "No data."}
          </Typography>
        :
          <Box sx={{ width: "100%", textAlign: "center" }} >
            <CircularProgress/>
          </Box>
        )
      }
      {(loadMore && rows.length < total) &&
        <Box>
          <Button 
            onClick={loadMore}
            color="primary"
            sx={{width: "100%"}} 
            variant="contained">
              Load More
          </Button>
        </Box>
      }
    </Box>
  )

  const filter = (colName: string) => {
    let newFilterList = [...filterList]
    if (filterList.includes(colName)) {
      newFilterList = newFilterList.filter(name => name !== colName)
    } else {
      newFilterList.push(colName)
    }
    setFilterList(newFilterList)
  }

  const columnClick = (column: Column<T>) => {
    const sortKey = column.sortKey ? column.sortKey : column.title.toLowerCase()
    if (setOrderBy) {
      if (orderBy === sortKey && setOrder && order) {
        if (order === "desc") {
          setOrder("asc")
        } else {
          setOrderBy("")
        }
      } else {
        if (setOrder) setOrder("desc")
        setOrderBy(sortKey)
      }
    }
  }

  const showOrderIcon = (column: Column<T>) => {
    if (!orderBy) return
    const sortKey = column.sortKey ? column.sortKey : column.title.toLowerCase()
    if (sortKey === orderBy) {
      if (order === "asc") {
        return <ArrowDownward fontSize="small" sx={{ marginLeft: 1 }} />
      } else {
        return <ArrowUpward fontSize="small" sx={{ marginLeft: 1 }} />
      }
    }
    return null;
  }

  const resizeClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>, index: number) => {
    
    let newWidths: number[] = []
    event.currentTarget.parentElement?.parentElement?.parentElement?.childNodes.forEach(node => {
      if (node instanceof HTMLElement) {
        newWidths.push(node.clientWidth)
      }
    })
    
    rowWidthsRef.current = newWidths
    dragIndexRef.current = index;
    dragStartRef.current = event.clientX;

    window.addEventListener('mousemove', resizeDrag);
    window.addEventListener('mouseup', resizeUnclick);
  }

  const resizeUnclick = () => {
    dragIndexRef.current = undefined;
    dragStartRef.current = undefined;
    window.removeEventListener('mousemove', resizeDrag);
    window.removeEventListener('mouseup', resizeUnclick);
  }

  const resizeDrag = (e: any) => {
    const index = dragIndexRef.current;
    const dragStart = dragStartRef.current
    const rowWidths = rowWidthsRef.current
    if (index === undefined) return
    if (rowWidths[index] === undefined) return
    if (rowWidths[index+1] === undefined) return
    if (!dragStart) return

    let newWidths: number[] = [...rowWidths]
    newWidths[index] = newWidths[index] - (dragStart - e.clientX)
    newWidths.every((width, j) => {
      if (j <= index || width === 0) return true
      else if (newWidths[j] !== undefined) {
        newWidths[j] = newWidths[j] + (dragStart - e.clientX)
        return false
      }
    })
    setRowWidths(newWidths)
    
  }

  return (
    <Box className={classes.tableContainer} component={Paper}>
      <Table>
        <TableHead className={stickyHeader ? classes.stickyHeader : undefined}>
          <TableRow sx={ !title && !setSearchText ? { display: "none" } : undefined}>
            <TableCell colSpan={10000} sx={{ border: "none" }}>
              <Grid2 container justifyContent="space-between">
                <Grid2>
                  <Box sx={{ marginTop: -1 }}>
                    {renderTitle()}
                    {renderSubtitle()}
                  </Box>
                </Grid2>
                <Grid2>
                  <Grid2 container alignItems={"center"} spacing={1}>
                    <Grid2>
                      {actions && actions}
                    </Grid2>
                    <Grid2>
                      {endTitleElement}
                    </Grid2>
                    <Grid2>
                      {setSearchText &&<Box className={classes.searchContainer}>
                        {searchBar()}
                      </Box>}
                    </Grid2>
                    <Grid2>
                      { columns && !hideKeys &&
                        <IconButton onClick={openColumnMenu} >
                          <ViewColumn />
                        </IconButton>
                      }
                    </Grid2>
                  </Grid2>
                </Grid2>
              </Grid2>
            </TableCell>
          </TableRow>
        
          {customElement && 
            <TableRow>
              <TableCell colSpan={10000} sx={{ border: "none" }}>
                {customElement}
              </TableCell>
            </TableRow>
          }
        
          <TableRow sx={ hideKeys ? { display: "none" } : undefined } >
            { rowSelect && <TableCell></TableCell> }
            { renderGutter && <TableCell></TableCell>}
            { columns ?
              columns.map((column, index) => {
                if (filterList.includes(column.title) || column.hidden) return (
                  <TableCell key={"row-"+index+"cell-"+index} sx={{ width: 0, margin: 0, padding: 0}} ></TableCell>
                )
                return (
                  <TableCell key={"header-cell-"+index} width={rowWidths[index] ? rowWidths[index] : undefined}>
                    <Grid2 container spacing={1} justifyContent={"space-between"}>
                      <Grid2  
                        alignItems="center" 
                        display="flex"
                        onClick={() => !column.disableSort && columnClick(column)} 
                        sx={{ cursor: column.disableSort ? "default" : "pointer" }}
                      >
                        {column.title} {showOrderIcon(column)}
                      </Grid2>
                      {index < columns.length-1 && resizeable &&
                        <Grid2
                          sx={{ cursor: "col-resize", userSelect: "none" }}
                          onMouseDown={event => resizeClick(event, index)}
                        >
                          |
                        </Grid2>
                      }
                    </Grid2>
                  </TableCell>
                )
              })
            : (rows && rows.length > 0) &&
              Object.keys(rows[0]).map((value, index) => {
                return(
                  <TableCell key={"header-cell-"+index}>
                    {value.toString()}
                  </TableCell>
                )
              })
            }
          </TableRow>
        </TableHead>
        <TableBody>
          { isLoading ? 
            <TableRow>
              { isLoading &&
                <TableCell colSpan={6} sx={{ textAlign: "center"}}>
                  <CircularProgress />
                </TableCell>
              }
            </TableRow>
          :
            rows.map((row, index) => {
              // if (filterList.includes()) return null
              return (
                <React.Fragment key={"row-"+index}>
                  <TableRow onClick={() => onRowClick&&onRowClick(row)} className={onRowClick ? classes.rowHover : undefined}>
                    {rowSelect &&
                      <TableCell>
                        <Checkbox 
                          checked={selectedRows ? selectedRows.includes(index) : false} 
                          onClick={(event) => {event.stopPropagation()}} 
                          onChange={(_, checked) => {
                            rowSelect(row, index, checked)
                          }}/>
                      </TableCell>
                    }
                    {renderGutter && 
                      <TableCell width={1}>
                        <IconButton sx={{
                          transform: openGutters.includes(index) ? 
                                      'rotate(90deg)' : 'rotate(0deg)',
                          transition: 'transform 0.3s ease',
                          marginRight: (isMobile || mobileView) ? -1 : 0
                        }}
                        onClick={(event) => handleCollapse(index, event)}>
                          <ChevronRight/>
                        </IconButton>
                      </TableCell>
                    }
                    {columns ? 
                      columns.map((column, j) => {
                        if (filterList.includes(column.title) || column.hidden) return (
                          <TableCell key={"row-"+index+"cell-"+j} sx={{ width: 0, padding: 0, margin: 0}}></TableCell>
                        )
                        if (column.render) return (
                          <TableCell key={"row-"+index+"cell-"+j} sx={{padding: 0, paddingRight: 2, width: rowWidths[j] ? rowWidths[j] : undefined}}>
                            {column.render(row)}
                          </TableCell>
                        )
                        return (
                          <TableCell key={"row-"+index+"cell-"+j}>
                            {Object.values(row)[j] && Object.values(row)[j].toString()}
                          </TableCell>
                        )
                      })
                    :
                      Object.values(row).map((value, j) => {
                        return (
                          <TableCell key={"row-"+index+"cell-"+j}>
                            {value ? value.toString() : ""}
                          </TableCell>
                        )
                      })
                    }
                  </TableRow>
                  {renderGutter && 
                    <TableRow className={classes.gutter}>
                      <TableCell colSpan={6} sx={{padding:0, margin: 0, border: "none"}}>
                        <Collapse in={openGutters.includes(index)} timeout="auto" unmountOnExit>
                          {renderGutter(row)}
                        </Collapse>
                      </TableCell>
                    </TableRow>
                  }
                </React.Fragment>
              )
            })
          }
          {(rows && rows.length)< 1 && !isLoading &&
            <TableRow>
              <TableCell colSpan={6} sx={{textAlign: "center" }}>
                <Typography variant="body2" color="textSecondary">
                  {noDataMessage ? noDataMessage : "No data found"}  
                </Typography>  
              </TableCell>
            </TableRow>
          }
        </TableBody>
      </Table>
      {!hidePagination &&
        <TablePagination
          rowsPerPageOptions={rowsPerPageOptions ? rowsPerPageOptions : [5, 10, 20]}
          component="div"
          count={total ? total : 0}
          rowsPerPage={pageSize}
          page={page}
          onPageChange={(_event, page) => handlePageChange(page)}
          onRowsPerPageChange={handleRowsPerPageChange}
        />
      }
      <Menu
        id="userMenu"
        anchorEl={columnAnchor}
        open={columnAnchor !== null}
        onClose={closeColumnMenu}
        disableAutoFocusItem>
          {columns?.map((column, index) => {
            return (
              <MenuItem 
                onClick={() => filter(column.title)}
                key={"column-list-"+index} 
                dense 
                sx={{ marginLeft: -0.75, paddingRight: 3 }}
              >
                <ListItemIcon>
                  <Checkbox 
                    checked={!filterList.includes(column.title)}
                  />
                </ListItemIcon>
                <ListItemText primary={column.title} />
              </MenuItem>
            )
          })}
          <MenuItem onClick={() => setRowWidths([])}>
            <Typography variant="body2" padding={1}>
              Reset Widths
            </Typography>
          </MenuItem>
      </Menu>
    </Box>
  )
}