import { registeredPlugins } from "@racletteCore/setup/plugin-system"
import { getCurrentPluginKey } from "virtual:plugin-key"
import { useVueQueryObservableHelper } from "@racletteOrchestrator/composables/useVueQueryObservableHelper"
import { useVueWriteOperationHelper } from "@racletteOrchestrator/composables/useVueWriteOperationHelper"
import { useVueStateObservableHelper } from "@racletteOrchestrator/composables/useVueStateObservableHelper"
import { getCurrentInstance, onUnmounted } from "vue"
import { clone } from "ramda"
import { $store, createStableHash } from "@racletteCore"
import type { pluginKey } from "@racletteCore"
import {
  useRouteState,
  type PluginApi,
  type PluginApiParams,
} from "@racletteOrchestrator"
import { useNamespacedI18n } from "@racletteOrchestrator/composables/useNamespacedI18n"
import { getCompositionByPath } from "@racletteCore/store/reducers/compositions/selectors"

//TODO move this into a $store api module
const findWidgetUUIDs = (obj: any): string[] => {
  const uuids: string[] = []

  const recurse = (value: any): void => {
    if (Array.isArray(value)) {
      value.forEach(recurse)
    } else if (value && typeof value === "object") {
      if (value.widget?.uuid) {
        uuids.push(value.widget.uuid)
      }
      Object.values(value).forEach(recurse)
    }
  }

  recurse(obj.widgetsLayout)
  return uuids
}

