"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, { FocusEvent, KeyboardEvent, PointerEvent, 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 FileDownload from "@mui/icons-material/FileDownload";
import dayjs from "dayjs";
import "dayjs/locale/zh-hk";
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { Autocomplete, Box, Checkbox, Chip, FormHelperText, ListSubheader } from "@mui/material";
import { InputAdornment, NumberInput } from "../utils/numberInput";
import { intersectionWith } from "lodash";

interface BaseCriterion<T extends string> {
  label: string;
  label2?: string;
  paramName: T;
  paramName2?: T;
}

interface TextCriterion<T extends string> extends BaseCriterion<T> {
  type: "text";
  textType?: React.HTMLInputTypeAttribute;
  error?: boolean;
  helperText?: React.ReactNode;
}

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

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";
  needMonth?: boolean;
}

interface MonthYearCriterion<T extends string> extends BaseCriterion<T> {
  type: "monthYear";
}

interface NumberCriterion<T extends string> extends BaseCriterion<T> {
  type: "number";
}

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

export type Criterion<T extends string> =
  | TextCriterion<T>
  | SelectCriterion<T>
  | AutocompleteCriterion<T>
  | DateRangeCriterion<T>
  | MonthYearCriterion<T>
  | NumberCriterion<T>
  | DateCriterion<T>;

interface Props<T extends string> {
  criteria: Criterion<T>[];
  onSearch: (inputs: Record<T, string>) => void;
  onReset?: () => void;
  formType?: String;
}

