import { CreateProjectInputs, PaymentInputs } from "@/app/api/projects/actions";
import { TaskGroup } from "@/app/api/tasks";
import { Add, Check, Close, Delete } from "@mui/icons-material";
import {
  Stack,
  Typography,
  Grid,
  FormControl,
  Box,
  Button,
} from "@mui/material";
import {
  GridColDef,
  GridActionsCellItem,
  GridToolbarContainer,
  GridRowModesModel,
  GridRowModes,
  FooterPropsOverrides,
  GridRowId,
  GridEventListener,
  useGridApiRef,
  GridRowModel,
  GridRenderEditCellParams,
  GridCellParams,
} from "@mui/x-data-grid";
import { LocalizationProvider, DatePicker } from "@mui/x-date-pickers";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import dayjs from "dayjs";
import "dayjs/locale/zh-hk";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import StyledDataGrid from "../StyledDataGrid";
import {
  INPUT_DATE_FORMAT,
  OUTPUT_DATE_FORMAT,
  moneyFormatter,
  truncateMoney,
} from "@/app/utils/formatUtil";
import isDate from "lodash/isDate";
import BulkAddPaymentModal from "./BulkAddPaymentModal";
import { deleteDialog } from "../Swal/CustomAlerts";

interface Props {
  taskGroupId: TaskGroup["id"];
}

type PaymentRowError = keyof PaymentInputs;

type PaymentRow = Partial<
  PaymentInputs & { _isNew: boolean; _errors: Set<string> }
>;

class ProcessRowUpdateError extends Error {
  public readonly rowId: GridRowId;
  public readonly errors: Set<PaymentRowError> | undefined;
  constructor(
    rowId: GridRowId,
    message?: string,
    errors?: Set<PaymentRowError>,
  ) {
    super(message);
    this.rowId = rowId;
    this.errors = errors;

    Object.setPrototypeOf(this, ProcessRowUpdateError.prototype);
  }
}

