import {create} from 'zustand';
import {searchMessages} from '@modules/chats/services';
import {SearchMessageMatchStoreItem} from '@modules/chats/interfaces';
import {mapSearchMessageMatchesFromServer} from '@modules/chats/utils';

type SelectedMatch = {
  index: number;
  ts: string;
  /**
   * Number between 0 and 1, <and about its purpose>
   **/
  viewPosition: number;
};

type DidSearchInitListenerCallback = (
  currentSelectedMatch: Nullable<SelectedMatch>,
) => void;

interface SearchMessagesStore {
  /** Current search query text */
  query: string;
  /** ID of the channel being searched */
  channelId: string;
  /** Loading state for search requests */
  isLoading: boolean;
  /** Visibility state of the search UI */
  isSearchVisible: boolean;
  /** Formatted label showing the count of matches (e.g., "5" or "99+") */
  matchesCountLabel: string;
  /** Array of matched messages with their timestamps */
  matches: SearchMessageMatchStoreItem[];
  /** Map for quick lookup of match index by message timestamp */
  matchedMessagesTsIndexMap: Map<string, number>;
  /** Currently selected match with its index and timestamp */
  currentSelectedMatch: Nullable<SelectedMatch>;
  /** Cursor for loading next page of search results */
  nextCursor?: Nullable<string>;
  /** Controller to abort ongoing search requests */
  abortController: Nullable<AbortController>;
  /** Listeners to be called when search is initialized */
  didSearchInitListeners: Array<DidSearchInitListenerCallback>;
  /** Toggles the visibility of the search UI */
  setIsSearchVisible: (isVisible: boolean) => void;
  /** Initiates a new search in the specified channel */
  initSearch: (channelId: string, text: string) => Promise<void>;
  /** Selects a match by its index and returns the match details */
  setCurrentSelectedMatch: (index: number) => Nullable<SelectedMatch>;
  /** Loads the next page of search results using the cursor */
  loadNextCursor: () => Promise<void>;
  /** Navigates to the next match in the results */
  nextMatch: () => Nullable<SelectedMatch>;
  /** Navigates to the previous match in the results */
  prevMatch: () => Nullable<SelectedMatch>;
  /** Adds a listener to be called when search is initialized*/
  addSearchInitListener: (cb: DidSearchInitListenerCallback) => () => void;
  /** Resets the store to initial state and aborts ongoing requests */
  cleanup: () => void;
}

const EMPTY_RESULTS: SearchMessageMatchStoreItem[] = [];

const MIN_SEARCH_SYMBOLS = 2;

const REQUEST_PRELOAD_THRESHOLD = 10;

const initialState: Pick<
  SearchMessagesStore,
  | 'query'
  | 'channelId'
  | 'matchesCountLabel'
  | 'isSearchVisible'
  | 'matches'
  | 'currentSelectedMatch'
  | 'matchedMessagesTsIndexMap'
  | 'isLoading'
  | 'nextCursor'
  | 'abortController'
> = {
  query: '',
  channelId: '',
  matches: EMPTY_RESULTS,
  matchesCountLabel: '0',
  isSearchVisible: false,
  currentSelectedMatch: null,
  matchedMessagesTsIndexMap: new Map<string, number>(),
  isLoading: false,
  nextCursor: null,
  abortController: null,
};