function SearchBox<T extends string>({
  criteria,
  onSearch,
  onReset,
  formType,
}: Props<T>) {
  const { t } = useTranslation("common");
  const defaultAll = { value: "All", label: t("All"), group: t("All") }
  const defaultInputs = useMemo(
    () =>
      criteria.reduce<Record<T, string>>(
        (acc, c) => {
          let defaultValue: string | number = ""
          switch (c.type) {
            case "select":
              if (!(c.needAll === false)) {
                defaultValue = "All"
              } else if (c.options.length > 0) {
                defaultValue = c.options[0]
              }
              break;
            case "autocomplete":
              if (!(c.needAll === false)) {
                defaultValue = "All"
              } else if (c.options.length > 0) {
                defaultValue = c.options[0].value
              }
              break;
            case "monthYear":
              defaultValue = dayjs().format("YYYY-MM")
              break;
            case "date":
              defaultValue = dayjs().format("YYYY-MM-DD")
              break;  
          }

          return {
            ...acc,
            [c.paramName]: defaultValue
          };
        },
        {} as Record<T, string>
      ),
    [criteria]
  );
  const [inputs, setInputs] = useState(defaultInputs);

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

  const makeNumberChangeHandler = useCallback(
    (paramName: T): (event: FocusEvent<HTMLInputElement, Element> | PointerEvent<Element> | KeyboardEvent<Element>, value: number | null) => void => {
      return (event, value) => {
        setInputs((i) => ({ ...i, [paramName]: 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 singleNewVal = newValue as AutocompleteOptions;
        setInputs((i) => ({ ...i, [paramName]: singleNewVal.value }));
      }
    };
  }, []);

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

    };
  }, []);

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

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

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

  const handleReset = () => {
    setInputs(defaultInputs);
    onReset?.();
  };

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

  return (
    <Card>
      <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
        <Typography variant="overline">{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}
                    type={c.textType ?? "text"}
                    fullWidth
                    onChange={makeInputChangeHandler(c.paramName)}
                    value={inputs[c.paramName]}
                    error={Boolean(c.error)}
                    helperText={Boolean(c.error) && c.helperText}
                  />
                )}
                {c.type === "select" && (
                  <FormControl fullWidth>
                    <InputLabel>{c.label}</InputLabel>
                    <Select
                      label={c.label}
                      onChange={makeSelectChangeHandler(c.paramName)}
                      value={inputs[c.paramName]}
                    >
                      {!(c.needAll === false) && (
                        <MenuItem value={"All"}>{t("All")}</MenuItem>
                      )}
                      {c.options.map((option, index) => (
                        <MenuItem key={`${option}-${index}`} value={option}>
                          {t(option)}
                        </MenuItem>
                      ))}
                    </Select>
                  </FormControl>
                )}
                {c.type === "autocomplete" && c.options.some(option => Boolean(option.group)) && (
                  <Autocomplete
                    multiple={Boolean(c.multiple)}
                    noOptionsText={c.noOptionsText ?? t("No options")}
                    disableClearable
                    fullWidth
                    value={c.multiple ? intersectionWith(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))}
                    groupBy={(option) => option.group!!}
                    getOptionLabel={(option) => option.label}
                    options={!(c.needAll === false) ? [defaultAll, ...c.options] : c.options}
                    disableCloseOnSelect={Boolean(c.multiple)}
                    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
                    }
                    renderGroup={(params) => (
                      <React.Fragment key={`${params.key}-${params.group}`}>
                        <ListSubheader>{params.group}</ListSubheader>
                        {params.children}
                      </React.Fragment>
                    )}
                    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 === "autocomplete" && !c.options.some(option => Boolean(option.group)) && (
                  <Autocomplete
                    multiple={Boolean(c.multiple)}
                    noOptionsText={c.noOptionsText ?? t("No options")}
                    disableClearable
                    fullWidth
                    value={c.multiple ? intersectionWith(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={!Boolean(c.multiple) && !(c.needAll === false) ? [defaultAll, ...c.options] : c.options}
                    disableCloseOnSelect={Boolean(c.multiple)}
                    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 === "number" && (
                  <NumberInput
                    placeholder={c.label}
                    min={50}
                    max={99}
                    onChange={makeNumberChangeHandler(c.paramName)}
                    endAdornment={<InputAdornment>%</InputAdornment>}
                    value={inputs[c.paramName] && parseFloat(inputs[c.paramName])}
                  />
                )}
                {(c.type === "monthYear" || 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
                          label={c.label}
                          onChange={c.type === "monthYear" ? makeMonthYearChangeHandler(c.paramName) : makeSingleDateChangeHandler(c.paramName)}
                          value={
                            inputs[c.paramName]
                              ? dayjs(inputs[c.paramName])
                              : dayjs()
                          }
                          views={c.type === "monthYear" ? ["month", "year"] : ["day", "month", "year" ]}
                        />
                      </FormControl>
                    </Box>
                  </LocalizationProvider>
                )}
                {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
                          label={c.label}
                          onChange={makeDateChangeHandler(c.paramName, c.needMonth)}
                          value={
                            inputs[c.paramName]
                              ? dayjs(inputs[c.paramName])
                              : null
                          }
                          views={c.needMonth ? ["month", "year"] : ["day", "month", "year"]}
                        />
                      </FormControl>
                      <Box
                        display="flex"
                        alignItems="center"
                        justifyContent="center"
                        marginInline={2}
                      >
                        {"-"}
                      </Box>
                      <FormControl fullWidth>
                        <DatePicker
                          label={c.label2}
                          onChange={makeDateToChangeHandler(c.paramName, c.needMonth)}
                          value={
                            inputs[c.paramName.concat("To") as T]
                              ? dayjs(inputs[c.paramName.concat("To") as T])
                              : null
                          }
                          views={c.needMonth ? ["month", "year"] : ["day", "month", "year"]}
                        />
                      </FormControl>
                    </Box>
                  </LocalizationProvider>
                )}
              </Grid>
            );
          })}
        </Grid>
        <CardActions sx={{ justifyContent: "flex-end" }}>
          <Button
            variant="text"
            startIcon={<RestartAlt />}
            onClick={handleReset}
          >
            {t("Reset")}
          </Button>
          <Button
            variant="outlined"
            startIcon={
              (formType === "download" && <FileDownload />) || <Search />
            }
            onClick={handleSearch}
          >
            {(formType === "download" && t("Download")) || t("Search")}
          </Button>
        </CardActions>
      </CardContent>
    </Card>
  );
}

export default SearchBox;
