"use client";

import Grid from "@mui/material/Grid";
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import Typography from "@mui/material/Typography";
import React, { SyntheticEvent, useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import TextField from "@mui/material/TextField";
import FormControl from "@mui/material/FormControl";
import InputLabel from "@mui/material/InputLabel";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";
import CardActions from "@mui/material/CardActions";
import Button from "@mui/material/Button";
import RestartAlt from "@mui/icons-material/RestartAlt";
import Search from "@mui/icons-material/Search";
import dayjs, { Dayjs } from "dayjs";
import "dayjs/locale/zh-hk";
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import { TimePicker } from "@mui/x-date-pickers/TimePicker";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import {
  Autocomplete,
  Box,
  Checkbox,
  Chip,
  ListSubheader,
} from "@mui/material";
import MultiSelect from "@/components/SearchBox/MultiSelect";
import { intersectionWith } from "lodash";
import { INPUT_DATE_FORMAT, INPUT_TIME_FORMAT, OUTPUT_DATE_FORMAT, OUTPUT_TIME_FORMAT, dayjsToDateTimeString } from "@/app/utils/formatUtil";
import { DateTimePicker } from "@mui/x-date-pickers";

interface BaseCriterion<T extends string> {
  label: string;
  label2?: string;
  paramName: T;
  paramName2?: T;
  // options?: T[] | string[];
  defaultValue?: string;
  /** 與 `defaultValue` 配對，用於 dateRange / datetimeRange 重置時的結束值 */
  defaultValueTo?: string;
  preFilledValue?: string | { from?: string; to?: string };
  filterObj?: T;
  handleSelectionChange?: (selectedOptions: T[]) => void;
}

interface OptionWithLabel<T extends string> {
  label: string;
  value: any;
}

interface TextCriterion<T extends string> extends BaseCriterion<T> {
  type: "text";
  placeholder?: string;
}

interface SelectCriterion<T extends string> extends BaseCriterion<T> {
  type: "select";
  options: string[];
}

interface SelectWithLabelCriterion<T extends string> extends BaseCriterion<T> {
  type: "select-labelled";
  options: OptionWithLabel<T>[];
}

interface MultiSelectCriterion<T extends string> extends BaseCriterion<T> {
  type: "multi-select";
  options: T[];
  selectedOptions: T[];
  handleSelectionChange: (selectedOptions: T[]) => void;
}

interface AutocompleteOptions {
  value: string | number;
  label: string;
  group?: string;
}

interface AutocompleteCriterion<T extends string> extends BaseCriterion<T> {
  type: "autocomplete";
  options: AutocompleteOptions[];
  multiple?: boolean;
  noOptionsText?: string;
  needAll?: boolean;
}

interface DateRangeCriterion<T extends string> extends BaseCriterion<T> {
  type: "dateRange";
}

interface DatetimeRangeCriterion<T extends string> extends BaseCriterion<T> {
  type: "datetimeRange";
}

interface DateCriterion<T extends string> extends BaseCriterion<T> {
  type: "date";
}

interface TimeCriterion<T extends string> extends BaseCriterion<T> {
  type: "time";
}

export type Criterion<T extends string> =
  | TextCriterion<T>
  | SelectCriterion<T>
  | SelectWithLabelCriterion<T>
  | DateRangeCriterion<T>
  | DatetimeRangeCriterion<T>
  | DateCriterion<T>
  | TimeCriterion<T>
  | MultiSelectCriterion<T>
  | AutocompleteCriterion<T>;

interface Props<T extends string> {
  criteria: Criterion<T>[];
  // TODO: may need to check the type is "autocomplete" and "multiple" = true, then allow string[].
  // TODO: may need to check the type is "dateRange", then add T and `${T}To` in the same time.
  // onSearch: (inputs: Record<T | (Criterion<T>["type"] extends "dateRange" ? `${T}To` : never), string>) => void;
  onSearch: (inputs: Record<T | `${T}To`, string>) => void;
  onReset?: () => void;
  /** Optional actions rendered in the same row as Reset/Search (e.g. Download, Upload buttons) */
  extraActions?: React.ReactNode;
  /** Disable inputs/actions while external task is running */
  disabled?: boolean;
}

function SearchBox<T extends string>({
  criteria,
  onSearch,
  onReset,
  extraActions,
  disabled = false,
}: Props<T>) {
  const { t } = useTranslation("common");
  const defaultAll: AutocompleteOptions = {
    value: "All",
    label: t("All"),
    group: t("All"),
  };
  const defaultInputs = useMemo(
    () =>
      criteria.reduce<Record<T | `${T}To`, string>>(
        (acc, c) => {
          let tempCriteria = {
            ...acc,
            [c.paramName]:
              c.type === "select" ||
              c.type === "select-labelled" ||
              (c.type === "autocomplete" && !Boolean(c.multiple))
                ? "All"
                : c.type === "autocomplete" && Boolean(c.multiple)
                  ? [defaultAll.value]
                  : "",
          };

          if (c.type === "dateRange" || c.type === "datetimeRange") {
            tempCriteria = {
              ...tempCriteria,
              [c.paramName]: c.defaultValue ?? "",
              [`${c.paramName}To`]: c.defaultValueTo ?? "",
            };
          }
          if (c.type === "date") {
            tempCriteria = {
              ...tempCriteria,
              [c.paramName]: c.defaultValue ?? "",
            };
          }
          return tempCriteria;
        },
        {} as Record<T | `${T}To`, string>,
      ),
    [criteria],
  );
  const preFilledInputs = useMemo(() => {
    const preFilledCriteria = criteria.reduce<Record<T | `${T}To`, string>>(
      (acc, c) => {
        if (c.type === "dateRange" || c.type === "datetimeRange") {
          if (typeof c.preFilledValue === "object" && c.preFilledValue !== null) {
            return {
              ...acc,
              ...(c.preFilledValue.from ? { [c.paramName]: c.preFilledValue.from } : {}),
              ...(c.preFilledValue.to ? { [`${c.paramName}To`]: c.preFilledValue.to } : {}),
            };
          }
        }
        if (typeof c.preFilledValue === "string") {
          return { ...acc, [c.paramName]: c.preFilledValue };
        }
        return acc;
      },
      {} as Record<T | `${T}To`, string>,
    );
    return {...defaultInputs, ...preFilledCriteria}
  }, [defaultInputs, criteria])

  const [inputs, setInputs] = useState(preFilledInputs);
  const [isReset, setIsReset] = useState(false);

  const makeInputChangeHandler = useCallback(
    (paramName: T): React.ChangeEventHandler<HTMLInputElement> => {
      return (e) => {
        setInputs((i) => ({ ...i, [paramName]: e.target.value }));
      };
    },
    [],
  );

  const makeSelectChangeHandler = useCallback((paramName: T) => {
    return (e: SelectChangeEvent) => {
      setInputs((i) => ({ ...i, [paramName]: e.target.value }));
    };
  }, []);

  const makeAutocompleteChangeHandler = useCallback(
    (paramName: T, multiple: boolean) => {
      return (
        e: SyntheticEvent,
        newValue: AutocompleteOptions | AutocompleteOptions[],
      ) => {
        if (multiple) {
          const multiNewValue = newValue as AutocompleteOptions[];
          setInputs((i) => ({
            ...i,
            [paramName]: multiNewValue.map(({ value }) => value),
          }));
        } else {
          const singleNewValue = newValue as AutocompleteOptions;
          setInputs((i) => ({ ...i, [paramName]: singleNewValue.value }));
        }
      };
    },
    [],
  );

  const makeDateChangeHandler = useCallback((paramName: T) => {
    return (e: any) => {
      setInputs((i) => ({ ...i, [paramName]: dayjs(e).format("YYYY-MM-DD") }));
    };
  }, []);

  const makeDateToChangeHandler = useCallback((paramName: T) => {
    return (e: any) => {
      setInputs((i) => ({
        ...i,
        [paramName + "To"]: dayjs(e).format("YYYY-MM-DD"),
      }));
    };
  }, []);

  const makeDatetimeChangeHandler = useCallback((paramName: T) => {
    return (value: Dayjs | null) => {
      setInputs((i) => ({
        ...i,
        [paramName]: value ? dayjsToDateTimeString(value) : null
      }));
    };
  }, []);

  const makeDatetimeToChangeHandler = useCallback((paramName: T) => {
    return (value: Dayjs | null) => {
      setInputs((i) => ({
        ...i,
        [paramName + "To"]: value ? dayjsToDateTimeString(value) : null
      }));
    };
  }, []);

  const makeTimeChangeHandler = useCallback((paramName: T) => {
    return (value: Dayjs | null) => {
      setInputs((i) => ({
        ...i,
        [paramName]: value ? value.format("HH:mm") : ""
      }));
    };
  }, []);

  const handleReset = () => {
    setInputs(preFilledInputs);
    onReset?.();
    setIsReset(!isReset);
  };

  const handleSearch = () => {
    onSearch(inputs);
  };

  return (
    <Card className="app-search-criteria" elevation={0}>
      <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
        <Typography className="app-search-criteria-label" variant="overline" sx={{ display: "block", mb: 0.5 }}>
          {t("Search Criteria")}
        </Typography>
        <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
          {criteria.map((c) => {
            return (
              <Grid key={c.paramName} item xs={6}>
                {c.type === "text" && (
                  <TextField
                    label={c.label}
                    fullWidth
                    placeholder={c.placeholder}
                    onChange={makeInputChangeHandler(c.paramName)}
                    value={inputs[c.paramName]}
                    disabled={disabled}
                  />
                )}
                {/* eslint-disable-next-line @typescript-eslint/no-unused-vars */}
                {/* {c.type === "multi-select" && (
                  <MultiSelect
                    label={t(c.label)}
                    options={c?.options}
                    selectedValues={c.filterObj?.[c.paramName] ?? []}
                    onChange={c.handleSelectionChange}
                    isReset={isReset}
                  />
                )} */}
                {c.type === "select" && (
                  <FormControl fullWidth>
                    <InputLabel>{c.label}</InputLabel>
                    <Select
                      label={c.label}
                      onChange={makeSelectChangeHandler(c.paramName)}
                      value={inputs[c.paramName] ?? "All"}
                      disabled={disabled}
                    >
                      <MenuItem value={"All"}>{t("All")}</MenuItem>
                      {c.options.map((option) => (
                        <MenuItem key={option} value={option}>
                          {t(option)}
                        </MenuItem>
                      ))}
                    </Select>
                  </FormControl>
                )}
                {c.type === "select-labelled" && (
                  <FormControl fullWidth>
                    <InputLabel>{c.label}</InputLabel>
                    <Select
                      label={c.label}
                      onChange={makeSelectChangeHandler(c.paramName)}
                      value={inputs[c.paramName] ?? "All"}
                      disabled={disabled}
                    >
                      <MenuItem value={"All"}>{t("All")}</MenuItem>
                      {c.options.map((option) => (
                        <MenuItem key={option.value} value={option.value}>
                          {option.label}
                        </MenuItem>
                      ))}
                    </Select>
                  </FormControl>
                )}
                {c.type === "autocomplete" && (
                  <Autocomplete
                    groupBy={
                      c.options.filter((option) => option.group !== "All")
                        .length > 0 && c.options.every((option) => option.group)
                        ? (option) =>
                            option.group && option.group.trim() !== ""
                              ? option.group
                              : "Ungrouped"
                        : undefined
                    }
                    multiple={Boolean(c.multiple)}
                    noOptionsText={c.noOptionsText ?? t("No options")}
                    disableClearable
                    fullWidth
                    disabled={disabled}
                    value={
                      c.multiple
                        ? intersectionWith(
                            [defaultAll, ...c.options],
                            inputs[c.paramName],
                            (option, v) => {
                              return option.value === (v ?? "");
                            },
                          )
                        : c.options.find(
                            (option) => option.value === inputs[c.paramName],
                          ) ?? defaultAll
                    }
                    onChange={makeAutocompleteChangeHandler(
                      c.paramName,
                      Boolean(c.multiple),
                    )}
                    getOptionLabel={(option) => option.label}
                    options={[defaultAll, ...c.options]}
                    disableCloseOnSelect={Boolean(c.multiple)}
                    renderGroup={
                      c.options.every((option) => option.group)
                        ? (params) => (
                            <React.Fragment
                              key={`${params.key}-${params.group}`}
                            >
                              <ListSubheader>{params.group}</ListSubheader>
                              {params.children}
                            </React.Fragment>
                          )
                        : undefined
                    }
                    renderTags={
                      c.multiple
                        ? (value, getTagProps) =>
                            value.map((option, index) => {
                              // eslint-disable-next-line @typescript-eslint/no-unused-vars
                              const { key, ...chipProps } = getTagProps({
                                index,
                              });
                              return (
                                <Chip
                                  {...chipProps}
                                  key={`${option.value}-${option.label}`}
                                  label={option.label}
                                />
                              );
                            })
                        : undefined
                    }
                    renderOption={(
                      params: React.HTMLAttributes<HTMLLIElement> & {
                        key?: React.Key;
                      },
                      option,
                      { selected },
                    ) => {
                      // eslint-disable-next-line @typescript-eslint/no-unused-vars
                      const { key, ...rest } = params;
                      return (
                        <MenuItem
                          {...rest}
                          disableRipple
                          value={option.value}
                          key={`${option.value}--${option.label}`}
                        >
                          {c.multiple && (
                            <Checkbox
                              disableRipple
                              key={`checkbox-${option.value}`}
                              checked={selected}
                              sx={{ transform: "translate(0)" }}
                            />
                          )}
                          {option.label}
                        </MenuItem>
                      );
                    }}
                    renderInput={(params) => (
                      <TextField
                        {...params}
                        variant="outlined"
                        label={c.label}
                      />
                    )}
                  />
                )}
                {c.type === "dateRange" && (
                  <LocalizationProvider
                    dateAdapter={AdapterDayjs}
                    // TODO: Should maybe use a custom adapterLocale here to support YYYY-MM-DD
                    adapterLocale="zh-hk"
                  >
                    <Box display="flex">
                      <FormControl fullWidth>
                        <DatePicker
                          format={`${OUTPUT_DATE_FORMAT}`}
                          label={c.label}
                          onChange={makeDateChangeHandler(c.paramName)}
                          disabled={disabled}
                          value={
                            dayjs(inputs[c.paramName]).isValid()
                              ? dayjs(inputs[c.paramName])
                              : null
                          }
                        />
                      </FormControl>
                      <Box
                        display="flex"
                        alignItems="center"
                        justifyContent="center"
                        marginInline={2}
                      >
                        {"-"}
                      </Box>
                      <FormControl fullWidth>
                        <DatePicker
                          format={`${OUTPUT_DATE_FORMAT}`}
                          label={c.label2 ? c.label2 : null}
                          onChange={makeDateToChangeHandler(c.paramName)}
                          disabled={disabled}
                          value={
                            dayjs(inputs[`${c.paramName}To`]).isValid()
                              ? dayjs(inputs[`${c.paramName}To`])
                              : null
                          }
                        />
                      </FormControl>
                    </Box>
                  </LocalizationProvider>
                )}
                {c.type === "datetimeRange" && (
                  <LocalizationProvider
                    dateAdapter={AdapterDayjs}
                    // TODO: Should maybe use a custom adapterLocale here to support YYYY-MM-DD
                    adapterLocale="zh-hk"
                  >
                    <Box display="flex">
                      <FormControl fullWidth>
                        <DateTimePicker
                          views={['year','month','day','hours', 'minutes', 'seconds']}
                          format={`${OUTPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`}
                          label={c.label}
                          onChange={makeDatetimeChangeHandler(c.paramName)}
                          disabled={disabled}
                          value={
                            dayjs(inputs[c.paramName]).isValid()
                              ? dayjs(inputs[c.paramName])
                              : null
                          }
                        />
                      </FormControl>
                      <Box
                        display="flex"
                        alignItems="center"
                        justifyContent="center"
                        marginInline={2}
                      >
                        {"-"}
                      </Box>
                      <FormControl fullWidth>
                        <DateTimePicker
                          views={['year','month','day','hours', 'minutes', 'seconds']}
                          format={`${OUTPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`}
                          label={c.label2 ? c.label2 : null}
                          onChange={makeDatetimeToChangeHandler(c.paramName)}
                          disabled={disabled}
                          value={
                            dayjs(inputs[`${c.paramName}To`]).isValid()
                              ? dayjs(inputs[`${c.paramName}To`])
                              : null
                          }
                        />
                      </FormControl>
                    </Box>
                  </LocalizationProvider>
                )}
                {c.type === "date" && (
                  <LocalizationProvider
                    dateAdapter={AdapterDayjs}
                    // TODO: Should maybe use a custom adapterLocale here to support YYYY-MM-DD
                    adapterLocale="zh-hk"
                  >
                    <Box display="flex">
                      <FormControl fullWidth>
                        <DatePicker
                          format={`${OUTPUT_DATE_FORMAT}`}
                          label={c.label}
                          onChange={makeDateChangeHandler(c.paramName)}
                          disabled={disabled}
                          value={
                            inputs[c.paramName] && dayjs(inputs[c.paramName]).isValid()
                              ? dayjs(inputs[c.paramName])
                              : null
                          }
                        />
                      </FormControl>
                    </Box>
                  </LocalizationProvider>
                )}
                {c.type === "time" && (
                  <LocalizationProvider
                    dateAdapter={AdapterDayjs}
                    adapterLocale="zh-hk"
                  >
                    <FormControl fullWidth>
                      <TimePicker
                        format="HH:mm"
                        label={c.label}
                        onChange={makeTimeChangeHandler(c.paramName)}
                        disabled={disabled}
                        value={
                          inputs[c.paramName] && dayjs(inputs[c.paramName], "HH:mm").isValid()
                            ? dayjs(inputs[c.paramName], "HH:mm")
                            : null
                        }
                      />
                    </FormControl>
                  </LocalizationProvider>
                )}
              </Grid>
            );
          })}
        </Grid>
        <CardActions sx={{ justifyContent: "flex-start", gap: 1, pt: 2, flexWrap: "wrap" }}>
          <Button
            variant="outlined"
            startIcon={<RestartAlt />}
            onClick={handleReset}
            disabled={disabled}
            sx={{ borderColor: "#e2e8f0", color: "#334155" }}
          >
            {t("Reset")}
          </Button>
          <Button
            variant="contained"
            color="primary"
            startIcon={<Search />}
            onClick={handleSearch}
            disabled={disabled}
          >
            {t("Search")}
          </Button>
          {extraActions}
        </CardActions>
      </CardContent>
    </Card>
  );
}

export default SearchBox;
