import * as DialogPrimitive from "@radix-ui/react-dialog";
import {
  flexRender,
  getCoreRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  SortingState,
  useReactTable,
  Column,
  Row,
} from "@tanstack/react-table";
import clsx from "clsx";
import { useEffect, useMemo, useRef, useState } from "react";
import xss from "xss";
import useDarkMode from "../../utils/useDarkMode";
import useLocalStorage from "../../utils/useLocalStorage";
import {
  formatNumber,
  formatNumberMagnitude,
  formatNumberNoMagnitude,
  fuzzyFilter,
  includesDateNames,
  includesPriceNames,
  isEqual,
} from "../../utils/utils";
import CloseIcon from "../Icons/Close";
import Select from "../Select";
import Toast from "../Toast";
import DraggableColumnHeader, {
  isoYearRegex,
  magnitudeRegex,
} from "./ColumnHeader";
import DownloadFinishedDialog from "./DownloadFinishedDialog";
import Export from "./Export";
import FilterColumns from "./FilterColumns";
import Pagination, { validatePageSize } from "./Pagination";

const date = new Date();

const MAX_COLUMNS = 50;
export const DEFAULT_ROWS_PER_PAGE = 30;

//@ts-ignore
function getCellWidth(row, column) {
  try {
    const indexLabel = row.hasOwnProperty("index")
      ? "index"
      : row.hasOwnProperty("Index")
      ? "Index"
      : null;
    const indexValue = indexLabel ? row[indexLabel] : null;
    const value = row[column];
    const valueType = typeof value;
    const only_numbers = value?.toString().replace(/[^0-9]/g, "");

    const probablyDate =
      only_numbers?.length >= 4 &&
      (includesDateNames(column) ||
        column.toLowerCase() === "index" ||
        (indexValue &&
          typeof indexValue === "string" &&
          (indexValue.toLowerCase().includes("date") ||
            indexValue.toLowerCase().includes("day") ||
            indexValue.toLowerCase().includes("time") ||
            indexValue.toLowerCase().includes("timestamp") ||
            indexValue.toLowerCase().includes("year") ||
            indexValue.toLowerCase().includes("month") ||
            indexValue.toLowerCase().includes("week") ||
            indexValue.toLowerCase().includes("hour") ||
            indexValue.toLowerCase().includes("minute"))));

    const probablyLink = valueType === "string" && value.startsWith("http");

    if (probablyLink || !probablyDate) {
      return value?.toString().length ?? 0;
    }
    if (
      probablyDate &&
      !isNaN(new Date(value).getTime()) &&
      !isoYearRegex.test(value?.toString())
    ) {
      if (typeof value === "string") {
        return value?.toString().length ?? 0;
      }
      try {
        const date = new Date(value);
        let dateFormatted = "";
        if (
          date.getUTCHours() === 0 &&
          date.getUTCMinutes() === 0 &&
          date.getUTCSeconds() === 0 &&
          date.getMilliseconds() === 0
        ) {
          dateFormatted = date.toISOString().split("T")[0];
        } else {
          dateFormatted = date.toISOString();
          dateFormatted = `${dateFormatted.split("T")[0]} ${
            dateFormatted.split("T")[1].split(".")[0]
          }`;
        }

        return dateFormatted?.toString().length ?? 0;
      } catch (e) {
        return value?.toString().length ?? 0;
      }
    }

    return value?.toString().length ?? 0;
  } catch (e) {
    return 0;
  }
}