export const usePluginApi = (
  params: pluginKey | PluginApiParams = {},
): PluginApi => {
  const instance = getCurrentInstance()
  const getState = useVueStateObservableHelper()

  const routeState = useRouteState()
  let pluginKeyOverride: string | undefined
  let actionIdOverride: string | undefined

  if (typeof params === "string") {
    pluginKeyOverride = params
  }
  if (typeof params === "object") {
    if (params?.actionId?.length) {
      actionIdOverride = params.actionId
    }
    if (params.pluginKey) {
      pluginKeyOverride = params.pluginKey
    }
  }
  if (!instance) {
    throw new Error("usePluginApi must be used within setup()")
  }

  const pluginKey = getCurrentPluginKey(instance.uid)

  if (!pluginKey && !pluginKeyOverride) {
    console.error("usePluginApi must be used within a plugin component")
    return {} as PluginApi
  }
  let plugin = pluginKey ? clone(registeredPlugins[pluginKey]) : null
  if (pluginKeyOverride) {
    const newPlugin = clone(registeredPlugins[pluginKeyOverride])

    if (!newPlugin) {
      console.error(
        `Plugin '${pluginKeyOverride}' not found during usePluginApi() setup`,
      )
      return {} as PluginApi
    }

    // override logger here as we incarnate a foreign api
    if (plugin) {
      newPlugin.$api.$log = plugin.$api.$log
    }
    plugin = newPlugin
  }
  if (!plugin || !plugin.$api) {
    console.error(`Plugin API not found for plugin: ${pluginKey}`)

    return {} as PluginApi
  }
  const $instanceApi = plugin.$api

  if (plugin.initCud) {
    $instanceApi.$data = plugin.initCud(
      {
        read: useVueQueryObservableHelper,
        write: useVueWriteOperationHelper,
      },
      instance.uid,
      actionIdOverride,
    )
  }
  if (plugin.initSocket) {
    $instanceApi.$socket = plugin.initSocket()
  }
  $instanceApi.$i18n = useNamespacedI18n(pluginKeyOverride ?? pluginKey)
  if (!pluginKeyOverride) {
    const getSiblingWidgetStates = (compositionId: string = "byRoute") => {
      const currentSlotType = instance.props?.slotType
      let siblingWidgetIds = []
      const activeCompositionPaths = []

      if (compositionId === "byRoute") {
        const { compositionSlots } = routeState
        compositionSlots.value.forEach((slot) => {
          if (currentSlotType) {
            if (slot.slotName === currentSlotType) {
              activeCompositionPaths.push(slot.slotValue)
            }
          } else {
            activeCompositionPaths.push(slot.slotValue)
          }
        })
        activeCompositionPaths?.forEach((path) => {
          const composition = getCompositionByPath(path)
          siblingWidgetIds = siblingWidgetIds.concat(
            findWidgetUUIDs(composition),
          )
        })
      } else {
        // TODO implement widget retrieval from state via compositionId
        console.log("not implemented yet")
      }

      if (!siblingWidgetIds.length) {
        console.error("There is no sibling widgets")
        return {}
      } else {
        const getResult = () => {
          const { state } = getState(["widgets", siblingWidgetIds])
          return state
        }
        return getResult()
      }
    }
    const getWidgetId = () => {
      return instance?.props?.widgetId || instance?.props?.uuid || ""
    }
    $instanceApi.$store = {
      hasChanges: $store.hasChanges,
      getSiblingWidgetStates,
      /* Store getter */
      getWidgetState: (widgetId: string = getWidgetId()) => {
        if (!widgetId.length) {
          console.error("No widgetId was provided")
          return {}
        } else {
          const getResult = () => {
            const { state } = getState(["widgets", widgetId])
            return state
          }
          return getResult()
        }
      },
      getUiState: () => {
        const getResult = () => {
          const { state } = getState(["ui"])

          return state
        }
        return getResult()
      },
      getQueryState: (actionId) => {
        if (!actionId) return {}
        const getResult = () => {
          const { state } = getState(["queries", actionId])

          return state
        }
        return getResult()
      },

      getQueryData: (actionId) => {
        if (!actionId) return {}
        console.error("not implemented yet")
      },
      get: (pathArray, filter) => {
        const getResult = () => {
          const { state } = getState(pathArray, filter)
          return state
        }
        return getResult()
      },
      /* Store Setter */
      setUiState: (payload) =>
        $store.dispatch({
          type: "ui/update",
          payload,
        }),
      setWidgetState: (payload, widgetId = getWidgetId()) => {
        $store.dispatch({
          type: "widgets/update",
          uuid: widgetId,
          override: true,
          payload,
        })
      },
      updateWidgetState: (
        payload: object,
        widgetId: string = getWidgetId(),
      ) => {
        if (!widgetId) return false
        $store.dispatch({
          type: "widgets/update",
          uuid: widgetId,
          payload: payload,
        })
      },
      updateConfig: (payload: object, widgetId: string = getWidgetId()) => {
        if (!widgetId) return false
        $store.dispatch({
          type: "widgets/update",
          uuid: widgetId,
          payload: {
            config: payload,
          },
        })
      },
      updatePublic: (payload: object, widgetId: string = getWidgetId()) => {
        if (!widgetId) return false
        $store.dispatch({
          type: "widgets/update",
          uuid: widgetId,
          payload: {
            public: payload,
          },
        })
      },
      publishWorkingState: (
        payload: object,
        widgetId: string = getWidgetId(),
      ) => {
        if (!widgetId) return false
        $store.dispatch({
          type: "widgets/update",
          uuid: widgetId,
          payload: {
            workingState: payload,
          },
        })
      },
      updateDataItem: (
        uuid: string,
        payload: object,
        override: boolean = false,
      ) => {
        $store.dispatch({
          type: "data/update",
          docId: uuid,
          override,
          payload: payload,
        })
      },
    }
  }
  const getUser = () => {
    const { state } = getState("user")
    return state
  }
  if (plugin.initEventbus) {
    $instanceApi.$eventbus = plugin.initEventbus()
  }

  // Retrieve the user for the api
  $instanceApi.$user = getUser()
  onUnmounted(() => {
    if ($instanceApi.$socket?.registeredListeners?.length) {
      // Clean up socket events
      $instanceApi.$socket.registeredListeners.forEach((listener) => {
        $instanceApi.$socket.off(listener.eventName, listener.handlerFn)
      })
    }
  })

  return $instanceApi
}
