/**
 * data api for the store
 */
import { getData } from "@racletteCore/lib/data/dataApi"
import { resolveQueryParams } from "@racletteCore/lib/dataHelper"
import { $store, $eventbus } from "@racletteCore"
import type {
  CustomStoreActionsPayload,
  QueryParamsInput,
} from "@racletteCore/types"
import { BehaviorSubject } from "rxjs"
import { mergeDeepRight } from "ramda"

const callbacks = {}
const queryApis = {}

export const $queryApi = (
  actionId: string,
  target = "",
  { params = {}, options = {}, config = {} }: CustomStoreActionsPayload = {},
) => {
  if (queryApis[actionId] && !target.length) {
    // Early return existing queryApi
    return queryApis[actionId]
  }
  const errorSubject$ = new BehaviorSubject(null)
  const responseSubject$ = new BehaviorSubject({})
  // check if we have a query fullfilling the needs of the one requested
  // here. If not we will update the one with this actionId
  let actionType = "queries/add"
  if (!options.hasOwnProperty("useCache")) {
    options.useCache = true
  }
  if (options.cb) {
    // remove callback function to prevent it from tainting the store reducers
    callbacks[actionId] = options.cb
    delete options.cb
  }
  $store.dispatch({
    type: actionType,
    payload: {
      actionId,
      target,
      // TODO: subscriber params are re-resolved on execute(), but this registration
      // snapshot may leave store query params stale until the next execute()
      // we should check the query behaviour for the store and how to deal with changing queries
      params: resolveQueryParams(params),
      options,
      config,
    },
  })
  const queryApi = {
    error$: errorSubject$.asObservable(),
    response$: responseSubject$.asObservable(),
    query$: $store.select((state) => state.queries[actionId]),
    uuids$: $store.select((state) => state.queries[actionId].uuids),
    clear: async () => {
      errorSubject$.next({})
      responseSubject$.next({})
      await $store.dispatch({
        type: "queries/updateItems",
        payload: { items: {}, actionId },
      })
    },
    remove: async () => {
      await $store.dispatch({
        type: "queries/remove",
        payload: { actionId },
      })
      delete callbacks[actionId]
      responseSubject$.complete()
      errorSubject$.complete()
    },
    execute: async (executeParams?: QueryParamsInput) => {
      errorSubject$.next(null)
      responseSubject$.next({})
      // `params` may be a subscriber () => {...} from the orchestrator — always resolve here.
      const baseParams = resolveQueryParams(params)
      const queryParams =
        executeParams === undefined
          ? baseParams
          : mergeDeepRight(baseParams, resolveQueryParams(executeParams))
      await $store.dispatch({
        type: "queries/update",
        payload: {
          loading: true,
          actionId: actionId,
          params: queryParams,
        },
      })

      const resultData = await getData(
        actionId,
        target,
        queryParams,
        options,
      ).catch((e) => {
        errorSubject$.next(e.response)
      })
      responseSubject$.next(resultData?.response)
      if (callbacks[actionId] && typeof callbacks[actionId] === "function") {
        callbacks[actionId](resultData)
      }
      if (resultData?.result) {
        resultData.result = await resultData.result
      }
      return resultData
    },
    onDeleted: (callback) => {
      if (callback) {
        $eventbus.once("query_deleted_" + actionId, (query) => {
          callback(query)
        })
      }
    },
  }
  if (options.immediate === true) {
    queryApi.execute()
  }

  queryApis[actionId] = queryApi
  return queryApi
}

export default $queryApi
