import { TimeEntry } from "@/app/api/timesheets/actions";
import { Check, Delete, Close } from "@mui/icons-material";
import {
  Box,
  Button,
  FormControl,
  InputLabel,
  Modal,
  ModalProps,
  Paper,
  SxProps,
  TextField,
  Typography,
} from "@mui/material";
import React, { useCallback, useEffect, useMemo } from "react";
import { Controller, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import ProjectSelect from "./ProjectSelect";
import { AssignedProject, ProjectWithTasks } from "@/app/api/projects";
import TaskGroupSelect, {
  TaskGroupSelectWithoutProject,
} from "./TaskGroupSelect";
import TaskSelect from "./TaskSelect";
import { Task, TaskGroup } from "@/app/api/tasks";
import uniqBy from "lodash/uniqBy";
import { roundToNearestQuarter } from "@/app/utils/manhourUtils";
import { shortDateFormatter } from "@/app/utils/formatUtil";
import {
  DAILY_NORMAL_MAX_HOURS,
  TIMESHEET_DAILY_MAX_HOURS,
} from "@/app/api/timesheets/utils";
import dayjs from "dayjs";

export interface Props extends Omit<ModalProps, "children"> {
  onSave: (timeEntry: TimeEntry, recordDate?: string) => Promise<void>;
  onDelete?: () => Promise<void>;
  defaultValues?: Partial<TimeEntry>;
  allProjects: ProjectWithTasks[];
  assignedProjects: AssignedProject[];
  modalSx?: SxProps;
  recordDate?: string;
  isHoliday?: boolean;
  miscTasks: Task[];
}

const modalSx: SxProps = {
  position: "absolute",
  top: "50%",
  left: "50%",
  transform: "translate(-50%, -50%)",
  width: "90%",
  maxHeight: "90%",
  padding: 3,
  display: "flex",
  flexDirection: "column",
  gap: 2,
};
const TimesheetEditModal: React.FC<Props> = ({
  onSave,
  onDelete,
  open,
  onClose,
  defaultValues,
  allProjects,
  assignedProjects,
  modalSx: mSx,
  recordDate,
  isHoliday,
  miscTasks,
}) => {
  const {
    t,
    i18n: { language },
  } = useTranslation("home");

  const taskGroupsByProject = useMemo(() => {
    return allProjects.reduce<{
      [projectId: AssignedProject["id"]]: {
        value: TaskGroup["id"];
        label: string;
      }[];
    }>((acc, project) => {
      return {
        ...acc,
        [project.id]: uniqBy(
          project.tasks.map((t) => ({
            value: t.taskGroup.id,
            label: t.taskGroup.name,
          })),
          "value",
        ),
      };
    }, {});
  }, [allProjects]);

  const taskGroupsWithoutProject = useMemo(
    () =>
      uniqBy(
        miscTasks.map((t) => t.taskGroup),
        "id",
      ),
    [miscTasks],
  );

  const {
    register,
    control,
    reset,
    getValues,
    setValue,
    trigger,
    setError,
    formState,
    watch,
  } = useForm<TimeEntry>();

  useEffect(() => {
    reset(defaultValues ?? { id: Date.now() });
  }, [defaultValues, reset]);

  const saveHandler = useCallback(async () => {
    const valid = await trigger();
    if (valid) {
      try {
        await onSave(getValues(), recordDate);
        reset({ id: Date.now() });
      } catch (e) {
        setError("root", {
          message: e instanceof Error ? e.message : "Unknown error",
        });
      }
    }
  }, [getValues, onSave, recordDate, reset, setError, trigger]);

  const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>(
    (...args) => {
      onClose?.(...args);
      reset({ id: Date.now() });
    },
    [onClose, reset],
  );

  const projectId = watch("projectId");
  const taskGroupId = watch("taskGroupId");
  const otHours = watch("otHours");

  return (
    <Modal open={open} onClose={closeHandler}>
      <Paper sx={{ ...modalSx, ...mSx }}>
        {recordDate && (
          <Typography
            variant="h6"
            marginBlockEnd={2}
            color={isHoliday ? "error.main" : undefined}
          >
            {shortDateFormatter(language).format(new Date(recordDate))}
          </Typography>
        )}
        <FormControl fullWidth>
          <InputLabel shrink>{t("Project Code and Name")}</InputLabel>
          <Controller
            control={control}
            name="projectId"
            render={({ field }) => (
              <ProjectSelect
                referenceDay={dayjs(recordDate)}
                multiple={false}
                allProjects={allProjects}
                assignedProjects={assignedProjects}
                value={field.value}
                onProjectSelect={(newId) => {
                  field.onChange(newId ?? null);
                  const firstTaskGroup = (
                    typeof newId === "number" ? taskGroupsByProject[newId] : []
                  )[0];

                  setValue("taskGroupId", firstTaskGroup?.value);
                  setValue("taskId", undefined);
                }}
                isTimesheetAdmendment={true}
              />
            )}
            rules={{ deps: ["taskGroupId", "taskId"] }}
          />
        </FormControl>
        <FormControl fullWidth>
          <InputLabel shrink>{t("Stage")}</InputLabel>
          <Controller
            control={control}
            name="taskGroupId"
            render={({ field }) =>
              projectId ? (
                <TaskGroupSelect
                  error={Boolean(formState.errors.taskGroupId)}
                  projectId={projectId}
                  taskGroupsByProject={taskGroupsByProject}
                  value={field.value}
                  onTaskGroupSelect={(newId) => {
                    field.onChange(newId ?? null);
                  }}
                />
              ) : (
                <TaskGroupSelectWithoutProject
                  value={field.value}
                  onTaskGroupSelect={(newId) => {
                    field.onChange(newId ?? null);
                    if (!newId) {
                      setValue("taskId", undefined);
                    }
                  }}
                  taskGroups={taskGroupsWithoutProject}
                />
              )
            }
            rules={{
              validate: (id) => {
                if (!projectId) {
                  return true;
                }
                const taskGroups = taskGroupsByProject[projectId];
                return taskGroups.some((tg) => tg.value === id);
              },
              deps: ["taskId"],
            }}
          />
        </FormControl>
        <FormControl fullWidth>
          <InputLabel shrink>{t("Task")}</InputLabel>
          <Controller
            control={control}
            name="taskId"
            render={({ field }) => (
              <TaskSelect
                error={Boolean(formState.errors.taskId)}
                projectId={projectId}
                taskGroupId={taskGroupId}
                allProjects={allProjects}
                value={field.value}
                onTaskSelect={(newId) => {
                  field.onChange(newId ?? null);
                }}
                miscTasks={miscTasks}
              />
            )}
            rules={{
              validate: (id) => {
                const tasks = projectId
                  ? allProjects.find((p) => p.id === projectId)?.tasks
                  : miscTasks;

                return taskGroupId
                  ? Boolean(
                      tasks?.some(
                        (task) =>
                          task.id === id && task.taskGroup.id === taskGroupId,
                      ),
                    )
                  : !id;
              },
            }}
          />
        </FormControl>
        <TextField
          type="number"
          label={t("Hours")}
          fullWidth
          {...register("inputHours", {
            setValueAs: (value) => roundToNearestQuarter(parseFloat(value)),
            validate: (value) => {
              if (value) {
                if (isHoliday) {
                  return t("Cannot input normal hours on holidays");
                }

                return (
                  (0 < value && value <= DAILY_NORMAL_MAX_HOURS) ||
                  t(
                    "Input hours should be between 0 and {{DAILY_NORMAL_MAX_HOURS}}",
                    { DAILY_NORMAL_MAX_HOURS },
                  )
                );
              } else {
                return Boolean(value || otHours) || t("Required");
              }
            },
          })}
          error={Boolean(formState.errors.inputHours)}
          helperText={formState.errors.inputHours?.message}
        />
        <TextField
          type="number"
          label={t("Other Hours")}
          fullWidth
          {...register("otHours", {
            setValueAs: (value) => roundToNearestQuarter(parseFloat(value)),
            validate: (value) => (value ? value > 0 : true),
          })}
          error={Boolean(formState.errors.otHours)}
        />
        <TextField
          label={t("Remarks")}
          fullWidth
          multiline
          rows={2}
          error={Boolean(formState.errors.remark)}
          {...register("remark", {
            validate: (value) =>
              Boolean(projectId || taskGroupId || value) ||
              t("Required for non-billable tasks"),
          })}
          helperText={formState.errors.remark?.message}
        />
        {formState.errors.root?.message && (
          <Typography variant="caption" color="error">
            {t(formState.errors.root.message, {
              DAILY_NORMAL_MAX_HOURS,
              TIMESHEET_DAILY_MAX_HOURS,
            })}
          </Typography>
        )}
        <Box display="flex" justifyContent="flex-end" gap={1}>
          {onDelete && (
            <Button
              variant="outlined"
              startIcon={<Delete />}
              color="error"
              onClick={onDelete}
            >
              {t("Delete")}
            </Button>
          )}
          <Button
            variant="outlined"
            startIcon={<Close />}
            onClick={(event) => closeHandler(event, "backdropClick")}
          >
            {t("Close")}
          </Button>
          <Button
            variant="contained"
            startIcon={<Check />}
            onClick={saveHandler}
          >
            {t("Save")}
          </Button>
        </Box>
      </Paper>
    </Modal>
  );
};

export default TimesheetEditModal;
