import { clsx } from "clsx";
import { Select as SelectPrimitive } from "radix-ui";
import * as React from "react";
import { useDropzone } from "react-dropzone";
import * as XLSX from "xlsx";
import { Button } from "..";
import { useColumnsContext } from "../../contexts";
import * as selectStyles from "../Select/styles.module.css";
import * as styles from "./styles.module.css";

enum ViewState {
  Upload = 0,
  Match = 1,
}

export type ColumnMap = {
  tableId: string;
  columns: Record<string, string>;
  extra: Record<string, any>;
};

type Classes = {
  secondaryButton?: string;
  primaryButton?: string;
};

export default function DataImporter({
  className,
  table = {
    tableId: "",
    columns: {},
    extra: {},
  },
  conflictAction = "update",
  onClose,
  classes = {},
  ...props
}: {
  className?: string;
  onClose: () => void;
  table: ColumnMap;
  conflictAction: "ignore" | "update";
  classes?: Classes;
}) {
  const [step, setStep] = React.useState(ViewState.Upload);
  const [sheet, setSheet] = React.useState<XLSX.Sheet | null>(null);
  const renderStep = React.useCallback(
    (step: ViewState) => {
      switch (step) {
        case ViewState.Upload:
          return (
            <Upload
              setSheet={setSheet}
              setStep={setStep}
              columnNames={table?.columns}
              classes={classes}
            />
          );
        case ViewState.Match:
          return (
            <Match
              sheet={sheet!}
              setStep={setStep}
              tableId={table?.tableId}
              columnNames={table?.columns}
              extra={table?.extra}
              conflictAction={conflictAction}
              classes={classes}
              onClose={onClose}
            />
          );
        default:
          return null;
      }
    },
    [sheet, table?.tableId, table?.columns, onClose],
  );

  return (
    <div className={clsx(styles.container, className)} {...props}>
      {renderStep(step)}
    </div>
  );
}

function Upload({
  setStep,
  setSheet,
  columnNames,
  classes = {},
}: {
  setStep: (step: ViewState) => void;
  setSheet: (sheet: XLSX.Sheet) => void;
  columnNames: Record<string, string>;
  classes?: Classes;
}) {
  const onDrop = React.useCallback((acceptedFiles: File[]) => {
    if (acceptedFiles.length === 0) {
      return;
    }
    const reader = new FileReader();
    reader.onabort = () => console.log("file reading was aborted");
    reader.onerror = () => console.log("file reading has failed");
    reader.onload = () => {
      // Do whatever you want with the file contents
      const workbook = XLSX.read(reader.result, {
        type: "buffer",
        cellDates: false,
        cellNF: false,
        cellText: false,
        dateNF: "yyyy-mm-dd",
        raw: true,
      });
      const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
      for (const addr of Object.keys(firstSheet)) {
        const cell = firstSheet[addr];
        if (cell && cell.t === "n" && XLSX.SSF.is_date(cell.z || "")) {
          // ✔ real date
          cell.t = "d";
          const dateObj = XLSX.SSF.parse_date_code(cell.v);
          // Convert to actual JavaScript Date
          cell.v = new Date(
            dateObj.y,
            dateObj.m - 1,
            dateObj.d,
            dateObj.H,
            dateObj.M,
            dateObj.S,
          );
        }
      }
      setSheet(firstSheet);
      setStep(ViewState.Match);
    };
    const file = acceptedFiles[0];
    reader.readAsArrayBuffer(file);
  }, []);
  const { getRootProps, getInputProps, open } = useDropzone({
    accept: {
      "": [".csv", ".tsv", ".xls", ".xlsx", ".xml"],
    },
    useFsAccessApi: false,
    multiple: false,
    onDrop,
  });
  return (
    <section className={styles.section} {...getRootProps()}>
      <div className={styles.backgroundTable}>
        <div className={styles.table}>
          {Object.values(columnNames ?? {}).map((name) => {
            return (
              <div className={styles.row} key={name}>
                <div className={styles.th}>
                  <p className={styles.thd}>{name}</p>
                </div>

                {Array.from({ length: 5 }).map((_, idx) => {
                  return (
                    <div className={styles.td} key={idx}>
                      <div className={styles.tdd}>{` `}</div>
                    </div>
                  );
                })}
              </div>
            );
          })}
        </div>
      </div>
      <div className={styles.upload}>
        <h1>Drag and drop a file here</h1>
        <p>You can upload: .csv, .tsv, .xls, .xlsx, .xml</p>
        <input className={styles.fileInput} {...getInputProps()} />
      </div>
    </section>
  );
}

