/**
 * Configure and build application store
 *
 * The application store is the single source of truth for any data living
 * outside the scope and lifecycle of a component.
 */
import {
  configureStore,
  LoggerExtension,
  ReduxDevtoolsExtension,
  UndoExtension,
  ImmutableStateExtension,
} from "mini-rx-store"
import {
  getItemFromLookup,
  getItemsFromLookup,
  getValueFromLookupItem,
  getValueFromLookup,
  deepFreeze,
} from "../lib/dataHelper"
import { isDevelopmentMode } from "@racletteCore/helpers/devMode"
import { applicationState } from "./state"
/* import feature reducers */
import metaReducers from "./reducers/metaReducer"
/* applications reducers */
import { widgetsReducers, widgetEffects } from "./reducers/widgets"
import projectsReducers from "./reducers/projects"
import uiReducers from "./reducers/ui"
import notificationsReducers from "./reducers/notifications"
import { dataReducers } from "./reducers/data"
import userReducers from "./reducers/user"
import { projectReducers } from "./reducers/project"
import {
  interactionLinksReducers,
  interactionLinksEffects,
} from "./reducers/interactionLinks"
import {
  compositionsReducers,
  compositionsEffects,
  compositionsSelectors,
} from "./reducers/compositions"
import backendReducers from "./reducers/backend"
import { queriesEffects, queriesReducers } from "./reducers/queries"
import { queriesCacheReducers } from "./reducers/queriesCache"
import log from "@racletteCore/lib/logger"
import type { RacletteStore, StoreHelperGetState } from "@racletteCore"

/* Apply different extensions depending on the environment */
const extensions = isDevelopmentMode()
    ? [
        new LoggerExtension(),
        new ReduxDevtoolsExtension({
          name: "racletteJS",
          trace: true,
          traceLimit: 25,
        }),

        new UndoExtension(),
        new ImmutableStateExtension(),
      ]
    : [new ImmutableStateExtension()]

let store: RacletteStore

const createStore = (eventbus, appSessionId: string): RacletteStore => {
  if (store) {
    return store
  }
  /* register application reducers */
  const applicationReducers = {
    compositions: compositionsReducers,
    interactionLinks: interactionLinksReducers,
    widgets: widgetsReducers,
    ui: uiReducers,
    notifications: notificationsReducers,
    user: userReducers,
    project: projectReducers,
    projects: projectsReducers,
    backend: backendReducers,
    data: dataReducers,
    queries: queriesReducers,
    queriesCache: queriesCacheReducers,
  }

  /* create and configure the store */
  const dispatchHook = (store) => {
    const originalDispatch = store.dispatch

    store.dispatch = async (action) => {
      // Before action handling
      originalDispatch(action)
      // After action handling
      if (action.resolveId) {
        log.event(`store_actionResolved_${action.resolveId} is emitted`, action)
        // if we have a resolveId given we will emit the action to this eventname so that we can trigger post action processing
        eventbus.emit("store_actionResolved_" + action.resolveId, action)
      }
      if (action.actionResult) {
        const actionResult = action.actionResult
        if (actionResult.relationships.resolved.length) {
          // if we have additional resolveId's to serve, lets do it!
          actionResult.relationships.resolved.forEach((resolveId) => {
            log.event(`store_actionResolved_${resolveId} is emitted`, action)
            eventbus.emit("store_actionResolved_" + resolveId, action)
          })
        }
      }
    }

    return store
  }
  const baseStore = configureStore({
    extensions,
    reducers: applicationReducers,
    initialState: applicationState,
    metaReducers,
  })
  dispatchHook(baseStore)
  /* create and register events */
  let stateLookup = {}
  baseStore
    .select((state) => state)
    .subscribe((state) => {
      stateLookup = state
    })

  store = Object.assign(baseStore, {
    appSessionId,
    hasChanges: (oldVal: string, newVal: string): boolean =>
      JSON.stringify(oldVal) !== JSON.stringify(newVal),
    selectors: {
      compositions: compositionsSelectors,
    },
    getState: (params: string | any[] | StoreHelperGetState = {}): object => {
      // Default values
      let stateKey = ""
      let attributeArr: any[] = []
      let valueArr: any[] = []

      if (typeof params === "string") {
        stateKey = params
      } else if (Array.isArray(params)) {
        attributeArr = params
      } else if (params && typeof params === "object") {
        stateKey = params.stateKey || ""
        attributeArr = params.attributeArr || []
        valueArr = params.valueArr || []
      }

      let result

      if (!stateKey && !attributeArr.length && !valueArr.length) {
        result = stateLookup
      } else if (stateKey && !attributeArr.length && !valueArr.length) {
        result = getItemFromLookup(stateLookup, stateKey)
      } else if (attributeArr.length && !stateKey && !valueArr.length) {
        result = getValueFromLookup(stateLookup, attributeArr)
      } else if (stateKey && attributeArr.length && !valueArr.length) {
        result = getValueFromLookupItem(stateLookup, stateKey, attributeArr)
      } else if (attributeArr.length && valueArr.length && !stateKey) {
        result = getItemsFromLookup(stateLookup, attributeArr, valueArr)
      }

      if (result && typeof result === "object" && !Array.isArray(result)) {
        result = deepFreeze(result)
      }

      return result
    },
  })

  return store
}

const registerStoreEffects = (store) => {
  const effects = []

  widgetEffects(store).forEach((effect) => effects.push(effect))
  compositionsEffects(store).forEach((effect) => effects.push(effect))
  interactionLinksEffects(store).forEach((effect) => effects.push(effect))
  queriesEffects(store).forEach((effect) => effects.push(effect))

  effects.forEach((effect) => store.effect(effect))
}

export default createStore
export { createStore, registerStoreEffects }
