// React
import React, {
  FC,
  ForwardedRef,
  forwardRef,
  useCallback,
  useEffect,
  useImperativeHandle,
  useMemo,
  useState,
} from "react";

// Components
import { Grid, Table, TableContainer } from "@mui/material";
import { LinearProgress } from "../LinearProgress";
import { Body, Header, Pagination } from "./components";

// Utils
import { getClearValue, multipleSort } from "./tools";
import _ from "lodash";
import { jsPDF } from "jspdf";
import "jspdf-autotable";

// Styles
import "./styles.scss";

// Types
import { Column, TableRef, Types } from "./types";

declare global {
  interface Navigator {
    msSaveBlob?: (blob: any, defaultName?: string) => boolean;
  }
}

export const TableView: FC<Types> = forwardRef(
  (props, ref: ForwardedRef<TableRef>) => {
    const {
      data,
      height,
      topheight,
      loading = false,
      stickyHeader = true,
      columns = [],
      onChangeColumns,
      onChangeSort,
      onSelectedItems,
      infiniteScroll = false,
      onChangePage,
      hidePagination,
      nextToken,
      groupingValueToCompare,
      grouping,
      searchText,
      messageNoData,
      disableItemSelection = false,
      groupingArea,
      onCollapse,
      keyColumn,
      hideAllInPagination = false,
      onChangeDisplayData,
      moreRowsAvailable,
      onChangeRowsPerPage,
      noMoreItemsMessage,
      exportTitle,
      getDataToExport,
      exportName,
      exportOrientation,
      defaultPage,
      valuesPerPage,
      allData,
      isLocalData
    } = props;

    // States
    const [sort, setSort] = useState({ key: "label", order: "desc" });
    const [page, setPage] = useState(0);
    const [selectedItems, setSelectedItems] = useState<any[]>([]);
    const [rowsPerPage, setRowsPerPage] = useState<any | number>(
      infiniteScroll ? 25 : 10
    );
    const [clickedRow, setClickedRow] = useState<any | null>(null);

    interface jsPDFCustom extends jsPDF {
      autoTable: any;
      autoTableSetDefaults: any;
    }

    let sortGroup: any;
    if (grouping) sortGroup = { key: groupingValueToCompare, order: "asc" };
    const [filterData, total] = useMemo(() => {
      if (infiniteScroll && nextToken === undefined) {
        return [data, props.total];
      }

      if (infiniteScroll) {
        return [data, 0];
      }

      if (onChangePage) {
        return [data, props.total];
      }

      let sorts = [];
      let viewData = data;
      let total = data.length;

      if (sortGroup) sorts.push(sortGroup);
      if (sort) sorts.push(sort);

      if (
        searchText &&
        typeof searchText !== "undefined" &&
        searchText.length > 0
      ) {
        viewData = data.filter((x: any) => {
          let result = false;
          columns.forEach((col: any) => {
            let value;
            if (col?.sortValue) {
              value = getClearValue(col?.sortValue(x));
            } else {
              value = getClearValue(x[col.key]);
            }

            if (
              value &&
              value?.toString().indexOf(searchText.toLowerCase()) >= 0
            ) {
              result = true;
            }
          });

          return result;
        });

        total = viewData.length;
      }

      if (sorts && sorts.length > 0)
        viewData = multipleSort(viewData || [], sorts || [], columns || []);

      return [viewData, total];
    }, [data, props.total]);

    const filterDataPaginate = useMemo(() => {
      if (infiniteScroll || onChangePage) {
        return filterData;
      }

      let currentPage = page - 1;
      if (!hidePagination && rowsPerPage != "ALL") {
        let valuesPaginate = filterData.slice(
          currentPage * rowsPerPage,
          currentPage * rowsPerPage + rowsPerPage
        );
        return valuesPaginate;
      }

      return filterData;
    }, [filterData]);

    // Effects
    useEffect(() => {
      if (data?.length && page == 0) {
        setPage(defaultPage || 1);
        onChangeDisplayData && onChangeDisplayData(filterData);
      }
    }, [props.data]);

    const  resetPagination =  useCallback(() => {
            setPage(defaultPage || 1);
            onChangePage && onChangePage(defaultPage || 1)
            setSelectedItems([]);
    }, [defaultPage])

    const refreshTable = useCallback(() => {
      setSelectedItems([])
      if (!onChangePage) return
      onChangePage(page)
    }, [page, onChangePage])

    useEffect(() => {
      if (!onChangePage || !total || data?.length || loading) return
      setPage(currentPage => {
          if (currentPage === 0) return currentPage
          onChangePage(currentPage - 1)
          return currentPage - 1
        }
      )
    }, [total, rowsPerPage, data, loading])

    // Handlers
    const handleRowClick = useCallback((row: any) => {
      setClickedRow(row);
    }, []);

    // Utils

    const getRowValue = (col: Column, row: any) => {
      let value = (col.exportValue && col.exportValue(row)) || row[col.key] || '';
      if (!['number', 'string'].includes(typeof value)) value = '';
      return value
    };
    const exportCSV = useCallback(() => {
      // Filter columns for export with exportOnly support
      const exportOnlyColumns = columns.filter(x => x.exportOnly === true && x.export);
      const visibleExportColumns = columns.filter(x => x.show && x.export);
      
      // Separate ID column from other export-only columns for proper ordering
      const idColumn = exportOnlyColumns.filter(x => x.key === 'id');
      const otherExportOnlyColumns = exportOnlyColumns.filter(x => x.key !== 'id');
      
      // Combine columns with deduplication (ID first, then visible, then other export-only)
      const headers = [...idColumn, ...visibleExportColumns, ...otherExportOnlyColumns].filter((column, index, self) =>
        index === self.findIndex(col => col.key === column.key)
      );

      let csvContent = exportTitle ? exportTitle + "\n" : "";
      csvContent += `${headers.map((x: any) => x.title).join(",")}` + "\n";

      let rows = getDataToExport
        ? getDataToExport(filterData, "csv")
        : filterData;
      if (selectedItems?.length) {
        rows = selectedItems;
      } else if (isLocalData) {
        rows = allData;
      }

      rows.forEach((row: any, index: any) => {
        //its necesary format the text because if possible has a line breaks or ,
        let itemValues: Column[] = [];
        if (getDataToExport) {
          itemValues = row;
        } else {
          itemValues = headers.map((col) => {
            let value = getRowValue(col, row)
            value = value.toString().replaceAll(/(\r\n|\n|\r)/gm, "") || "";
            if (value.includes(",")) value = `"${value}"`;
            return value;
          });
        }

        let dataString = itemValues.join(",");
        csvContent += index < rows.length ? dataString + "\n" : dataString;
      });

      let fileName = `${exportName || "exported-data"}.csv`;
      let mimeType = "text/csv;encoding:utf-8;charset=UTF-8";
      let a = document.createElement("a");

      mimeType = mimeType || "application/octet-stream";

      if (navigator.msSaveBlob) {
        // IE10
        navigator.msSaveBlob(
          new Blob([csvContent], {
            type: mimeType,
          }),
          fileName
        );
      } else if (URL && "download" in a) {
        //html5 A[download]
        a.href = URL.createObjectURL(
          new Blob([csvContent], {
            type: mimeType,
          })
        );
        a.setAttribute("download", fileName);
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
      } else {
        location.href =
          "data:application/octet-stream," + encodeURIComponent(csvContent); // only this mime type is supported
      }
    }, [columns, exportTitle, filterData, selectedItems, isLocalData, allData, exportName]);

    const exportPDF = useCallback(() => {
      const unit = "pt";
      const size = "A4"; // Use A1, A2, A3 or A4
      const orientation = exportOrientation || "portrait"; // portrait or landscape

      const doc = new jsPDF(orientation, unit, size) as jsPDFCustom;
      doc.setFontSize(12);

      doc.autoTableSetDefaults(
        {
          headStyles: {
            font: "helvetica",
            fillColor: "white",
            textColor: "black",
            halign: "center",
            fontStyle: "normal",
            fontSize: 10,
            lineColor: "black",
            lineWidth: { top: 1, right: 1, bottom: 1, left: 1 },
          },
        },
        doc
      );

      let rows = filterData;
      if (selectedItems?.length) {
        rows = selectedItems;
      } else if (isLocalData) {
        rows = allData;
      }

      const headers = columns.filter((x: any) => x?.disabled || (x.show && x.export));
      let content = {
        pageBreak: "auto",
        startY: exportTitle ? 40 : 20,
        head: [headers.map((x: any) => x.title)],
        //its necesary loop the data to assign styles for wrap text when is a long value
        body: getDataToExport
          ? getDataToExport(rows, "pdf")
          : rows.map((row: any) => {
              return headers.map((col: any) => {
                return {
                  content: getRowValue(col, row),
                  styles: {
                    cellWidth: "auto",
                    overflow: "linebreak",
                    minCellWidth: 50,
                    textColor: col.exportColorValue
                      ? col.exportColorValue(row)
                      : "black",
                  },
                };
              });
            })
      };

      if (exportTitle) doc.text("40", 15, exportTitle);

      doc.autoTable(content);
      doc.save(`${exportName || "export"}.pdf`);
    }, [
      exportOrientation,
      selectedItems,
      filterData,
      selectedItems,
      isLocalData,
      allData,
      exportName
    ]);

    // Hooks - Moved after exportPDF and exportCSV definitions
    useImperativeHandle(ref, () => ({
      selectedItems: selectedItems,
      exportPDF: exportPDF,
      exportCSV: exportCSV,
      setPage: setPage,
      setSelectedItems: setSelectedItems,
      columns: columns,
      page: page,
      rowsPerPage: rowsPerPage,
      setSort: setSort,
      resetPagination,
      refreshTable,
      getClickedRow: () => clickedRow
    }), [selectedItems, exportPDF, exportCSV, columns, page, rowsPerPage, sort, resetPagination, refreshTable, clickedRow]);

    return (
      <div
        style={{
          height: height
            ? height
            : (topheight && `calc(100vh - ${topheight}px)`) || "100%",
          width: "100%",
        }}
      >
        <Grid item className="gtc-rc-table_component">
          <>
            {loading && <LinearProgress />}
            <TableContainer>
              <Table
                aria-labelledby="tableTitle"
                size={"medium"}
                aria-label="enhanced table"
                stickyHeader={stickyHeader}
              >
                <Header
                  {...props}
                  columnsView={columns.filter((x: any) => x?.disabled || x.show)}
                  onChangeColumns={(newColumns: any) =>
                    onChangeColumns && onChangeColumns([...newColumns])
                  }
                  sort={sort}
                  page={page}
                  dataItems={filterDataPaginate}
                  onChangeSort={(_sort: any) => {
                    setSort(_sort);
                    let newSort = {..._sort};
                    if (_sort.sortField) newSort.key = _sort.sortField;
                    onChangeSort && onChangeSort(newSort);
                  }}
                  selectedItems={selectedItems}
                  onChageSelectedItems={(newSelectedtItems: any) => {
                    setSelectedItems(newSelectedtItems);
                    onSelectedItems && onSelectedItems(newSelectedtItems);
                  }}
                />
                <Body
                  data={data}
                  loading={loading}
                  messageNoData={messageNoData}
                  columns={columns}
                  disableItemSelection={disableItemSelection}
                  filterDataPaginate={filterDataPaginate}
                  grouping={grouping}
                  groupingValueToCompare={groupingValueToCompare}
                  groupingArea={groupingArea}
                  selectedItems={selectedItems}
                  keyColumn={keyColumn}
                  setSelectedItems={setSelectedItems}
                  onSelectedItems={onSelectedItems}
                  onCollapse={onCollapse}
                  infiniteScroll={infiniteScroll}
                  nextToken={nextToken}
                  onChangePage={onChangePage}
                  setPage={setPage}
                  page={page}
                  total={total}
                  rowsPerPage={rowsPerPage}
                  onChangeDisplayData={onChangeDisplayData}
                  filterData={filterData}
                  moreRowsAvailable={moreRowsAvailable}
                  noMoreItemsMessage={noMoreItemsMessage}
                  onRowClick={handleRowClick}
                />
              </Table>
            </TableContainer>
          </>
        </Grid>
        {!hidePagination && !infiniteScroll && (
          <Grid item xs>
            <div
              style={{
                padding: "5px 0px 0px 0px",
              }}
            >
              <Pagination
                page={page}
                rowsPerPage={rowsPerPage}
                count={total}
                pagination
                valuesPerPage={valuesPerPage}
                hideAllInPagination={hideAllInPagination}
                onChangePage={(event: any, value: any) => {
                  setPage(value);
                  if (onChangePage) onChangePage(value);
                  if (onChangeDisplayData) onChangeDisplayData(filterData);
                  setSelectedItems([]);
                  onSelectedItems && onSelectedItems([]);
                }}
                onChangeRowsPerPage={(value: any) => {
                  setRowsPerPage(value);
                  setPage(1);
                  if (onChangeRowsPerPage) onChangeRowsPerPage(value);
                  if (onChangeDisplayData) onChangeDisplayData(filterData);
                  setSelectedItems([]);
                  onSelectedItems && onSelectedItems([]);
                }}
              />
            </div>
          </Grid>
        )}
      </div>
    );
  }
);

export { useTablePagination } from "./hooks";
