import React, { Fragment, ReactChild, ReactNode, useEffect } from "react";
import { Button, Modal, Checkbox } from "antd";
import Papa from "papaparse";
import difference from "lodash/difference";
import union from "lodash/union";
import get from "lodash/get";
import set from "lodash/set";
import { ColumnsType, ColumnGroupType, ColumnType } from "antd/lib/table";
import { ButtonProps } from "antd/lib/button";

export interface ITableExportFields {
  [dataIndex: string]:
    | string
    | {
        header: string;
        formatter?: (fieldValue: any, record: any, index: number) => string;
      };
}

export interface IExportFieldButtonProps {
  /** Ant table's dataSource */
  dataSource?: any[];
  /** Ant table's columns */
  columns?: ColumnsType<any>;
  /** File name to use when exporting to csv */
  fileName?: string;
  /** Customize csv file like column header names, fields to include/exclude. More on this below. */
  fields?: ITableExportFields;
  /** Disables export button. Useful when you want to disable when dataSource is loading. */
  disabled?: boolean;
  /** Any of Ant Button component props as object. */
  btnProps?: ButtonProps;
  /** Can be used to change text in button. */
  children?: ReactChild | ReactNode;
  /** Shows a modal to pick which columns to include exported file. */
  showColumnPicker?: boolean;
}

type ColumnWithDataIndex = (ColumnGroupType<any> | ColumnType<any>) & {
  dataIndex?: string | string[];
};

const getFieldsFromColumns = (
  columns: ColumnsType<any>
): ITableExportFields => {
  const fields = {};
  columns?.forEach((column: ColumnWithDataIndex) => {
    const { title, key, dataIndex } = column;
    const fieldName =
      (Array.isArray(dataIndex) ? dataIndex.join(".") : dataIndex) ?? key;
    if (fieldName) {
      set(fields, fieldName, title);
    }
  });

  return fields;
};

const cleanupDataSource = (dataSource, exportFieldNames, selectedFields) => {
  if (!dataSource || dataSource.length === 0) {
    return { data: [], fields: [] };
  }

  const newData = [...dataSource];
  const fields = selectedFields.map(fieldName => {
    const fieldValue = get(exportFieldNames, fieldName);
    if (typeof fieldValue === "string") {
      return fieldValue;
    }
    return fieldValue.header || "";
  });

  const data = newData.map((record, rowIndex) => {
    return selectedFields.map(fieldName => {
      const fieldValue = get(exportFieldNames, fieldName);
      const recordValue = get(record, fieldName);
      if (typeof fieldValue === "string") {
        return recordValue;
      }
      return fieldValue?.formatter(recordValue, record, rowIndex) || null;
    });
  });

  return [fields, ...data];
};

export const ExportTableButton: React.FC<IExportFieldButtonProps> = props => {
  const {
    dataSource = [],
    fileName,
    fields,
    disabled,
    btnProps,
    columns = [],
    showColumnPicker = false,
  } = props;

  const [showModal, setShowModal] = React.useState(false);

  const fieldsOrColumns = fields ?? getFieldsFromColumns(columns);

  const [selectedFields, setSelectedFields] = React.useState(() => {
    if (fields) {
      return Object.keys(fields);
    } else if (columns) {
      return Object.keys(getFieldsFromColumns(columns));
    }

    return [];
  });

  useEffect(() => {
    if (fields) {
      setSelectedFields(Object.keys(fields));
    } else if (columns) {
      setSelectedFields(Object.keys(getFieldsFromColumns(columns)));
    }
  }, [fields, columns]);

  const handleDownloadCSV = React.useCallback(() => {
    if (!dataSource) {
      return;
    }

    let selectedFieldsInOriginalOrder = Object.keys(fieldsOrColumns).filter(
      name => selectedFields.indexOf(name) > -1
    );

    const data = cleanupDataSource(
      dataSource,
      fieldsOrColumns,
      selectedFieldsInOriginalOrder
    );

    const csv = Papa.unparse(data, {
      greedy: true,
      header: false,
    });
    const blob = new Blob([csv]);
    const a = window.document.createElement("a");
    a.href = window.URL.createObjectURL(blob);
    a.download = `${fileName || "table"}.csv`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);

    setShowModal(false);
  }, [dataSource, fieldsOrColumns, selectedFields, fileName]);

  const handleCheckboxChange = React.useCallback(
    (key, checked) => {
      let newSelectedFields = [...selectedFields];
      if (checked) {
        newSelectedFields = union(newSelectedFields, [key]);
      } else {
        newSelectedFields = difference(newSelectedFields, [key]);
      }

      setSelectedFields(newSelectedFields);
    },
    [selectedFields]
  );

  return (
    <Fragment>
      <Button
        onClick={() =>
          showColumnPicker ? setShowModal(true) : handleDownloadCSV()
        }
        disabled={disabled}
        {...btnProps}
      >
        {props.children ?? `Export to CSV`}
      </Button>
      {showColumnPicker ? (
        <Modal
          visible={showModal}
          onOk={() => handleDownloadCSV()}
          onCancel={() => setShowModal(false)}
          width={400}
          okButtonProps={{
            disabled: selectedFields.length < 1,
            title:
              selectedFields.length < 1
                ? "Please select at least one column."
                : null,
          }}
          okText={"Export"}
          title={"Select columns to export"}
        >
          <div className="d-flex flex-column align-start">
            {Object.entries(fieldsOrColumns).map(([key, value]) => {
              return (
                <Checkbox
                  key={key}
                  style={{ padding: 0, margin: 0 }}
                  defaultChecked={true}
                  checked={selectedFields.indexOf(key) > -1}
                  onChange={e => handleCheckboxChange(key, e.target.checked)}
                >
                  {typeof value === "string" ? value : value?.header ?? ""}
                </Checkbox>
              );
            })}
          </div>
        </Modal>
      ) : null}
    </Fragment>
  );
};

export default ExportTableButton;