import {
  IEventWithRoomId,
  IResultContext,
  ISearchRequestBody,
  ISearchResponse,
  ISearchResult,
  SearchOrderBy,
} from 'matrix-js-sdk';
import { useCallback } from 'react';
import { useMatrixClient } from '../../hooks/useMatrixClient';

export type ResultItem = {
  rank: number;
  event: IEventWithRoomId;
  context: IResultContext;
};

export type ResultGroup = {
  roomId: string;
  items: ResultItem[];
};

export type SearchResult = {
  nextToken?: string;
  highlights: string[];
  groups: ResultGroup[];
};

// Client-side msgtype post-filter. The Matrix search API cannot filter by
// msgtype server-side, so this is applied to already-returned results.
export type MsgTypeFilter = 'm.image' | 'm.file' | 'm.video';

/**
 * Filter result groups to items whose event msgtype is in `msgTypes` (OR/union).
 * Empty/absent filter returns groups unchanged. Now-empty groups are dropped.
 */
export const filterGroupsByMsgType = (
  groups: ResultGroup[],
  msgTypes: MsgTypeFilter[],
): ResultGroup[] => {
  if (msgTypes.length === 0) return groups;
  const allowed = new Set<string>(msgTypes);
  return groups
    .map((group) => ({
      ...group,
      items: group.items.filter((item) => {
        const msgtype = item.event.content?.msgtype;
        return typeof msgtype === 'string' && allowed.has(msgtype);
      }),
    }))
    .filter((group) => group.items.length > 0);
};

/**
 * Filter result groups to items whose event is currently pinned in its room.
 * `isPinned(roomId, eventId)` returns whether the event is in the room's
 * `m.room.pinned_events` set. When `enabled` is false, groups are returned
 * unchanged. Now-empty groups are dropped.
 */
export const filterGroupsByPinned = (
  groups: ResultGroup[],
  enabled: boolean,
  isPinned: (roomId: string, eventId: string) => boolean,
): ResultGroup[] => {
  if (!enabled) return groups;
  return groups
    .map((group) => ({
      ...group,
      items: group.items.filter((item) => isPinned(group.roomId, item.event.event_id)),
    }))
    .filter((group) => group.items.length > 0);
};

const groupSearchResult = (results: ISearchResult[]): ResultGroup[] => {
  const groups: ResultGroup[] = [];

  results.forEach((item) => {
    const roomId = item.result.room_id;
    const resultItem: ResultItem = {
      rank: item.rank,
      event: item.result,
      context: item.context,
    };

    const lastAddedGroup: ResultGroup | undefined = groups[groups.length - 1];
    if (lastAddedGroup && roomId === lastAddedGroup.roomId) {
      lastAddedGroup.items.push(resultItem);
      return;
    }
    groups.push({
      roomId,
      items: [resultItem],
    });
  });

  return groups;
};

const parseSearchResult = (result: ISearchResponse): SearchResult => {
  const roomEvents = result.search_categories.room_events;

  const searchResult: SearchResult = {
    nextToken: roomEvents?.next_batch,
    highlights: roomEvents?.highlights ?? [],
    groups: groupSearchResult(roomEvents?.results ?? []),
  };

  return searchResult;
};

export type MessageSearchParams = {
  term?: string;
  order?: string;
  rooms?: string[];
  senders?: string[];
  fromTs?: number;
  toTs?: number;
  containsUrl?: boolean;
};
export const useMessageSearch = (params: MessageSearchParams) => {
  const mx = useMatrixClient();
  const { term, order, rooms, senders, fromTs, toTs, containsUrl } = params;

  const searchMessages = useCallback(
    async (nextBatch?: string) => {
      if (!term)
        return {
          highlights: [],
          groups: [],
        };
      const limit = 50;

      const requestBody: ISearchRequestBody = {
        search_categories: {
          room_events: {
            event_context: {
              before_limit: 0,
              after_limit: 0,
              include_profile: false,
            },
            filter: {
              limit,
              rooms,
              senders,
              // from_ts / to_ts and contains_url are valid Matrix spec fields not yet in SDK types
              ...(fromTs !== undefined && { from_ts: fromTs }),
              ...(toTs !== undefined && { to_ts: toTs }),
              ...(containsUrl !== undefined && { contains_url: containsUrl }),
              // eslint-disable-next-line @typescript-eslint/no-explicit-any
            } as any,
            include_state: false,
            order_by: order as SearchOrderBy.Recent,
            search_term: term,
          },
        },
      };

      const r = await mx.search({
        body: requestBody,
        next_batch: nextBatch === '' ? undefined : nextBatch,
      });
      return parseSearchResult(r);
    },
    [mx, term, order, rooms, senders, fromTs, toTs, containsUrl],
  );

  return searchMessages;
};
