/* eslint-disable no-use-before-define */
import { getResolver } from '~/core/model';
import { pullFromCache, pushToCache } from '~/cache/api';
import { getActiveClient } from '~/client/api';

import {
  createQuery,
  filterByPropEquality,
  queryOptions,
  runQuery,
  sortByFirstCreated,
  sortByLastCreated,
} from '~/core/query';

import {
  COLLECTION_DEFAULT_CACHING_POLICY,
  COLLECTION_DEFAULT_PAGINATION_LIMIT,
  ENABLE_CACHE_MESSAGE,
} from '~/utils/constants';

import { queryStreams as _queryStreams } from '../internalApi/queryStreams';
import { onStreamRecorded, onStreamStarted, onStreamStopped } from '../events';

/* begin_public_function
  id: stream.query
*/
/**
 * ```js
 * import { StreamRepository } from '@amityco/ts-sdk-react-native'
 * const streams = await StreamRepository.getStreams()
 * ```
 *
 * Observe all mutations on a list of {@link Amity.Stream}s
 *
 * @param params for querying streams
 * @param callback the function to call when new data are available
 * @param config
 * @returns An {@link Amity.Unsubscriber} function to run when willing to stop observing the streams
 *
 * @category Stream Live Collection
 */
export const getStreams = (
  params: Amity.StreamLiveCollection,
  callback: Amity.LiveCollectionCallback<Amity.Stream>,
  config?: Amity.LiveCollectionConfig,
) => {
  const { log, cache } = getActiveClient();

  if (!cache) {
    console.log(ENABLE_CACHE_MESSAGE);
  }

  const timestamp = Date.now();
  log(`getStreams(tmpid: ${timestamp}) > listen`);

  const { limit: queryLimit, ...queryParams } = params;

  const limit = queryLimit ?? COLLECTION_DEFAULT_PAGINATION_LIMIT;
  const { policy = COLLECTION_DEFAULT_CACHING_POLICY } = config ?? {};

  const disposers: Amity.Unsubscriber[] = [];
  const cacheKey = ['streams', 'collection', params];

  const applyFilter = (data: Amity.Stream[]): Amity.Stream[] => {
    let streams = filterByPropEquality(data, 'isDeleted', params.isDeleted);

    streams = streams.sort(
      params.sortBy === 'lastCreated' ? sortByLastCreated : sortByFirstCreated,
    );

    return streams;
  };

  const responder = (data: Amity.StreamLiveCollectionCache, isEventModel = false) => {
    const streams: Amity.Stream[] =
      data.data
        .map(streamId => {
          return pullFromCache<Amity.Stream>(['stream', 'get', streamId])!;
        })
        .filter(Boolean)
        .map(({ data }) => data) ?? [];

    callback({
      onNextPage: onFetch,
      /*
       * Only apply filter to RTE Model
       */
      data: isEventModel ? applyFilter(streams) : streams,
      hasNextPage: !!data.params?.page,
      loading: data.loading,
      error: data.error,
    });
  };

  const realtimeRouter = (_: Amity.StreamActionType) => (stream: Amity.Stream) => {
    const collection = pullFromCache<Amity.StreamLiveCollectionCache>(cacheKey)?.data;
    if (!collection) return;

    collection.data = [...new Set([stream.streamId, ...collection.data])];

    pushToCache(cacheKey, collection);
    responder(collection, true);
  };

  const onFetch = (initial = false) => {
    const collection = pullFromCache<Amity.StreamLiveCollectionCache>(cacheKey)?.data;

    const streams = collection?.data ?? [];

    if (!initial && streams.length > 0 && !collection?.params.page) return;

    const query = createQuery(_queryStreams, {
      ...queryParams,
      limit: initial ? limit : undefined,
      page: !initial ? collection?.params.page : undefined,
    });

    runQuery(
      query,
      ({ data: result, error, loading, paging }) => {
        const data = {
          loading,
          error,
          params: { page: paging?.next },
          data: streams,
        };

        if (result) {
          data.data = [...new Set([...streams, ...result.map(getResolver('stream'))])];
        }
        pushToCache(cacheKey, data);

        responder(data);
      },
      queryOptions(policy),
    );
  };

  disposers.push(
    onStreamRecorded(realtimeRouter('onStreamRecorded')),
    onStreamStarted(realtimeRouter('onStreamStarted')),
    onStreamStopped(realtimeRouter('onStreamStopped')),
  );

  onFetch(true);

  return () => {
    log(`getStreams(tmpid: ${timestamp}) > dispose`);
    disposers.forEach(fn => fn());
  };
};
/* end_public_function */