export const useMessagesSearch = create<SearchMessagesStore>((set, get) => ({
  ...initialState,
  didSearchInitListeners: [], // declare it here to reset it only on cleanup
  setIsSearchVisible: (isSearchVisible: boolean) => {
    set({isSearchVisible});
  },
  initSearch: async (channelId: string, query: string) => {
    const trimmed = query.trim();
    if (trimmed.length < MIN_SEARCH_SYMBOLS) {
      set({
        ...initialState,
        isSearchVisible: true,
        channelId,
      });
      return;
    }

    const reset = () => {
      set({
        ...initialState,
        isSearchVisible: true,
        query: trimmed,
        channelId,
      });
    };

    get().abortController?.abort();

    const abortController = new AbortController();

    try {
      set({
        isLoading: true,
        abortController,
      });

      const response = await searchMessages({
        channelId,
        query: trimmed,
        signal: abortController.signal,
      });

      if (abortController.signal.aborted) {
        return;
      }

      if (response.data.length) {
        const matches = mapSearchMessageMatchesFromServer(
          response.data,
          trimmed,
        );
        const initialIndex = 0;

        set({
          isLoading: false,
          query: trimmed,
          channelId,
          matches,
          currentSelectedMatch: {
            index: initialIndex,
            ts: matches[initialIndex]?.ts || '',
            viewPosition: matches[initialIndex]?.viewPosition ?? 0.5,
          },
          matchedMessagesTsIndexMap: new Map(
            matches.map((match, index) => [match.ts, index]),
          ),
          matchesCountLabel:
            response.nextCursor || matches.length >= 100
              ? `99+`
              : `${matches.length}`,
          nextCursor: response.nextCursor,
        });

        // Notify listeners that search has been initialized
        get().didSearchInitListeners.forEach(cb =>
          cb(get().currentSelectedMatch),
        );
      } else {
        reset();
      }
    } catch (error) {
      if (error instanceof Error && error.name === 'AbortError') {
        return;
      }
      reset();
    }
  },

  nextMatch: () => {
    const {
      currentSelectedMatch,
      matches,
      setCurrentSelectedMatch,
      loadNextCursor,
    } = get();
    const currentIndex = currentSelectedMatch?.index;
    if (currentIndex !== undefined) {
      const nextIndex = currentIndex + 1;
      if (nextIndex < matches.length) {
        const result = setCurrentSelectedMatch(nextIndex);
        // Try to preload next cursor if nearing the end of current matches
        const remaining = matches.length - nextIndex;
        if (remaining <= REQUEST_PRELOAD_THRESHOLD) {
          loadNextCursor();
        }
        return result;
      }
    }
    return null;
  },
  prevMatch: () => {
    const {currentSelectedMatch, setCurrentSelectedMatch} = get();
    const currentIndex = currentSelectedMatch?.index;
    if (currentIndex !== undefined) {
      const prevIndex = currentIndex - 1;
      if (prevIndex >= 0) {
        return setCurrentSelectedMatch(prevIndex);
      }
    }
    return null;
  },
  setCurrentSelectedMatch: (index: number) => {
    const match = get().matches[index];
    if (match && match.ts) {
      set({
        currentSelectedMatch: {
          index: index,
          ts: match.ts,
          viewPosition: match.viewPosition,
        },
      });
      return {
        index,
        ts: match.ts,
        viewPosition: match.viewPosition,
      };
    }
    return null;
  },
  loadNextCursor: async () => {
    try {
      const {query, isLoading, nextCursor, channelId} = get();

      if (!nextCursor || isLoading) {
        return;
      }

      get().abortController?.abort();
      const abortController = new AbortController();

      set({
        isLoading: true,
        abortController,
      });

      const response = await searchMessages({
        channelId,
        query,
        cursor: nextCursor,
        signal: abortController.signal,
      });

      if (abortController.signal.aborted) {
        return;
      }

      if (response.data.length) {
        const newMatches = mapSearchMessageMatchesFromServer(
          response.data,
          query,
        );
        const {matchedMessagesTsIndexMap: matchedMessagesTsMap, matches} =
          get();

        // Filter out duplicates and add only new unique matches
        const filtered = newMatches.filter(ts => {
          if (matchedMessagesTsMap.has(ts.ts)) {
            return false;
          }
          return true;
        });

        const updatedMatches = [...matches, ...filtered];

        set({
          isLoading: false,
          matches: updatedMatches,
          matchedMessagesTsIndexMap: new Map(
            updatedMatches.map((match, index) => [match.ts, index]),
          ),
          nextCursor: response.nextCursor,
        });
      } else {
        set({
          isLoading: false,
          nextCursor: null,
        });
      }
    } catch (error) {
      set({
        isLoading: false,
      });
    }
  },
  addSearchInitListener: (cb: DidSearchInitListenerCallback) => {
    const {didSearchInitListeners} = get();
    set({
      didSearchInitListeners: [...didSearchInitListeners, cb],
    });
    return () => {
      set(state => {
        return {
          didSearchInitListeners: state.didSearchInitListeners.filter(
            listener => listener !== cb,
          ),
        };
      });
    };
  },
  cleanup: () => {
    get().abortController?.abort();
    set({...initialState, didSearchInitListeners: []});
  },
}));

export const cleanupAndCloseMessagesSearch = () => {
  useMessagesSearch.getState().cleanup();
};

export const openChatMessagesSearch = () => {
  useMessagesSearch.getState().setIsSearchVisible(true);
};