export const EXPORT_TYPES = ["csv", "png"];
export default function Table({
  data,
  columns,
  title,
  initialTheme,
  cmd = "",
}: {
  data: any[];
  columns: any[];
  title: string;
  initialTheme: "light" | "dark";
  cmd?: string;
}) {
  const [type, setType] = useLocalStorage("exportType", EXPORT_TYPES[0]);
  const [downloadFinished, setDownloadFinished] = useState(false);
  const [colorTheme, setTheme] = useDarkMode(initialTheme);
  const [darkMode, setDarkMode] = useState(
    colorTheme === "dark" ? true : false,
  );
  const toggleDarkMode = (checked: boolean) => {
    //@ts-ignore
    setTheme(colorTheme);
    setDarkMode(checked);
  };

  const [currentPage, setCurrentPage] = useLocalStorage(
    "rowsPerPage",
    DEFAULT_ROWS_PER_PAGE,
    validatePageSize,
  );
  const [advanced, setAdvanced] = useLocalStorage("advanced", false);
  const [colors, setColors] = useLocalStorage("colors", false);
  const [sorting, setSorting] = useState<SortingState>([]);
  const [globalFilter, setGlobalFilter] = useState("");
  const [fontSize, setFontSize] = useLocalStorage("fontSize", "1");
  const [open, setOpen] = useState(false);
  const defaultVisibleColumns = columns.reduce((acc, cur, idx) => {
    acc[cur] = idx < MAX_COLUMNS ? true : false;
    return acc;
  }, {});
  const [columnVisibility, setColumnVisibility] = useState(
    defaultVisibleColumns,
  );

  //@ts-ignore
  const getColumnWidth = (rows, accessor, headerText) => {
    const maxWidth = 200;
    const magicSpacing = 12;
    const cellLength = Math.max(
      //@ts-ignore
      ...rows.map((row) => getCellWidth(row, accessor)),
      headerText?.length ? headerText?.length + 8 : 0,
    );
    return Math.min(maxWidth, cellLength * magicSpacing);
  };

  const rtColumns = useMemo(
    () => [
      ...columns.map((column: any, index: number) => ({
        accessorKey: column,
        accessorFn: (row: any) => {
          const indexLabel = row.hasOwnProperty("index")
            ? "index"
            : row.hasOwnProperty("Index")
            ? "Index"
            : columns[0];
          const indexValue = indexLabel ? row[indexLabel] : null;
          const value = row[column];
          const only_numbers =
            value?.toString()?.split(".")?.[0]?.replace(/[^0-9]/g, "") ?? "";
          const probablyDate =
            only_numbers?.length >= 4 &&
            (includesDateNames(column) ||
              column.toLowerCase() === "index" ||
              (indexValue &&
                typeof indexValue === "string" &&
                (indexValue.toLowerCase().includes("date") ||
                  indexValue.toLowerCase().includes("time") ||
                  indexValue.toLowerCase().includes("timestamp") ||
                  indexValue.toLowerCase().includes("year") ||
                  indexValue.toLowerCase().includes("month") ||
                  indexValue.toLowerCase().includes("week") ||
                  indexValue.toLowerCase().includes("hour") ||
                  indexValue.toLowerCase().includes("minute"))));

          if (
            probablyDate &&
            value?.length === 4 &&
            isoYearRegex.test(value?.toString())
          )
            return value;

          if (probablyDate) {
            if (typeof value === "number") return value;
            return new Date(value).getTime();
          }
          return value;
        },
        id: column,
        header: column,
        size: getColumnWidth(data, column, column),
        footer: column,
        cell: ({ row }: any) => {
          const indexLabel = row.original.hasOwnProperty("index")
            ? "index"
            : row.original.hasOwnProperty("Index")
            ? "Index"
            : columns[0];
          const indexValue = indexLabel ? row.original[indexLabel] : null;
          const value = row.original[column];
          const valueType = typeof value;
          const only_numbers =
            value?.toString()?.split(".")?.[0]?.replace(/[^0-9]/g, "") ?? "";
          const probablyDate =
            only_numbers?.length >= 4 &&
            (includesDateNames(column) ||
              column.toLowerCase() === "index" ||
              (indexValue &&
                typeof indexValue === "string" &&
                (indexValue.toLowerCase().includes("date") ||
                  indexValue.toLowerCase().includes("time") ||
                  indexValue.toLowerCase().includes("timestamp") ||
                  indexValue.toLowerCase().includes("year"))));

          const probablyLink =
            valueType === "string" && value.startsWith("http");

          if (probablyLink) {
            return (
              <a
                className="_hyper-link"
                href={value}
                target="_blank"
                rel="noreferrer"
              >
                {value?.length > 25 ? `${value.substring(0, 25)}...` : value}
              </a>
            );
          }

          if (
            probablyDate &&
            value?.length === 4 &&
            isoYearRegex.test(value?.toString())
          ) {
            return <p>{value}</p>;
          }
          if (probablyDate && !isNaN(new Date(value).getTime())) {
            if (typeof value === "string") {
              const date = value.split("T")[0];
              const time = value.split("T")[1]?.split(".")[0];
              if (time === "00:00:00") {
                return <p>{date}</p>;
              }
              return (
                <p>
                  {date} {time}
                </p>
              );
            }
            try {
              const date = new Date(value);
              let dateFormatted = "";
              if (
                date.getUTCHours() === 0 &&
                date.getUTCMinutes() === 0 &&
                date.getUTCSeconds() === 0 &&
                date.getMilliseconds() === 0
              ) {
                dateFormatted = date.toISOString().split("T")[0];
              } else {
                dateFormatted = date.toISOString();
                dateFormatted = `${dateFormatted.split("T")[0]} ${
                  dateFormatted.split("T")[1].split(".")[0]
                }`;
              }

              return <p>{dateFormatted}</p>;
            } catch (e) {
              return <p>{value}</p>;
            }
          }
          if (
            valueType === "number" ||
            magnitudeRegex.test(value?.toString())
          ) {
            let valueFormatted = formatNumberMagnitude(value, column);
            const valueFormattedNoMagnitude = Number(
              formatNumberNoMagnitude(value),
            );

            if (
              typeof indexValue === "string" &&
              includesPriceNames(indexValue)
            ) {
              valueFormatted = Number(formatNumberNoMagnitude(value));
              const maxFixed = valueFormatted < 2 ? 4 : 2;
              valueFormatted = valueFormatted.toLocaleString("en-US", {
                maximumFractionDigits: maxFixed,
                minimumFractionDigits: 2,
              });
            }

            return (
              <p
                className={clsx("whitespace-nowrap", {
                  "text-black dark:text-white": !colors,
                  "text-[#16A34A]": valueFormattedNoMagnitude > 0 && colors,
                  "text-[#F87171]": valueFormattedNoMagnitude < 0 && colors,
                  "text-[#404040]": valueFormattedNoMagnitude === 0 && colors,
                })}
                title={formatNumber(value).toString() ?? ""}
              >
                {valueFormattedNoMagnitude !== 0
                  ? valueFormattedNoMagnitude > 0
                    ? `${valueFormatted}`
                    : `${valueFormatted}`
                  : valueFormatted}
              </p>
            );
          } else if (valueType === "string") {
            return <div dangerouslySetInnerHTML={{ __html: xss(value) }} />;
          }
          return <p>{value}</p>;
        },
      })),
    ],
    [advanced, colors],
  );

  const [lockFirstColumn, setLockFirstColumn] = useState(false);

  const [columnOrder, setColumnOrder] = useState(
    rtColumns.map((column) => column.id as string),
  );

  const resetOrder = () =>
    setColumnOrder(columns.map((column) => column.id as string));

  const needsReorder = useMemo(() => {
    const currentOrder = columnOrder.map((columnId) => columnId);
    const defaultOrder = rtColumns.map((column) => column.id as string);
    return !isEqual(currentOrder, defaultOrder);
  }, [columnOrder, rtColumns]);

  const table = useReactTable({
    data,
    columns: rtColumns,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    columnResizeMode: "onChange",
    onColumnVisibilityChange: setColumnVisibility,
    onColumnOrderChange: setColumnOrder,
    onSortingChange: setSorting,
    onGlobalFilterChange: setGlobalFilter,
    globalFilterFn: fuzzyFilter,
    state: {
      sorting,
      globalFilter,
      columnOrder,
      columnVisibility,
    },
    initialState: {
      pagination: {
        pageIndex: 0,
        pageSize:
          typeof currentPage === "string"
            ? currentPage.includes("All")
              ? data?.length
              : parseInt(currentPage)
            : currentPage,
      },
    },
  });

  const tableContainerRef = useRef<HTMLDivElement>(null);
  const { rows } = table.getRowModel();
  const visibleColumns = table.getVisibleFlatColumns();

  const [downloadFinishedDialogOpen, setDownloadFinishedDialogOpen] =
    useState(false);

  useEffect(() => {
    if (downloadFinished) {
      setDownloadFinished(false);
      setDownloadFinishedDialogOpen(true);
    }
  }, [downloadFinished]);

  return (
    <>
      <Toast
        toast={{
          id: "max-columns",
          title: "Max 12 columns are visible by default",
          description:
            "You can change this by clicking on advanced and then top right 'Filter' button",
          status: "info",
        }}
        open={open}
        setOpen={setOpen}
      />
      <DownloadFinishedDialog
        open={downloadFinishedDialogOpen}
        close={() => setDownloadFinishedDialogOpen(false)}
      />

      <div
        ref={tableContainerRef}
        className={clsx("overflow-x-hidden h-screen")}
      >
        <div className="relative p-4" id="table">
          <div className="absolute -inset-0.5 bg-gradient-to-r rounded-md blur-md from-[#072e49]/30 via-[#0d345f]/30 to-[#0d3362]/30" />
          <div
            className={
              "border border-grey-500/60 dark:border-grey-200/60 bg-white dark:bg-grey-900 rounded overflow-hidden relative z-20"
            }
          >
            <div
              className="_header relative gap-4 py-2 text-center text-xs flex items-center justify-between px-4 text-white"
              style={{
                fontSize: `${Number(fontSize) * 90}%`,
              }}
            >
              <div className="w-1/3">
                <svg
                  xmlns="http://www.w3.org/2000/svg"
                  width="64"
                  height="40"
                  fill="none"
                  viewBox="0 0 64 40"
                >
                  <path
                    fill="#fff"
                    d="M61.283 3.965H33.608v27.757h25.699V19.826H37.561v-3.965H63.26V3.965h-1.977zM39.538 23.792h15.815v3.965H37.561v-3.965h1.977zM59.306 9.913v1.983H37.561V7.931h21.745v1.982zM33.606 0h-3.954v3.965H33.606V0zM25.7 3.966H0V15.86h25.7v3.965H3.953v11.896h25.7V3.966h-3.955zm0 21.808v1.983H7.907v-3.965h17.791v1.982zm0-15.86v1.982H3.953V7.931h21.745v1.982zM37.039 35.693v2.952l-.246-.246-.245-.245-.245-.247-.245-.246-.246-.246-.245-.245-.245-.247-.247-.246-.245-.246-.245-.246-.245-.246-.246-.246h-.49v3.936h.49v-3.198l.246.246.245.246.245.246.245.246.246.246.246.246.245.247.246.245.245.246.245.247.245.246.246.245.245.246h.245v-3.936h-.49zM44.938 37.17h-.491v-1.477h-2.944v3.937h3.93v-2.46h-.495zm-2.944-.246v-.739h1.962v.984h-1.962v-.245zm2.944.984v1.23h-2.944V37.66h2.944v.247zM52.835 37.17h-.49v-1.477h-2.946v3.937h3.925v-2.46h-.489zm-2.944-.246v-.739h1.963v.984h-1.965l.002-.245zm2.944.984v1.23H49.89V37.66h2.946v.247zM29.174 35.693H25.739v3.936H29.663v-.491H26.229v-.984h2.943v-.493H26.229v-1.476h3.434v-.492h-.489zM13.37 35.693H9.934v3.937h3.925v-3.937h-.49zm0 .738v2.709h-2.945v-2.955h2.943l.001.246zM21.276 35.693h-3.435v3.937h.491v-1.476h3.434v-2.461h-.49zm0 .738v1.23h-2.944v-1.476h2.944v.246z"
                  />
                </svg>
              </div>
              <p className="font-bold w-1/3 flex flex-col gap-0.5 items-center">
                {title}
                {/* {source && (
                  <span className="font-normal text-[10px]">{`[${source}]`}</span>
                )} */}
              </p>
              <p className="w-1/3 text-right text-xs">
                {new Intl.DateTimeFormat("en-GB", {
                  dateStyle: "full",
                  timeStyle: "long",
                })
                  .format(date)
                  .replace(/:\d\d /, " ")}
                <br />
                <span className="text-grey-400">{cmd}</span>
              </p>
              {/* {source && typeof source === "string" && source.includes("*") && (
                <p className="text-[8px] absolute bottom-0 right-4">
                  *not affiliated
                </p>
              )} */}
            </div>
            <div className="overflow-auto max-h-[calc(100vh-170px)] smh:max-h-[calc(100vh-95px)]">
              <table className="text-sm relative">
                <thead
                  className="sticky top-0 bg-white dark:bg-grey-900"
                  style={{
                    fontSize: `${Number(fontSize) * 100}%`,
                  }}
                >
                  {table.getHeaderGroups().map((headerGroup, idx) => (
                    <tr key={headerGroup.id}>
                      {headerGroup.headers.map((header, idx2) => {
                        return (
                          <DraggableColumnHeader
                            setLockFirstColumn={setLockFirstColumn}
                            lockFirstColumn={lockFirstColumn}
                            idx={idx2}
                            advanced={advanced}
                            key={header.id}
                            header={header}
                            table={table}
                          />
                        );
                      })}
                    </tr>
                  ))}
                </thead>
                <tbody>
                  {table.getRowModel().rows.map((row, idx) => {
                    return (
                      <tr
                        key={row.id}
                        className="!h-[64px] border-b border-grey-400"
                        style={{
                          fontSize: `${Number(fontSize) * 100}%`,
                        }}
                      >
                        {row.getVisibleCells().map((cell, idx2) => {
                          return (
                            <td
                              key={cell.id}
                              className={clsx(
                                "whitespace-normal p-4 text-black dark:text-white",
                                {
                                  "bg-white dark:bg-grey-850": idx % 2 === 0,
                                  "bg-grey-100 dark:bg-[#202020]":
                                    idx % 2 === 1,
                                  "sticky left-0 z-10":
                                    idx2 === 0 && lockFirstColumn,
                                },
                              )}
                              style={{
                                width: cell.column.getSize(),
                              }}
                            >
                              {flexRender(
                                cell.column.columnDef.cell,
                                cell.getContext(),
                              )}
                            </td>
                          );
                        })}
                      </tr>
                    );
                  })}
                </tbody>
                {rows?.length > 30 && visibleColumns?.length > 4 && (
                  <tfoot>
                    {table.getFooterGroups().map((footerGroup) => (
                      <tr key={footerGroup.id}>
                        {footerGroup.headers.map((header) => (
                          <th
                            key={header.id}
                            colSpan={header.colSpan}
                            className="text-grey-500 bg-grey-100 dark:bg-grey-850 font-normal text-left text-sm h-10 p-4"
                            style={{
                              width: header.getSize(),
                              fontSize: `${Number(fontSize) * 100}%`,
                            }}
                          >
                            {header.isPlaceholder
                              ? null
                              : flexRender(
                                  header.column.columnDef.footer,
                                  header.getContext(),
                                )}
                          </th>
                        ))}
                      </tr>
                    ))}
                  </tfoot>
                )}
              </table>
            </div>
          </div>
        </div>
        <div className="smh:hidden flex max-h-[68px] overflow-x-auto bg-white/70 dark:bg-grey-900/70 backdrop-filter backdrop-blur z-20 bottom-0 left-0 w-full gap-10 justify-between py-4 px-4 text-sm">
          <div className="flex items-center gap-10">
            <DialogPrimitive.Root>
              <DialogPrimitive.Trigger className="_btn">
                Settings
              </DialogPrimitive.Trigger>
              <DialogPrimitive.Portal>
                <DialogPrimitive.Overlay className="_modal-overlay" />
                <DialogPrimitive.Content className="_modal">
                  <DialogPrimitive.Close className="absolute top-[40px] right-[46px] text-grey-200 hover:text-white rounded-[4px] focus:outline focus:outline-2 focus:outline-grey-500">
                    <CloseIcon className="w-6 h-6" />
                  </DialogPrimitive.Close>
                  <DialogPrimitive.Title className="uppercase font-bold tracking-widest">
                    Settings
                  </DialogPrimitive.Title>
                  <div className="grid grid-cols-2 gap-2 mt-10 text-sm">
                    {needsReorder && (
                      <button onClick={() => resetOrder()} className="_btn h-9">
                        Reset Order
                      </button>
                    )}
                    <Select
                      labelType="row"
                      value={!darkMode ? "dark" : "light"}
                      onChange={(value) => {
                        toggleDarkMode(value !== "dark");
                      }}
                      label="Theme"
                      placeholder="Select theme"
                      groups={[
                        {
                          label: "Theme",
                          items: [
                            {
                              label: "Dark",
                              value: "dark",
                            },
                            {
                              label: "Light",
                              value: "light",
                            },
                          ],
                        },
                      ]}
                    />
                    <Select
                      labelType="row"
                      value={type}
                      onChange={(value) => {
                        setType(value);
                      }}
                      label="Export type"
                      placeholder="Select export type"
                      groups={[
                        {
                          label: "Export type",
                          items: EXPORT_TYPES.map((type) => ({
                            label: type,
                            value: type,
                          })),
                        },
                      ]}
                    />
                    <Select
                      labelType="row"
                      value={fontSize}
                      onChange={setFontSize}
                      label="Font size"
                      placeholder="Select font size"
                      groups={[
                        {
                          label: "Font size",
                          items: [
                            {
                              label: "50%",
                              value: "0.5",
                            },
                            {
                              label: "75%",
                              value: "0.75",
                            },
                            {
                              label: "100%",
                              value: "1",
                            },
                            {
                              label: "125%",
                              value: "1.25",
                            },
                            {
                              label: "150%",
                              value: "1.5",
                            },
                            {
                              label: "175%",
                              value: "1.75",
                            },
                            {
                              label: "200%",
                              value: "2",
                            },
                          ],
                        },
                      ]}
                    />
                    <FilterColumns table={table} label="Filter" />
                    <div className="flex gap-2 items-center">
                      <Select
                        labelType="row"
                        value={advanced ? "advanced" : "simple"}
                        onChange={(value) => {
                          setAdvanced(value === "advanced");
                        }}
                        label="Type"
                        placeholder="Select type"
                        groups={[
                          {
                            label: "Type",
                            items: [
                              {
                                label: "Simple",
                                value: "simple",
                              },
                              {
                                label: "Advanced",
                                value: "advanced",
                              },
                            ],
                          },
                        ]}
                      />
                    </div>
                    <div className="flex gap-2 items-center">
                      <label htmlFor="colors">Colors</label>
                      <input
                        id="colors"
                        type="checkbox"
                        checked={colors}
                        onChange={() => setColors(!colors)}
                      />
                    </div>
                  </div>
                </DialogPrimitive.Content>
              </DialogPrimitive.Portal>
            </DialogPrimitive.Root>
            <FilterColumns onlyIconTrigger table={table} label="" />
          </div>
          <Pagination
            currentPage={currentPage}
            setCurrentPage={setCurrentPage}
            table={table}
          />
          <Export
            setType={setType}
            type={type}
            columns={columns}
            data={data}
            downloadFinished={setDownloadFinished}
          />
        </div>
      </div>
    </>
  );
}