const MilestoneSection: React.FC<Props> = ({ taskGroupId }) => {
  const {
    t,
    i18n: { language },
  } = useTranslation();
  const { getValues, setValue, formState } =
    useFormContext<CreateProjectInputs>();
  const [payments, setPayments] = useState<PaymentRow[]>(
    getValues("milestones")[taskGroupId]?.payments || [],
  );
  const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({});
  // Change the payments table depending on the TaskGroupId
  useEffect(() => {
    setPayments(getValues("milestones")[taskGroupId]?.payments || []);
    setRowModesModel({});
  }, [getValues, taskGroupId]);

  const apiRef = useGridApiRef();
  const addRow = useCallback(() => {
    // const id = Date.now();
    const minId = Math.min(...payments.map((payment) => payment.id!));
    const id = minId >= 0 ? -1 : minId - 1;
    setPayments((p) => [...p, { id, _isNew: true }]);
    setRowModesModel((model) => ({
      ...model,
      [id]: { mode: GridRowModes.Edit, fieldToFocus: "description" },
    }));
  }, [payments]);

  const validateRow = useCallback((row: PaymentRow) => {
    const errors = new Set<PaymentRowError>();
    if (!row.description) {
      errors.add("description");
    } else if (!(isDate(row.date) && row.date.getTime())) {
      errors.add("date");
    } else if (!(row.amount && row.amount >= 0)) {
      errors.add("amount");
    }

    return errors;
  }, []);

  const handleCancel = useCallback(
    (id: GridRowId) => () => {
      setRowModesModel((model) => ({
        ...model,
        [id]: { mode: GridRowModes.View, ignoreModifications: true },
      }));
      const editedRow = payments.find((payment) => payment.id === id);
      if (editedRow?._isNew) {
        setPayments((ps) => ps.filter((p) => p.id !== id));
      }
    },
    [payments],
  );

  const handleDelete = useCallback(
    (id: GridRowId) => () => {
      setPayments((ps) => ps.filter((p) => p.id !== id));
    },
    [],
  );

  const handleSave = useCallback(
    (id: GridRowId) => () => {
      setRowModesModel((model) => ({
        ...model,
        [id]: { mode: GridRowModes.View },
      }));
    },
    [],
  );

  const processRowUpdate = useCallback(
    (newRow: GridRowModel<PaymentRow>) => {
      const errors = validateRow(newRow);
      if (errors.size > 0) {
        throw new ProcessRowUpdateError(newRow.id!, "validation error", errors);
      }

      // eslint-disable-next-line @typescript-eslint/no-unused-vars
      const { _isNew, _errors, ...updatedRow } = newRow;
      setPayments((ps) =>
        ps.map((p) =>
          p.id === updatedRow.id
            ? { ...updatedRow, amount: truncateMoney(updatedRow.amount) }
            : p,
        ),
      );
      return updatedRow;
    },
    [validateRow],
  );

  const onProcessRowUpdateError = useCallback(
    (updateError: ProcessRowUpdateError) => {
      const errors = updateError.errors;
      const rowId = updateError.rowId;

      apiRef.current.updateRows([
        {
          id: rowId,
          _errors: errors,
        },
      ]);
    },
    [apiRef],
  );

  const columns = useMemo<GridColDef[]>(
    () => [
      {
        type: "actions",
        field: "actions",
        headerName: t("Actions"),
        getActions: ({ id }) => {
          if (rowModesModel[id]?.mode === GridRowModes.Edit) {
            return [
              <GridActionsCellItem
                key="accpet-action"
                icon={<Check />}
                label={t("Save")}
                onClick={handleSave(id)}
              />,
              <GridActionsCellItem
                key="cancel-action"
                icon={<Close />}
                label={t("Cancel")}
                onClick={handleCancel(id)}
              />,
            ];
          }

          return [
            <GridActionsCellItem
              key="delete-action"
              icon={<Delete />}
              label={t("Remove")}
              onClick={handleDelete(id)}
            />,
          ];
        },
      },
      {
        field: "description",
        headerName: t("Payment Milestone Description"),
        width: 300,
        editable: true,
      },
      {
        field: "date",
        headerName: t("Payment Milestone Date"),
        width: 200,
        type: "date",
        editable: true,
        valueFormatter(params) {
          return dayjs(params.value).format(OUTPUT_DATE_FORMAT);
        },
        renderEditCell(params: GridRenderEditCellParams<PaymentRow, Date>) {
          return (
            <Box sx={{ ".MuiFilledInput-input": { paddingY: "18px" } }}>
              <DatePicker
                format={OUTPUT_DATE_FORMAT}
                value={dayjs(params.value)}
                onChange={(value) =>
                  params.api.setEditCellValue({
                    id: params.id,
                    field: params.field,
                    value: value?.toDate(),
                  })
                }
              />
            </Box>
          );
        },
        valueGetter(params) {
          return new Date(params.value);
        },
      },
      {
        field: "amount",
        headerName: t("Payment Milestone Amount"),
        width: 300,
        editable: true,
        type: "number",
        valueGetter(params) {
          return truncateMoney(params.value);
        },
        valueFormatter(params) {
          return moneyFormatter.format(params.value);
        },
      },
    ],
    [handleCancel, handleDelete, handleSave, rowModesModel, t],
  );

  useEffect(() => {
    const milestones = getValues("milestones");
    setValue("milestones", {
      ...milestones,
      [taskGroupId]: {
        ...milestones[taskGroupId],
        payments: payments
          .filter((p) => !p._isNew && !p._errors)
          .map((p) => ({
            description: p.description!,
            id: p.id!,
            amount: p.amount!,
            date: dayjs(p.date!).format(INPUT_DATE_FORMAT),
          })),
      },
    });
  }, [getValues, payments, setValue, taskGroupId]);

  // Bulk add modal related
  const [bulkAddModalOpen, setBulkAddModalOpen] = useState(false);
  const closeBulkAddModal = useCallback(() => {
    setBulkAddModalOpen(false);
  }, []);
  const openBulkAddModal = useCallback(() => {
    setBulkAddModalOpen(true);
  }, []);
  const onBulkAddSave = useCallback(async (entries: PaymentInputs[]) => {
    setPayments((currentPayments) => [...currentPayments, ...entries]);
    setBulkAddModalOpen(false);
  }, []);
  const onBulkDelete = useCallback(() => {
    deleteDialog(() => {
      setPayments([])
    }, t)
  }, []);

  const footer = (
    <Box display="flex" gap={2} alignItems="center">
      <Button
        variant="outlined"
        startIcon={<Add />}
        onClick={addRow}
        size="small"
      >
        {t("Add Payment Milestone")}
      </Button>
      <Button
        variant="outlined"
        startIcon={<Add />}
        onClick={openBulkAddModal}
        size="small"
      >
        {t("Bulk Add Payment Milestones")}
      </Button>
      <Button
        variant="outlined"
        startIcon={<Delete />}
        color={"error"}
        onClick={onBulkDelete}
        size="small"
      >
        {t("Delete Payment Milestones")}
      </Button>
    </Box>
  );

  const startDate = getValues("milestones")[taskGroupId]?.startDate;
  const endDate = getValues("milestones")[taskGroupId]?.endDate;

  return (
    <LocalizationProvider
      dateAdapter={AdapterDayjs}
      adapterLocale={`${language}-hk`}
    >
      <Stack gap={1}>
        <Typography variant="overline" display="block" marginBlockEnd={1}>
          {t("Stage Milestones")}
        </Typography>
        {/* <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
          <Grid item xs>
            <FormControl fullWidth>
              <DatePicker
                label={t("Stage Start Date")}
                value={startDate ? dayjs(startDate) : null}
                format="YYYY/MM/DD"
                onChange={(date) => {
                  if (!date) return;
                  const milestones = getValues("milestones");
                  setValue("milestones", {
                    ...milestones,
                    [taskGroupId]: {
                      ...milestones[taskGroupId],
                      startDate: date.format(INPUT_DATE_FORMAT),
                    },
                  });
                }}
                slotProps={{
                  textField: {
                    error:
                      startDate === "Invalid Date" ||
                      new Date(startDate) > new Date(endDate) ||
                      (Boolean(formState.errors.milestones) &&
                        !Boolean(startDate)),
                  },
                }}
              />
            </FormControl>
          </Grid>
          <Grid item xs>
            <FormControl fullWidth>
              <DatePicker
                label={t("Stage End Date")}
                value={endDate ? dayjs(endDate) : null}
                format="YYYY/MM/DD"
                onChange={(date) => {
                  if (!date) return;
                  const milestones = getValues("milestones");
                  setValue("milestones", {
                    ...milestones,
                    [taskGroupId]: {
                      ...milestones[taskGroupId],
                      endDate: date.format(INPUT_DATE_FORMAT),
                    },
                  });
                }}
                slotProps={{
                  textField: {
                    error:
                      endDate === "Invalid Date" ||
                      new Date(startDate) > new Date(endDate) ||
                      (Boolean(formState.errors.milestones) &&
                        !Boolean(endDate)),
                  },
                }}
              />
            </FormControl>
          </Grid>
        </Grid> */}
        <Box
          sx={(theme) => ({
            marginBlockStart: 1,
            marginInline: -3,
            borderBottom: `1px solid ${theme.palette.divider}`,
          })}
        >
          <StyledDataGrid
            apiRef={apiRef}
            autoHeight
            sx={{
              "--DataGrid-overlayHeight": "100px",
              ".MuiDataGrid-row .MuiDataGrid-cell.hasError": {
                border: "1px solid",
                borderColor: "error.main",
              },
            }}
            disableColumnMenu
            editMode="row"
            rows={payments}
            rowModesModel={rowModesModel}
            onRowModesModelChange={setRowModesModel}
            processRowUpdate={processRowUpdate}
            onProcessRowUpdateError={onProcessRowUpdateError}
            columns={columns}
            getCellClassName={(params: GridCellParams<PaymentRow>) => {
              return params.row._errors?.has(params.field) ? "hasError" : "";
            }}
            slots={{
              footer: FooterToolbar,
              noRowsOverlay: NoRowsOverlay,
            }}
            slotProps={{
              footer: { child: footer },
            }}
          />
        </Box>
      </Stack>
      <BulkAddPaymentModal
        onSave={onBulkAddSave}
        open={bulkAddModalOpen}
        onClose={closeBulkAddModal}
      />
    </LocalizationProvider>
  );
};

const NoRowsOverlay: React.FC = () => {
  const { t } = useTranslation();
  return (
    <Box
      display="flex"
      justifyContent="center"
      alignItems="center"
      height="100%"
    >
      <Typography variant="caption">
        {t("Add some payment milestones!")}
      </Typography>
    </Box>
  );
};

const FooterToolbar: React.FC<FooterPropsOverrides> = ({ child }) => {
  return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>;
};

export default MilestoneSection;