function Match({
  tableId,
  sheet,
  setStep,
  columnNames = {},
  conflictAction = "update",
  extra = {},
  onClose,
  classes = {},
}: {
  tableId: string;
  sheet: XLSX.WorkSheet;
  setStep: (step: ViewState) => void;
  columnNames: Record<string, string>;
  conflictAction: "ignore" | "update";
  extra: Record<string, any>;
  onClose?: () => void;
  classes?: Classes;
}) {
  const columns = useColumnsContext();
  const [isLoading, setIsLoading] = React.useState(false);
  const [header, ...data] = React.useMemo(
    () =>
      XLSX.utils.sheet_to_json(sheet, {
        header: 1,
        blankrows: false,
      }) as any[][],
    [sheet],
  );
  const [columnToId, setColumnToId] = React.useState<Record<string, string>>(
    () => {
      const namesByKey = Object.entries(columnNames).reduce(
        (acc, [key, alias]) => {
          acc[alias.toLowerCase()] = key;
          return acc;
        },
        {},
      );
      return header.reduce((acc, header) => {
        const key = namesByKey[header.toLowerCase()];
        if (key) {
          acc[header] = key;
        } else if (columnNames[header]) {
          acc[header] = header;
        }
        return acc;
      }, {});
    },
  );
  const columnToIndex = React.useMemo(() => {
    const map: Record<string, number> = {};
    header.forEach((col, idx) => {
      map[col] = idx;
    });
    return map;
  }, [header]);
  return (
    <div className={styles.main}>
      <section className={styles.match}>
        <div className={styles.matchInner}>
          <div className={styles.matchHeader}>
            <p>Your column name</p>
            <p>Matches to</p>
          </div>
          {header.map((col, idx) => {
            return (
              <MatchRow
                key={col}
                name={col}
                value={columnToId[col] ?? "__empty__"}
                data={data.slice(0, 5).map((d) => d[idx])}
                onValueChange={(value) => {
                  setColumnToId((t) => ({ ...t, [col]: value ?? "__empty__" }));
                }}
                columnNames={columnNames}
              />
            );
          })}
        </div>
      </section>
      <div className={styles.footer}>
        <Button
          data-component="Button$Brevity"
          className={clsx(
            "__login-button",
            "__login-button-secondary",
            classes.secondaryButton,
          )}
          onClick={() => {
            setStep(ViewState.Upload);
          }}
          value="Back"
        />
        <Button
          data-component="Button$Brevity"
          className={clsx(
            "__dataImporter-button",
            "__dataImporter-button-primary",
            classes.primaryButton,
            styles.next,
          )}
          // isLoading={isLoading}
          onClick={async () => {
            setIsLoading(true);
            const parser = await import("any-date-parser");
            const url = `/api/gql/bulk/csv/${tableId}?action=${conflictAction}`;
            const idToColumn = Object.entries(columnToId).reduce(
              (acc, [v, k]) => {
                if (v === "__empty__") {
                  return acc;
                }
                acc[k] = v;
                return acc;
              },
              {},
            );
            const entries = Object.entries(columnNames ?? {});
            const extraColumns = Object.keys(extra ?? {});
            const extraValues = Object.values(extra ?? {});
            const filteredEntries = entries.filter(([id]) =>
              Object.hasOwn(idToColumn, id),
            );
            const newData = data.map((row) => {
              const newRow = filteredEntries.map(([id]) => {
                const column = columns[`${tableId}:${id}`];
                const oldColumn = idToColumn[id];
                const idx = columnToIndex[oldColumn] ?? -1;
                const value = row[idx];
                if (
                  column?.type === "date" &&
                  value &&
                  typeof value === "string"
                ) {
                  const maybeDate = parser.fromString(value);
                  if (maybeDate.isValid()) {
                    return maybeDate.toISOString();
                  }
                }
                return value;
              });
              return newRow.concat(extraValues);
            });
            const headerRow = filteredEntries
              .map(([id]) => id)
              .concat(extraColumns)
              .join(",");
            // batch newData into chunks of 1000
            for (let i = 0; i < newData.length; i += 1000) {
              const chunk = newData.slice(i, i + 1000);
              const newSheet = XLSX.utils.aoa_to_sheet(chunk, {});
              const csv = XLSX.utils.sheet_to_csv(newSheet);
              await fetch(url, {
                method: "POST",
                headers: {
                  "Content-Type": "text/csv",
                },
                body: headerRow + "\n" + csv,
              });
            }
            setIsLoading(false);
            onClose?.();
          }}
        >
          {isLoading ? "Uploading..." : "Confirm matches and upload"}
        </Button>
      </div>
    </div>
  );
}

