import React, { useCallback, useMemo, useState } from "react";
import { useSearchParams } from "react-router-dom";

import { SvgIconComponent } from "@mui/icons-material";
import AbcIcon from "@mui/icons-material/Abc";
import AccountCircleIcon from "@mui/icons-material/AccountCircle";
import AlternateEmailIcon from "@mui/icons-material/AlternateEmail";
import ClassIcon from "@mui/icons-material/Class";
import LanguageIcon from "@mui/icons-material/Language";
import NumbersIcon from "@mui/icons-material/Numbers";
import PhoneIcon from "@mui/icons-material/Phone";
import QuestionMarkOutlinedIcon from "@mui/icons-material/QuestionMarkOutlined";
import SearchIcon from "@mui/icons-material/Search";
import { SxProps } from "@mui/material";
import FormControl from "@mui/material/FormControl";
import IconButton from "@mui/material/IconButton";
import Stack from "@mui/material/Stack";
import {
  MuiChipsInputChipComponent,
  MuiChipsInputChipProps,
  MuiChipsInputProps,
} from "mui-chips-input";

import { isValidPhoneNumber, parsePhoneNumberWithError } from "libphonenumber-js";
import { useSnackbar } from "notistack";

import { useI18n } from "../contexts/I18nContext";
import { isEmail } from "../util/is_email";
import { isPositiveInteger } from "../util/is_positive_integer";

import ChipsInput from "./ChipsInput";

const styles: Record<string, SxProps> = {
  root: {
    position: "relative",
    ml: 2,
    width: "100%",
    maxWidth: 420,
  },

  formControl: {
    justifyContent: "center",
  },

  button: {
    position: "absolute",
    zIndex: 1,
    ml: 2,
  },
};

export type ChipType =
  | "code"
  | "email"
  | "name"
  | "phone"
  | "purchase_number"
  | "query"
  | "search"
  | "site_code"
  | "variant";

export function assertChipType(type: string): asserts type is ChipType {
  // prettier-ignore
  if (!["code", "email", "name", "phone", "purchase_number", "query", "search", "site_code", "variant"].includes(type)
  ) {
    throw new Error(`Invalid chip type: ${type}`);
  }
}

export const ChipIcons: Record<ChipType, SvgIconComponent> = {
  code: NumbersIcon,
  email: AlternateEmailIcon,
  name: AbcIcon,
  phone: PhoneIcon,
  purchase_number: NumbersIcon,
  query: QuestionMarkOutlinedIcon,
  search: AccountCircleIcon,
  site_code: LanguageIcon,
  variant: ClassIcon,
};

export const getChipIcon = (type: ChipType): SvgIconComponent => {
  return ChipIcons[type] ?? QuestionMarkOutlinedIcon;
};

/**
 * Parses a string as into the {@link ChipsSearchInput} type returning undefined if it was not specified
 * which type or if it could not be inferred from its format.  This function currently supports
 * parsing or infering emails, phone numbers and purchase numbers as {@link ChipsSearchInput}s.
 */
export const parseSearchInput =
  (allowedTypes: ChipType[], fallbackType: string) =>
  (input: string): ChipsSearchInput | undefined => {
    if (!input) return undefined;

    if (input.includes(":")) {
      // eslint-disable-next-line prefer-const
      let [type, value] = input.split(":");
      type = type.toLowerCase();
      assertChipType(type);

      if (!allowedTypes.includes(type)) {
        throw new Error(`Invalid type: ${type}`);
      }

      if (allowedTypes.includes("phone") && type === "phone") {
        if (isValidPhoneNumber(input, "SE")) {
          return { phone: parsePhoneNumberWithError(input, "SE").format("E.164") };
        } else {
          throw new Error(`Invalid phone number: ${input}`);
        }
      }

      return { [type]: value } as ChipsSearchInput;
    }

    if (allowedTypes.includes("email") && isEmail(input)) {
      return { email: input };
    }

    if (allowedTypes.includes("purchase_number") && input.startsWith("#")) {
      const purchase_number = input.slice(1);
      if (isPositiveInteger(purchase_number)) {
        return { purchase_number };
      }
    }

    if (
      allowedTypes.includes("phone") &&
      (input.startsWith("0") || input.startsWith("+")) &&
      isValidPhoneNumber(input, "SE") // TODO: i18n
    ) {
      return { phone: parsePhoneNumberWithError(input, "SE").format("E.164") };
    }

    return { [fallbackType]: input } as ChipsSearchInput;
  };

