import { useCallback, useEffect, useRef, type ReactNode } from 'react';
import { useLocation, useSearchParams } from 'react-router';
import { toast } from 'react-toastify/unstyled';
import { SearchParamKeys } from '../lib/constants';
import { CODE_TO_MESSAGE_MAP, QueryMessageCode, QueryMessageType } from '../lib/QueryMessages';

type QueryMessageString = `${QueryMessageCode}__${QueryMessageType}`;

export type QueryMessage = {
  type: QueryMessageType;
  code: QueryMessageCode;
  message: ReactNode;
};

export type UseQueryMessageOptions = {
  displayMessages?: boolean;
};

export type UseQueryMessageReturn = {
  setQueryMessage: (messageCode: QueryMessageCode, messageType: QueryMessageType) => void;
  clearQueryMessage: () => void;
  toSearchParamQueryMessage: (message: QueryMessageCode, type: QueryMessageType) => QueryMessageString;
};

export function useQueryMessage(
  { displayMessages }: UseQueryMessageOptions = {
    displayMessages: true,
  }
): UseQueryMessageReturn {
  const location = useLocation();
  const [searchParams, setSearchParams] = useSearchParams();
  const messageStr = useRef<QueryMessageString | null>(null);

  useEffect(() => {
    // Reset messageStr when location changes to allow re-displaying the same message on navigation
    messageStr.current = null;
  }, [location.pathname]);

  useEffect(() => {
    const queryMessageStr = searchParams.get(SearchParamKeys.Message);
    if (!(queryMessageStr && queryMessageStr !== messageStr.current)) return;
    const [queryMessage, queryMessageString] = toQueryMessage(queryMessageStr) ?? [null, null];
    if (!queryMessage) return;

    messageStr.current = queryMessageString;
    if (displayMessages) {
      toast[queryMessage.type](queryMessage.message, {
        position: 'top-center',
        autoClose: 5000,
        hideProgressBar: false,
        closeOnClick: true,
        pauseOnHover: true,
        draggable: false,
        progress: undefined,
        theme: 'colored',
        toastId: 'login-route-info-message',
      });
    }
  }, [displayMessages, searchParams]);

  const setQueryMessage = useCallback(
    (messageCode: QueryMessageCode, messageType: QueryMessageType) => {
      const queryMessageString: QueryMessageString = `${messageCode}__${messageType}`;
      messageStr.current = queryMessageString;
      setSearchParams((prev) => {
        prev.set(SearchParamKeys.Message, queryMessageString);
        return prev;
      });
    },
    [setSearchParams]
  );

  const clearQueryMessage = useCallback(() => {
    messageStr.current = null;
    setSearchParams((prev) => {
      prev.delete(SearchParamKeys.Message);
      return prev;
    });
  }, [setSearchParams]);

  const toSearchParamQueryMessage = useCallback((message: QueryMessageCode, type: QueryMessageType): QueryMessageString => {
    return `${message}__${type}`;
  }, []);

  return {
    setQueryMessage,
    clearQueryMessage,
    toSearchParamQueryMessage,
  };
}

function isValidQueryMessageCode(code: string): code is QueryMessageCode {
  return Object.values(QueryMessageCode).includes(code as QueryMessageCode);
}

function isValidQueryMessageType(type: string): type is QueryMessageType {
  return Object.values(QueryMessageType).includes(type as QueryMessageType);
}

function toQueryMessage(value: string): [QueryMessage, QueryMessageString] | null {
  const [code, type] = value.split('__');
  if (!isValidQueryMessageCode(code) || !isValidQueryMessageType(type)) return null;
  return [
    {
      code: code,
      type: type,
      message: CODE_TO_MESSAGE_MAP[code],
    },
    `${code}__${type}`,
  ];
}