function MatchRow({
  name,
  data,
  onValueChange,
  value,
  columnNames = {},
}: {
  name: string;
  data: string[];
  value: string;
  onValueChange: (value: string) => void;
  columnNames: Record<string, string>;
}) {
  const [isOpen, setIsOpen] = React.useState(false);
  return (
    <section>
      <div className={styles.matchRow}>
        <div
          className={styles.matchColumnLeft}
          role="button"
          onClick={() => setIsOpen((t) => !t)}
        >
          <button className={styles.matchButton}>
            <svg
              width="24"
              height="24"
              viewBox="0 0 24 24"
              xmlns="http://www.w3.org/2000/svg"
              className={clsx(styles.icon, isOpen ? styles.iconOpen : "")}
            >
              <path
                d="M8.87 6.558a.429.429 0 0 1 .732-.303l5.268 5.267a.84.84 0 0 1 0 1.214l-5.268 5.268a.429.429 0 0 1-.731-.303V6.558z"
                fill="currentColor"
                fillRule="evenodd"
              ></path>
            </svg>
          </button>
          <h1>{name}</h1>
        </div>
        <div className={styles.matchColumnRight}>
          <SelectPrimitive.Root value={value} onValueChange={onValueChange}>
            <SelectPrimitive.Trigger
              data-component="Select$Brevity"
              className={clsx(selectStyles.trigger, styles.select)}
              onPointerDownCapture={(e) => {
                if (
                  process.env.PREVIEW &&
                  document.body.classList.contains("__design")
                ) {
                  e.preventDefault();
                }
              }}
            >
              <SelectPrimitive.Value />
              <SelectPrimitive.Icon asChild>
                <svg
                  className={selectStyles.svg}
                  viewBox="0 0 24 24"
                  role="presentation"
                  focusable="false"
                  aria-hidden="true"
                >
                  <path
                    fill="currentColor"
                    d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"
                  />
                </svg>
              </SelectPrimitive.Icon>
            </SelectPrimitive.Trigger>
            <SelectPrimitive.Portal>
              <SelectPrimitive.Content
                className={clsx(selectStyles.content, selectStyles.popper)}
                position="popper"
              >
                <SelectPrimitive.Viewport
                  className={clsx(selectStyles.viewport, selectStyles.popper)}
                >
                  <SelectPrimitive.Item
                    className={selectStyles.item}
                    value={"__empty__"}
                  >
                    <SelectPrimitive.ItemIndicator>
                      <svg
                        className={selectStyles.indicator}
                        xmlns="http://www.w3.org/2000/svg"
                        width="24"
                        height="24"
                        viewBox="0 -960 960 960"
                      >
                        <path d="M382-354l339-339q12-12 28.5-12t28.5 12q12 12 12 28.5T778-636L410-268q-12 12-28 12t-28-12L182-440q-12-12-11.5-28.5T183-497q12-12 28.5-12t28.5 12l142 143z"></path>
                      </svg>
                    </SelectPrimitive.ItemIndicator>
                    <SelectPrimitive.ItemText>Ignore</SelectPrimitive.ItemText>
                  </SelectPrimitive.Item>
                  {Object.entries(columnNames ?? {})?.map(
                    ([id, alias], idx) => {
                      return (
                        <SelectPrimitive.Item
                          key={id}
                          className={selectStyles.item}
                          value={id}
                        >
                          <SelectPrimitive.ItemIndicator>
                            <svg
                              className={selectStyles.indicator}
                              xmlns="http://www.w3.org/2000/svg"
                              width="24"
                              height="24"
                              viewBox="0 -960 960 960"
                            >
                              <path d="M382-354l339-339q12-12 28.5-12t28.5 12q12 12 12 28.5T778-636L410-268q-12 12-28 12t-28-12L182-440q-12-12-11.5-28.5T183-497q12-12 28.5-12t28.5 12l142 143z"></path>
                            </svg>
                          </SelectPrimitive.ItemIndicator>
                          <SelectPrimitive.ItemText>
                            {alias}
                          </SelectPrimitive.ItemText>
                        </SelectPrimitive.Item>
                      );
                    },
                  )}
                </SelectPrimitive.Viewport>
              </SelectPrimitive.Content>
            </SelectPrimitive.Portal>
          </SelectPrimitive.Root>
        </div>
      </div>
      {isOpen ? (
        <div className={styles.matchDropdown}>
          <div className={styles.matchDropdownInner}>
            {data.map((item) => {
              return (
                <div key={item} className={styles.matchDropdownItem}>
                  {itemToString(item)}
                </div>
              );
            })}
          </div>
        </div>
      ) : null}
    </section>
  );
}

function itemToString(item: string | Date | null | undefined) {
  if (item === null || item === undefined) {
    return "null";
  }
  if (item instanceof Date) {
    return item.toLocaleString();
  }
  if (typeof item === "object") {
    return JSON.stringify(item);
  }
  return String(item);
}

function convertDates(item: string) {
  if (item && typeof item === "string") {
    const date = new Date(item);
    if (isNaN(date.getTime())) {
      return item;
    }
    return date.toLocaleString();
  }
  return item;
}