export type ChipsSearchInput = { [K in ChipType]: { [P in K]: string } }[ChipType];

export interface SearchBarProps {
  allowedTypes: ChipType[];
  fallbackType?: string;
  onChange?: (inputs: ChipsSearchInput[]) => void;
  onSubmit?: (event: React.FormEvent<HTMLFormElement>) => void;
  placeholder: string;
}

export const ChipsSearchBar: React.FC<SearchBarProps> = ({
  allowedTypes,
  fallbackType = "query",
  placeholder,
  onChange,
  onSubmit,
}) => {
  const { enqueueSnackbar } = useSnackbar();
  const [searchParams, setSearchParams] = useSearchParams();
  const { t } = useI18n();
  const parseInput = useMemo(() => parseSearchInput(allowedTypes, fallbackType), [allowedTypes]);

  const [inputs, setInputs] = useState<ChipsSearchInput[]>(
    () =>
      allowedTypes
        .flatMap((type) =>
          searchParams.getAll(type).map((value) => {
            try {
              assertChipType(type);
              return { [type.toLowerCase()]: value };
            } catch (error) {
              console.error("[CHIPS_SERACH_BAR]", error);
              enqueueSnackbar(t("Invalid search query found when loading page."), {
                variant: "warning",
              });
              return null;
            }
          }),
        )
        .filter(Boolean) as ChipsSearchInput[],
  );

  const chips = useMemo(
    () =>
      inputs
        .map((i) => {
          const [key, value] = Object.entries(i)[0];
          assertChipType(key);
          return allowedTypes.includes(key) ? value : null;
        })
        .filter(Boolean) as string[],
    [inputs],
  );

  const setSearchInputs = useCallback(
    (inputs: ChipsSearchInput[]) => {
      setSearchParams((sp) => {
        const entries = Array.from(sp.entries()).filter(([key]) => {
          assertChipType(key);
          return !allowedTypes.includes(key);
        });
        return [...entries, ...inputs.flatMap(Object.entries)];
      });
    },
    [setSearchParams],
  );

  const handleChange = useCallback(
    (inputs: string[]) => {
      try {
        const newInputs = inputs.map(parseInput).filter(Boolean) as ChipsSearchInput[];
        setInputs(newInputs);
        if (onChange != null) {
          onChange(newInputs);
        } else {
          setSearchInputs(newInputs);
        }
      } catch (error) {
        console.error("[CHIPS_SERACH_BAR]", error);
        enqueueSnackbar(t("Invalid search query."), { variant: "warning" });
      }
    },
    [setInputs, onChange],
  );

  const renderChip: MuiChipsInputProps["renderChip"] = useCallback(
    (Component: MuiChipsInputChipComponent, key: React.Key, props: MuiChipsInputChipProps) => {
      try {
        const input = inputs.find((i) => {
          const value = Object.values(i)[0];
          return value === props.title;
        });

        if (input == null) return <Component key={key} {...props} />;

        const type = Object.keys(input)[0];
        assertChipType(type);
        const Icon = getChipIcon(type);
        return <Component key={key} {...props} icon={<Icon />} />;
      } catch (error) {
        console.error("[CHIPS_SERACH_BAR]", error);
        enqueueSnackbar(t("Invalid search query."), { variant: "warning" });
        return <Component key={key} {...props} />;
      }
    },
    [chips, enqueueSnackbar, parseInput, t],
  );

  return (
    <Stack component="form" sx={styles.root} onSubmit={onSubmit}>
      <FormControl sx={styles.formControl}>
        <IconButton aria-label="Search" sx={styles.button} type="submit">
          <SearchIcon />
        </IconButton>

        <ChipsInput
          placeholder={chips.length > 0 ? "" : placeholder}
          renderChip={renderChip}
          size="small"
          validate={(input) => {
            try {
              parseInput(input);
              return true;
            } catch {
              enqueueSnackbar(t("Invalid search type."), { variant: "warning" });
              return false;
            }
          }}
          value={chips}
          variant="outlined"
          onChange={handleChange}
        />
      </FormControl>
    </Stack>
  );
};
