import type { PluginInstallationItemResponse } from '@dify/contracts/api/console/workspaces/types.gen'
import type {
  PluginInfoFromMarketPlace,
  PluginsFromMarketplaceByInfoResponse,
  PluginsFromMarketplaceResponse,
} from '@dify/contracts/marketplace'
import type { MutateOptions, QueryClient, QueryOptions } from '@tanstack/react-query'
import type {
  FormOption,
  ModelProvider,
} from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { AutoUpdateConfig } from '@/app/components/plugins/reference-setting-modal/auto-update-setting/types'
import type {
  DebugInfo as DebugInfoTypes,
  Dependency,
  GitHubItemAndMarketPlaceDependency,
  InstalledPluginCategoryListResponse,
  InstalledPluginListWithTotalResponse,
  InstallPackageResponse,
  InstallStatusResponse,
  MetaData,
  PackageDependency,
  Permissions,
  Plugin,
  PluginDeclaration,
  PluginDetail,
  PluginTask,
  PluginTaskStart,
  ReferenceSetting,
  uploadGitHubResponse,
  VersionInfo,
  VersionListResponse,
} from '@/app/components/plugins/types'
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { cloneDeep } from 'es-toolkit/object'
import { useAtomValue } from 'jotai'
import { useCallback, useEffect, useRef } from 'react'
import { FormTypeEnum } from '@/app/components/base/form/types'
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
import { getFormattedPlugin } from '@/app/components/plugins/marketplace/utils'
import { PluginCategoryEnum, PluginSource, TaskStatus } from '@/app/components/plugins/types'
import { workspacePermissionKeysAtom } from '@/context/permission-state'
import { fetchModelProviderModelList } from '@/service/common'
import { fetchPluginInfoFromMarketPlace, uninstallPlugin } from '@/service/plugins'
import { hasPermission } from '@/utils/permission'
// oxlint-disable-next-line no-restricted-imports
import { get, getMarketplace, post, postMarketplace } from './base'
import { consoleQuery } from './client'
import { useInvalidateAllBuiltInTools } from './use-tools'

const NAME_SPACE = 'plugins'
const useInstalledPluginListKey = [NAME_SPACE, 'installedPluginList']
const usePluginTaskListKey = [NAME_SPACE, 'pluginTaskList']

type PluginTaskListResponse = {
  tasks: PluginTask[]
}

const getString = (value: unknown) => {
  return typeof value === 'string' ? value : ''
}

const getBoolean = (value: unknown) => {
  return value === true
}

const getNumber = (value: unknown) => {
  return typeof value === 'number' ? value : undefined
}

const isRecord = (value: unknown): value is Record<string, unknown> => {
  return !!value && typeof value === 'object' && !Array.isArray(value)
}

const getRecord = (value: unknown, key: string) => {
  if (!isRecord(value)) return undefined

  const child = value[key]
  return isRecord(child) ? child : undefined
}

const getRecordArray = (value: unknown, key: string) => {
  if (!isRecord(value)) return []

  const child = value[key]
  return Array.isArray(child) ? child.filter(isRecord) : []
}

const getStringArray = (value: unknown) => {
  return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : []
}

const getI18nValue = (value: object | null | undefined, key: string) => {
  return value ? getString(Object.entries(value).find(([itemKey]) => itemKey === key)?.[1]) : ''
}

const normalizeI18nObject = (
  value: object | null | undefined,
  fallback = '',
): PluginDeclaration['label'] => {
  const en = getI18nValue(value, 'en_US') || getI18nValue(value, 'en-US') || fallback
  const zhHans = getI18nValue(value, 'zh_Hans') || getI18nValue(value, 'zh-Hans') || en
  const ja = getI18nValue(value, 'ja_JP') || getI18nValue(value, 'ja-JP') || en
  const ptBr = getI18nValue(value, 'pt_BR') || getI18nValue(value, 'pt-BR') || en

  return {
    'en-US': en,
    'zh-Hans': zhHans,
    'zh-Hant': en,
    'pt-BR': ptBr,
    'es-ES': en,
    'fr-FR': en,
    'de-DE': en,
    'ja-JP': ja,
    'ko-KR': en,
    'ru-RU': en,
    'it-IT': en,
    'th-TH': en,
    'uk-UA': en,
    'vi-VN': en,
    'ro-RO': en,
    'pl-PL': en,
    'hi-IN': en,
    'tr-TR': en,
    'fa-IR': en,
    'sl-SI': en,
    'id-ID': en,
    'nl-NL': en,
    'ar-TN': en,
    en_US: en,
    zh_Hans: zhHans,
    ja_JP: ja,
  }
}

const normalizePluginCategory = (
  category: PluginInstallationItemResponse['declaration']['category'],
): PluginCategoryEnum => {
  switch (category) {
    case PluginCategoryEnum.tool:
      return PluginCategoryEnum.tool
    case PluginCategoryEnum.model:
      return PluginCategoryEnum.model
    case PluginCategoryEnum.datasource:
      return PluginCategoryEnum.datasource
    case PluginCategoryEnum.trigger:
      return PluginCategoryEnum.trigger
    case PluginCategoryEnum.agent:
      return PluginCategoryEnum.agent
    case PluginCategoryEnum.extension:
      return PluginCategoryEnum.extension
  }
  return PluginCategoryEnum.extension
}

const normalizePluginSource = (source: PluginInstallationItemResponse['source']): PluginSource => {
  switch (source) {
    case PluginSource.github:
      return PluginSource.github
    case PluginSource.marketplace:
      return PluginSource.marketplace
    case PluginSource.local:
      return PluginSource.local
    case PluginSource.debugging:
      return PluginSource.debugging
  }
  return PluginSource.marketplace
}

const normalizePluginMeta = (meta: Record<string, unknown>): MetaData => {
  return {
    repo: getString(meta.repo),
    version: getString(meta.version),
    package: getString(meta.package),
  }
}

const normalizeToolCredentialOption = (
  option: Record<string, unknown>,
): NonNullable<
  NonNullable<PluginDeclaration['tool']>['credentials_schema'][number]['options']
>[number] => ({
  label: normalizeI18nObject(getRecord(option, 'label'), getString(option.value)),
  value: getString(option.value),
})

const normalizeToolCredential = (
  credential: Record<string, unknown>,
): NonNullable<PluginDeclaration['tool']>['credentials_schema'][number] => ({
  name: getString(credential.name),
  label: normalizeI18nObject(getRecord(credential, 'label'), getString(credential.name)),
  help: credential.help === null ? null : normalizeI18nObject(getRecord(credential, 'help')),
  placeholder: normalizeI18nObject(getRecord(credential, 'placeholder')),
  type: getString(credential.type),
  required: getBoolean(credential.required),
  default: getString(credential.default),
  options: getRecordArray(credential, 'options').map(normalizeToolCredentialOption),
})

const normalizePluginToolDeclaration = (value: unknown): PluginDeclaration['tool'] => {
  if (!isRecord(value)) return undefined

  const identity = getRecord(value, 'identity')
  if (!identity) return undefined

  const name = getString(identity.name)

  return {
    identity: {
      author: getString(identity.author),
      name,
      description: normalizeI18nObject(getRecord(identity, 'description')),
      icon: getString(identity.icon),
      label: normalizeI18nObject(getRecord(identity, 'label'), name),
      tags: getStringArray(identity.tags),
    },
    credentials_schema: getRecordArray(value, 'credentials_schema').map(normalizeToolCredential),
  }
}

const normalizePluginEndpointDeclaration = (value: unknown): PluginDeclaration['endpoint'] => {
  if (!isRecord(value)) return undefined

  return {
    settings: getRecordArray(value, 'settings').map(normalizeToolCredential),
    endpoints: getRecordArray(value, 'endpoints').map((endpoint) => ({
      path: getString(endpoint.path),
      method: getString(endpoint.method),
      hidden: endpoint.hidden === undefined ? undefined : getBoolean(endpoint.hidden),
    })),
  }
}

const normalizeParameterDefault = (value: unknown) => {
  if (Array.isArray(value)) return value.filter((item) => typeof item === 'string')
  if (typeof value === 'string') return value
  if (value === undefined || value === null) return undefined
  return String(value)
}

const normalizeFormType = (type: unknown): FormTypeEnum => {
  switch (type) {
    case FormTypeEnum.appSelector:
      return FormTypeEnum.appSelector
    case FormTypeEnum.boolean:
      return FormTypeEnum.boolean
    case FormTypeEnum.checkbox:
      return FormTypeEnum.checkbox
    case FormTypeEnum.dynamicSelect:
      return FormTypeEnum.dynamicSelect
    case FormTypeEnum.file:
      return FormTypeEnum.file
    case FormTypeEnum.files:
      return FormTypeEnum.files
    case FormTypeEnum.modelSelector:
      return FormTypeEnum.modelSelector
    case FormTypeEnum.multiToolSelector:
      return FormTypeEnum.multiToolSelector
    case FormTypeEnum.radio:
      return FormTypeEnum.radio
    case FormTypeEnum.secretInput:
      return FormTypeEnum.secretInput
    case FormTypeEnum.select:
      return FormTypeEnum.select
    case FormTypeEnum.textNumber:
    case 'number':
      return FormTypeEnum.textNumber
    case FormTypeEnum.textInput:
    case 'string':
    case 'array':
    case 'object':
      return FormTypeEnum.textInput
  }
  return FormTypeEnum.textInput
}

const normalizeParameterOption = (
  option: Record<string, unknown>,
): NonNullable<PluginDeclaration['trigger']['subscription_schema'][number]['options']>[number] => ({
  value: getString(option.value),
  label: normalizeI18nObject(getRecord(option, 'label'), getString(option.value)),
  icon: getString(option.icon) || undefined,
})

const normalizeParameterSchema = (
  parameter: Record<string, unknown>,
): PluginDeclaration['trigger']['subscription_schema'][number] => ({
  name: getString(parameter.name),
  label: normalizeI18nObject(getRecord(parameter, 'label'), getString(parameter.name)),
  type: normalizeFormType(parameter.type),
  auto_generate: parameter.auto_generate ?? undefined,
  template: parameter.template ?? undefined,
  scope: parameter.scope ?? undefined,
  required: getBoolean(parameter.required),
  multiple: getBoolean(parameter.multiple),
  default: normalizeParameterDefault(parameter.default),
  min: parameter.min ?? undefined,
  max: parameter.max ?? undefined,
  precision: parameter.precision ?? undefined,
  options: getRecordArray(parameter, 'options').map(normalizeParameterOption),
  description: normalizeI18nObject(getRecord(parameter, 'description')),
})

const normalizeCredentialSchema = (
  schema: Record<string, unknown>,
): PluginDeclaration['trigger']['subscription_constructor']['credentials_schema'][number] => ({
  name: getString(schema.name),
  label: normalizeI18nObject(getRecord(schema, 'label'), getString(schema.name)),
  description: normalizeI18nObject(getRecord(schema, 'description')),
  type: normalizeFormType(schema.type),
  scope: schema.scope ?? undefined,
  required: getBoolean(schema.required),
  default: schema.default ?? undefined,
  options: getRecordArray(schema, 'options').map(normalizeParameterOption),
  help: normalizeI18nObject(getRecord(schema, 'help')),
  url: getString(schema.url),
  placeholder: normalizeI18nObject(getRecord(schema, 'placeholder')),
})

const normalizeTriggerEventParameter = (
  parameter: Record<string, unknown>,
): PluginDeclaration['trigger']['events'][number]['parameters'][number] => ({
  name: getString(parameter.name),
  label: normalizeI18nObject(getRecord(parameter, 'label'), getString(parameter.name)),
  type: getString(parameter.type),
  auto_generate: parameter.auto_generate ?? undefined,
  template: parameter.template ?? undefined,
  scope: parameter.scope ?? undefined,
  required: getBoolean(parameter.required),
  multiple: getBoolean(parameter.multiple),
  default: parameter.default ?? '',
  min: getNumber(parameter.min),
  max: getNumber(parameter.max),
  precision: getNumber(parameter.precision),
  options: getRecordArray(parameter, 'options').map(normalizeParameterOption),
  description: normalizeI18nObject(getRecord(parameter, 'description')),
})

const normalizeTriggerEvent = (
  event: Record<string, unknown>,
): PluginDeclaration['trigger']['events'][number] => {
  const identity = getRecord(event, 'identity')
  const name = getString(event.name)

  return {
    name,
    identity: {
      author: getString(identity?.author),
      name: getString(identity?.name) || name,
      label: normalizeI18nObject(getRecord(identity, 'label'), name),
      provider: getString(identity?.provider) || undefined,
    },
    description: normalizeI18nObject(getRecord(event, 'description'), name),
    parameters: getRecordArray(event, 'parameters').map(normalizeTriggerEventParameter),
    output_schema: getRecord(event, 'output_schema') ?? {},
  }
}

const createEmptyTrigger = (name: string): PluginDeclaration['trigger'] => ({
  events: [],
  identity: {
    author: '',
    name,
    label: normalizeI18nObject(undefined, name),
    description: normalizeI18nObject(undefined),
    icon: '',
    tags: [],
  },
  subscription_constructor: {
    credentials_schema: [],
    oauth_schema: {
      client_schema: [],
      credentials_schema: [],
    },
    parameters: [],
  },
  subscription_schema: [],
})

const normalizePluginTriggerDeclaration = (
  value: unknown,
  fallbackName: string,
): PluginDeclaration['trigger'] => {
  if (!isRecord(value)) return createEmptyTrigger(fallbackName)

  const identity = getRecord(value, 'identity')
  const subscriptionConstructor = getRecord(value, 'subscription_constructor')
  const oauthSchema = getRecord(subscriptionConstructor, 'oauth_schema')

  return {
    events: getRecordArray(value, 'events').map(normalizeTriggerEvent),
    identity: {
      author: getString(identity?.author),
      name: getString(identity?.name) || fallbackName,
      label: normalizeI18nObject(getRecord(identity, 'label'), fallbackName),
      description: normalizeI18nObject(getRecord(identity, 'description')),
      icon: getString(identity?.icon),
      tags: getStringArray(identity?.tags),
    },
    subscription_constructor: {
      credentials_schema: getRecordArray(subscriptionConstructor, 'credentials_schema').map(
        normalizeCredentialSchema,
      ),
      oauth_schema: {
        client_schema: getRecordArray(oauthSchema, 'client_schema').map(normalizeCredentialSchema),
        credentials_schema: getRecordArray(oauthSchema, 'credentials_schema').map(
          normalizeCredentialSchema,
        ),
      },
      parameters: getRecordArray(subscriptionConstructor, 'parameters').map(
        normalizeParameterSchema,
      ),
    },
    subscription_schema: getRecordArray(value, 'subscription_schema').map(normalizeParameterSchema),
  }
}

const normalizePluginDeclaration = (plugin: PluginInstallationItemResponse): PluginDeclaration => {
  const { declaration } = plugin
  return {
    plugin_unique_identifier: plugin.plugin_unique_identifier,
    version: declaration.version,
    author: declaration.author ?? '',
    icon: declaration.icon,
    icon_dark: declaration.icon_dark ?? undefined,
    name: declaration.name,
    category: normalizePluginCategory(declaration.category),
    label: normalizeI18nObject(declaration.label, declaration.name),
    description: normalizeI18nObject(declaration.description, declaration.name),
    created_at: declaration.created_at,
    resource: declaration.resource,
    plugins: declaration.plugins,
    verified: declaration.verified ?? false,
    endpoint: normalizePluginEndpointDeclaration(declaration.endpoint),
    tool: normalizePluginToolDeclaration(declaration.tool),
    datasource: normalizePluginToolDeclaration(declaration.datasource),
    model: declaration.model,
    tags: declaration.tags ?? [],
    agent_strategy: declaration.agent_strategy,
    meta: {
      version: getString(declaration.meta.version) || declaration.version,
      minimum_dify_version: getString(declaration.meta.minimum_dify_version) || undefined,
    },
    trigger: normalizePluginTriggerDeclaration(declaration.trigger, declaration.name),
  }
}

export const normalizeInstalledPluginDetail = (
  plugin: PluginInstallationItemResponse,
): PluginDetail => {
  const declaration = normalizePluginDeclaration(plugin)

  return {
    id: plugin.id,
    created_at: plugin.created_at,
    updated_at: plugin.updated_at,
    name: declaration.name,
    plugin_id: plugin.plugin_id,
    plugin_unique_identifier: plugin.plugin_unique_identifier,
    declaration,
    installation_id: plugin.id,
    tenant_id: plugin.tenant_id,
    endpoints_setups: plugin.endpoints_setups,
    endpoints_active: plugin.endpoints_active,
    version: plugin.version,
    latest_version: plugin.version,
    latest_unique_identifier: plugin.plugin_unique_identifier,
    source: normalizePluginSource(plugin.source),
    meta: normalizePluginMeta(plugin.meta),
    status: 'active',
    deprecated_reason: '',
    alternative_plugin_id: '',
  }
}

const isUnfinishedPluginTask = (task: PluginTask) =>
  task.status === TaskStatus.pending || task.status === TaskStatus.running

const normalizeStartedPluginTask = (task: PluginTaskStart): PluginTask => ({
  ...task,
  plugins: task.plugins.map((plugin) => ({
    ...plugin,
    taskId: plugin.taskId || task.id,
  })),
})

const upsertStartedPluginTask = (queryClient: QueryClient, response: InstallPackageResponse) => {
  if (!response.task) return

  const startedTask = normalizeStartedPluginTask(response.task)

  queryClient.setQueryData<PluginTaskListResponse>(usePluginTaskListKey, (previous) => {
    if (!previous) return { tasks: [startedTask] }

    const existingTaskIndex = previous.tasks.findIndex((task) => task.id === startedTask.id)
    if (existingTaskIndex === -1) return { tasks: [startedTask, ...previous.tasks] }

    return {
      tasks: previous.tasks.map((task) => (task.id === startedTask.id ? startedTask : task)),
    }
  })
}

const preserveLocalUnfinishedPluginTasks = (
  previousData: PluginTaskListResponse | undefined,
  nextData: PluginTaskListResponse,
) => {
  if (!previousData) return nextData

  const nextTaskIds = new Set(nextData.tasks.map((task) => task.id))
  const missingUnfinishedTasks = previousData.tasks.filter(
    (task) => !nextTaskIds.has(task.id) && isUnfinishedPluginTask(task),
  )
  if (!missingUnfinishedTasks.length) return nextData

  return {
    ...nextData,
    tasks: [...missingUnfinishedTasks, ...nextData.tasks],
  }
}

export const useCheckInstalled = ({
  pluginIds,
  enabled,
}: {
  pluginIds: string[]
  enabled: boolean
}) => {
  return useQuery(
    consoleQuery.workspaces.current.plugin.list.installations.ids.post.queryOptions({
      input: { body: { plugin_ids: pluginIds } },
      enabled,
      staleTime: 0,
      select: (response) => ({
        plugins: response.plugins.map(normalizeInstalledPluginDetail),
      }),
    }),
  )
}

export const useInvalidateCheckInstalled = () => {
  const queryClient = useQueryClient()
  return () => {
    queryClient.invalidateQueries({
      queryKey: consoleQuery.workspaces.current.plugin.list.installations.ids.post.key(),
    })
  }
}

const useRecommendedMarketplacePluginsKey = [NAME_SPACE, 'recommendedMarketplacePlugins']
const useRecommendedMarketplacePlugins = ({
  collection = '__recommended-plugins-tools',
  enabled = true,
  limit = 15,
}: {
  collection?: string
  enabled?: boolean
  limit?: number
} = {}) => {
  return useQuery<Plugin[]>({
    queryKey: [...useRecommendedMarketplacePluginsKey, collection, limit],
    queryFn: async () => {
      const response = await postMarketplace<{ data: { plugins: Plugin[] } }>(
        `/collections/${collection}/plugins`,
        {
          body: {
            limit,
          },
        },
      )
      return response.data.plugins.map((plugin) => getFormattedPlugin(plugin))
    },
    enabled,
    staleTime: 60 * 1000,
  })
}

export const useFeaturedToolsRecommendations = (enabled: boolean, limit = 15) => {
  const { data: plugins = [], isLoading } = useRecommendedMarketplacePlugins({
    collection: '__recommended-plugins-tools',
    enabled,
    limit,
  })

  return {
    plugins,
    isLoading,
  }
}

export const useFeaturedTriggersRecommendations = (enabled: boolean, limit = 15) => {
  const { data: plugins = [], isLoading } = useRecommendedMarketplacePlugins({
    collection: '__recommended-plugins-triggers',
    enabled,
    limit,
  })

  return {
    plugins,
    isLoading,
  }
}

type UseInstalledPluginListOptions = {
  category?: PluginCategoryEnum
  refetchOnMount?: boolean | 'always'
}

export const useInstalledPluginList = (
  disable?: boolean,
  pageSize = 100,
  options?: UseInstalledPluginListOptions,
) => {
  const category = options?.category
  const fetchPlugins = async ({ pageParam = 1 }) => {
    const path = category
      ? `/workspaces/current/plugin/${category}/list`
      : '/workspaces/current/plugin/list'

    if (category)
      return get<InstalledPluginCategoryListResponse>(
        `${path}?page=${pageParam}&page_size=${pageSize}`,
      )

    return get<InstalledPluginListWithTotalResponse>(
      `${path}?page=${pageParam}&page_size=${pageSize}`,
    )
  }

  const { data, error, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isSuccess } =
    useInfiniteQuery({
      enabled: !disable,
      queryKey: category ? [...useInstalledPluginListKey, category] : useInstalledPluginListKey,
      queryFn: fetchPlugins,
      getNextPageParam: (lastPage, pages) => {
        if (category)
          return 'has_more' in lastPage && lastPage.has_more ? pages.length + 1 : undefined

        const totalItems = 'total' in lastPage ? lastPage.total : 0
        const currentPage = pages.length
        const itemsLoaded = currentPage * pageSize

        if (itemsLoaded >= totalItems) return

        return currentPage + 1
      },
      initialPageParam: 1,
      refetchOnMount: options?.refetchOnMount,
    })

  const plugins = data?.pages.flatMap((page) => page.plugins) ?? []
  const firstPage = data?.pages[0]
  const builtinTools = firstPage && 'builtin_tools' in firstPage ? firstPage.builtin_tools : []
  const total = data?.pages[0] && 'total' in data.pages[0] ? data.pages[0].total : plugins.length

  return {
    data: disable
      ? undefined
      : {
          plugins,
          builtin_tools: builtinTools,
          total,
        },
    isLastPage: !hasNextPage,
    loadNextPage: () => {
      fetchNextPage()
    },
    isLoading,
    isFetching: isFetchingNextPage,
    error,
    isSuccess,
  }
}

export const useInvalidateInstalledPluginList = () => {
  const queryClient = useQueryClient()
  const invalidateAllBuiltInTools = useInvalidateAllBuiltInTools()
  return () => {
    queryClient.invalidateQueries({
      queryKey: useInstalledPluginListKey,
    })
    invalidateAllBuiltInTools()
  }
}

export const useInstallPackageFromMarketPlace = (
  options?: MutateOptions<InstallPackageResponse, Error, string>,
) => {
  const queryClient = useQueryClient()
  return useMutation({
    ...options,
    mutationFn: (uniqueIdentifier: string) => {
      return post<InstallPackageResponse>('/workspaces/current/plugin/install/marketplace', {
        body: { plugin_unique_identifiers: [uniqueIdentifier] },
      })
    },
    onSuccess: (data, variables, context, mutation) => {
      upsertStartedPluginTask(queryClient, data)
      options?.onSuccess?.(data, variables, context, mutation)
    },
  })
}

export const useUpdatePackageFromMarketPlace = (
  options?: MutateOptions<InstallPackageResponse, Error, object>,
) => {
  const queryClient = useQueryClient()
  return useMutation({
    ...options,
    mutationFn: (body: object) => {
      return post<InstallPackageResponse>('/workspaces/current/plugin/upgrade/marketplace', {
        body,
      })
    },
    onSuccess: (data, variables, context, mutation) => {
      upsertStartedPluginTask(queryClient, data)
      options?.onSuccess?.(data, variables, context, mutation)
    },
  })
}

export const usePluginDeclarationFromMarketPlace = (pluginUniqueIdentifier: string) => {
  return useQuery({
    queryKey: [NAME_SPACE, 'pluginDeclaration', pluginUniqueIdentifier],
    queryFn: () =>
      get<{ manifest: PluginDeclaration }>('/workspaces/current/plugin/marketplace/pkg', {
        params: { plugin_unique_identifier: pluginUniqueIdentifier },
      }),
    enabled: !!pluginUniqueIdentifier,
  })
}

export const useVersionListOfPlugin = (pluginID: string) => {
  return useQuery<{ data: VersionListResponse }>({
    enabled: !!pluginID,
    queryKey: [NAME_SPACE, 'versions', pluginID],
    queryFn: () =>
      getMarketplace<{ data: VersionListResponse }>(`/plugins/${pluginID}/versions`, {
        params: { page: 1, page_size: 100 },
      }),
  })
}

export const useInstallPackageFromLocal = () => {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: (uniqueIdentifier: string) => {
      return post<InstallPackageResponse>('/workspaces/current/plugin/install/pkg', {
        body: { plugin_unique_identifiers: [uniqueIdentifier] },
      })
    },
    onSuccess: (data) => {
      upsertStartedPluginTask(queryClient, data)
    },
  })
}

export const useInstallPackageFromGitHub = () => {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: ({
      repoUrl,
      selectedVersion,
      selectedPackage,
      uniqueIdentifier,
    }: {
      repoUrl: string
      selectedVersion: string
      selectedPackage: string
      uniqueIdentifier: string
    }) => {
      return post<InstallPackageResponse>('/workspaces/current/plugin/install/github', {
        body: {
          repo: repoUrl,
          version: selectedVersion,
          package: selectedPackage,
          plugin_unique_identifier: uniqueIdentifier,
        },
      })
    },
    onSuccess: (data) => {
      upsertStartedPluginTask(queryClient, data)
    },
  })
}

export const useUploadGitHub = (payload: { repo: string; version: string; package: string }) => {
  return useQuery({
    queryKey: [NAME_SPACE, 'uploadGitHub', payload],
    queryFn: () =>
      post<uploadGitHubResponse>('/workspaces/current/plugin/upload/github', {
        body: payload,
      }),
    retry: 0,
  })
}

export const useInstallOrUpdate = ({
  onSuccess,
}: {
  onSuccess?: (res: InstallStatusResponse[]) => void
}) => {
  const queryClient = useQueryClient()
  const { mutateAsync: updatePackageFromMarketPlace } = useUpdatePackageFromMarketPlace()

  return useMutation({
    mutationFn: (data: {
      payload: Dependency[]
      plugin: Plugin[]
      installedInfo: Record<string, VersionInfo>
    }) => {
      const { payload, plugin, installedInfo } = data

      return Promise.all(
        payload.map(async (item, i) => {
          try {
            const orgAndName = `${plugin[i]?.org || plugin[i]?.author}/${plugin[i]?.name}`
            const installedPayload = installedInfo[orgAndName]
            const isInstalled = !!installedPayload
            let uniqueIdentifier = ''
            let taskId = ''
            let isFinishedInstallation = false

            if (item.type === 'github') {
              const data = item as GitHubItemAndMarketPlaceDependency
              // From local bundle don't have data.value.github_plugin_unique_identifier
              uniqueIdentifier = data.value.github_plugin_unique_identifier!
              if (!uniqueIdentifier) {
                const { unique_identifier } = await post<uploadGitHubResponse>(
                  '/workspaces/current/plugin/upload/github',
                  {
                    body: {
                      repo: data.value.repo!,
                      version: data.value.release! || data.value.version!,
                      package: data.value.packages! || data.value.package!,
                    },
                  },
                )
                uniqueIdentifier = data.value.github_plugin_unique_identifier! || unique_identifier
                // has the same version, but not installed
                if (uniqueIdentifier === installedPayload?.uniqueIdentifier) {
                  return {
                    status: TaskStatus.success,
                    taskId: '',
                    uniqueIdentifier: '',
                  }
                }
              }
              if (!isInstalled) {
                const response = await post<InstallPackageResponse>(
                  '/workspaces/current/plugin/install/github',
                  {
                    body: {
                      repo: data.value.repo!,
                      version: data.value.release! || data.value.version!,
                      package: data.value.packages! || data.value.package!,
                      plugin_unique_identifier: uniqueIdentifier,
                    },
                  },
                )
                upsertStartedPluginTask(queryClient, response)
                const { task_id, all_installed } = response
                taskId = task_id
                isFinishedInstallation = all_installed
              }
            }
            if (item.type === 'marketplace') {
              const data = item as GitHubItemAndMarketPlaceDependency
              uniqueIdentifier =
                data.value.marketplace_plugin_unique_identifier! ||
                (plugin[i]?.latest_package_identifier ?? '') ||
                (plugin[i]?.plugin_id ?? '')
              if (uniqueIdentifier === installedPayload?.uniqueIdentifier) {
                return {
                  status: TaskStatus.success,
                  taskId: '',
                  uniqueIdentifier: '',
                }
              }
              if (!isInstalled) {
                const response = await post<InstallPackageResponse>(
                  '/workspaces/current/plugin/install/marketplace',
                  {
                    body: {
                      plugin_unique_identifiers: [uniqueIdentifier],
                    },
                  },
                )
                upsertStartedPluginTask(queryClient, response)
                const { task_id, all_installed } = response
                taskId = task_id
                isFinishedInstallation = all_installed
              }
            }
            if (item.type === 'package') {
              const data = item as PackageDependency
              uniqueIdentifier = data.value.unique_identifier
              if (uniqueIdentifier === installedPayload?.uniqueIdentifier) {
                return {
                  status: TaskStatus.success,
                  taskId: '',
                  uniqueIdentifier: '',
                }
              }
              if (!isInstalled) {
                const response = await post<InstallPackageResponse>(
                  '/workspaces/current/plugin/install/pkg',
                  {
                    body: {
                      plugin_unique_identifiers: [uniqueIdentifier],
                    },
                  },
                )
                upsertStartedPluginTask(queryClient, response)
                const { task_id, all_installed } = response
                taskId = task_id
                isFinishedInstallation = all_installed
              }
            }
            if (isInstalled) {
              if (item.type === 'package') {
                await uninstallPlugin(installedPayload.installedId)
                const response = await post<InstallPackageResponse>(
                  '/workspaces/current/plugin/install/pkg',
                  {
                    body: {
                      plugin_unique_identifiers: [uniqueIdentifier],
                    },
                  },
                )
                upsertStartedPluginTask(queryClient, response)
                const { task_id, all_installed } = response
                taskId = task_id
                isFinishedInstallation = all_installed
              } else {
                const response = await updatePackageFromMarketPlace({
                  original_plugin_unique_identifier: installedPayload?.uniqueIdentifier,
                  new_plugin_unique_identifier: uniqueIdentifier,
                })
                const { task_id, all_installed } = response
                taskId = task_id
                isFinishedInstallation = all_installed
              }
            }
            if (isFinishedInstallation) {
              return {
                status: TaskStatus.success,
                taskId: '',
                uniqueIdentifier: '',
              }
            } else {
              return {
                status: TaskStatus.running,
                taskId,
                uniqueIdentifier,
              }
            }
          } catch {
            return Promise.resolve({ status: TaskStatus.failed, taskId: '', uniqueIdentifier: '' })
          }
        }),
      )
    },
    onSuccess,
  })
}

export const useDebugKey = () => {
  return useQuery({
    queryKey: [NAME_SPACE, 'debugKey'],
    queryFn: () => get<DebugInfoTypes>('/workspaces/current/plugin/debugging-key'),
  })
}

type PluginAutoUpgradeSettingsResponse = {
  category: PluginCategoryEnum
  auto_upgrade: AutoUpdateConfig
}

type MarketplacePluginInfoRequest = {
  organization?: string
  plugin?: string
  version?: string
}

const useReferenceSettingKey = [NAME_SPACE, 'referenceSettings']
const usePluginPermissionSettingsKey = [...useReferenceSettingKey, 'permission']
const usePluginAutoUpgradeSettingsKey = [...useReferenceSettingKey, 'autoUpgrade']
const pluginAutoUpgradeSettingsQueryKey = (category: PluginCategoryEnum) => [
  ...usePluginAutoUpgradeSettingsKey,
  category,
]

const areStringArraysEqual = (left: string[], right: string[]) => {
  return left.length === right.length && left.every((value, index) => value === right[index])
}

const arePermissionsEqual = (left: Permissions | undefined, right: Permissions | undefined) => {
  return (
    left?.install_permission === right?.install_permission &&
    left?.debug_permission === right?.debug_permission
  )
}

const areAutoUpgradeSettingsEqual = (
  left: AutoUpdateConfig | undefined,
  right: AutoUpdateConfig | undefined,
) => {
  return (
    left?.strategy_setting === right?.strategy_setting &&
    left?.upgrade_time_of_day === right?.upgrade_time_of_day &&
    left?.upgrade_mode === right?.upgrade_mode &&
    areStringArraysEqual(left?.exclude_plugins ?? [], right?.exclude_plugins ?? []) &&
    areStringArraysEqual(left?.include_plugins ?? [], right?.include_plugins ?? [])
  )
}

// legacy plugin permission
// export const hasPluginPermission = (permission: PermissionType | undefined, isAdmin: boolean) => {
//   if (!permission)
//     return false

//   if (permission === PermissionType.noOne)
//     return false

//   if (permission === PermissionType.everyone)
//     return true

//   return isAdmin
// }

export const usePluginPermissionSettings = () => {
  return useQuery({
    queryKey: usePluginPermissionSettingsKey,
    queryFn: () => get<Permissions>('/workspaces/current/plugin/permission/fetch'),
  })
}

export const usePluginAutoUpgradeSettings = (category: PluginCategoryEnum) => {
  return useQuery({
    queryKey: pluginAutoUpgradeSettingsQueryKey(category),
    queryFn: () =>
      get<PluginAutoUpgradeSettingsResponse>('/workspaces/current/plugin/auto-upgrade/fetch', {
        params: { category },
      }),
    staleTime: 60 * 1000,
  })
}

export const useInvalidateReferenceSettings = () => {
  const queryClient = useQueryClient()
  return () => {
    queryClient.invalidateQueries({
      queryKey: useReferenceSettingKey,
    })
  }
}

export const useMutationPluginPermissionSettings = ({
  onSuccess,
}: {
  onSuccess?: () => void
} = {}) => {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (payload: Permissions) => {
      return post('/workspaces/current/plugin/permission/change', { body: payload })
    },
    onMutate: async (payload) => {
      await queryClient.cancelQueries({ queryKey: usePluginPermissionSettingsKey })
      const previousPermission = queryClient.getQueryData<Permissions>(
        usePluginPermissionSettingsKey,
      )
      const hadPreviousPermission = previousPermission !== undefined

      queryClient.setQueryData(usePluginPermissionSettingsKey, payload)

      return { previousPermission, hadPreviousPermission }
    },
    onError: (_error, _payload, context) => {
      if (context?.hadPreviousPermission)
        queryClient.setQueryData(usePluginPermissionSettingsKey, context.previousPermission)
      else queryClient.removeQueries({ queryKey: usePluginPermissionSettingsKey })
    },
    onSuccess: () => {
      onSuccess?.()
    },
  })
}

export const useMutationPluginAutoUpgradeSettings = ({
  category,
  onSuccess,
}: {
  category: PluginCategoryEnum
  onSuccess?: () => void
}) => {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (payload: AutoUpdateConfig) => {
      return post('/workspaces/current/plugin/auto-upgrade/change', {
        body: {
          category,
          auto_upgrade: payload,
        },
      })
    },
    onMutate: async (payload) => {
      const queryKey = pluginAutoUpgradeSettingsQueryKey(category)
      await queryClient.cancelQueries({ queryKey })
      const previousAutoUpgrade =
        queryClient.getQueryData<PluginAutoUpgradeSettingsResponse>(queryKey)
      const hadPreviousAutoUpgrade = previousAutoUpgrade !== undefined

      queryClient.setQueryData(pluginAutoUpgradeSettingsQueryKey(category), {
        category,
        auto_upgrade: payload,
      } satisfies PluginAutoUpgradeSettingsResponse)

      return { previousAutoUpgrade, hadPreviousAutoUpgrade }
    },
    onError: (_error, _payload, context) => {
      if (context?.hadPreviousAutoUpgrade)
        queryClient.setQueryData(
          pluginAutoUpgradeSettingsQueryKey(category),
          context.previousAutoUpgrade,
        )
      else queryClient.removeQueries({ queryKey: pluginAutoUpgradeSettingsQueryKey(category) })
    },
    onSuccess: () => {
      onSuccess?.()
    },
  })
}

export const useMutationReferenceSettings = ({
  category,
  currentReferenceSetting,
  onSuccess,
}: {
  category: PluginCategoryEnum
  currentReferenceSetting?: ReferenceSetting
  onSuccess?: () => void
}) => {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (payload: ReferenceSetting) => {
      const mutations: Array<Promise<unknown>> = []

      if (!arePermissionsEqual(payload.permission, currentReferenceSetting?.permission))
        mutations.push(
          post('/workspaces/current/plugin/permission/change', { body: payload.permission }),
        )

      if (
        !areAutoUpgradeSettingsEqual(payload.auto_upgrade, currentReferenceSetting?.auto_upgrade)
      ) {
        mutations.push(
          post('/workspaces/current/plugin/auto-upgrade/change', {
            body: {
              category,
              auto_upgrade: payload.auto_upgrade,
            },
          }),
        )
      }

      return Promise.all(mutations)
    },
    onMutate: async (payload) => {
      const shouldUpdatePermission = !arePermissionsEqual(
        payload.permission,
        currentReferenceSetting?.permission,
      )
      const shouldUpdateAutoUpgrade = !areAutoUpgradeSettingsEqual(
        payload.auto_upgrade,
        currentReferenceSetting?.auto_upgrade,
      )
      const autoUpgradeQueryKey = pluginAutoUpgradeSettingsQueryKey(category)

      await Promise.all([
        shouldUpdatePermission
          ? queryClient.cancelQueries({ queryKey: usePluginPermissionSettingsKey })
          : Promise.resolve(),
        shouldUpdateAutoUpgrade
          ? queryClient.cancelQueries({ queryKey: autoUpgradeQueryKey })
          : Promise.resolve(),
      ])

      const previousPermission = queryClient.getQueryData<Permissions>(
        usePluginPermissionSettingsKey,
      )
      const previousAutoUpgrade =
        queryClient.getQueryData<PluginAutoUpgradeSettingsResponse>(autoUpgradeQueryKey)
      const hadPreviousPermission = previousPermission !== undefined
      const hadPreviousAutoUpgrade = previousAutoUpgrade !== undefined

      if (shouldUpdatePermission)
        queryClient.setQueryData(usePluginPermissionSettingsKey, payload.permission)

      if (shouldUpdateAutoUpgrade) {
        queryClient.setQueryData(autoUpgradeQueryKey, {
          category,
          auto_upgrade: payload.auto_upgrade,
        } satisfies PluginAutoUpgradeSettingsResponse)
      }

      return {
        previousPermission,
        previousAutoUpgrade,
        hadPreviousPermission,
        hadPreviousAutoUpgrade,
        shouldUpdatePermission,
        shouldUpdateAutoUpgrade,
      }
    },
    onError: (_error, _payload, context) => {
      if (context?.shouldUpdatePermission && context.hadPreviousPermission)
        queryClient.setQueryData(usePluginPermissionSettingsKey, context.previousPermission)
      else if (context?.shouldUpdatePermission)
        queryClient.removeQueries({ queryKey: usePluginPermissionSettingsKey })

      if (context?.shouldUpdateAutoUpgrade && context.hadPreviousAutoUpgrade)
        queryClient.setQueryData(
          pluginAutoUpgradeSettingsQueryKey(category),
          context.previousAutoUpgrade,
        )
      else if (context?.shouldUpdateAutoUpgrade)
        queryClient.removeQueries({ queryKey: pluginAutoUpgradeSettingsQueryKey(category) })
    },
    onSuccess,
  })
}

export const useRemoveAutoUpgrade = () => {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (payload: { plugin_id: string; category: PluginCategoryEnum }) => {
      return post('/workspaces/current/plugin/auto-upgrade/exclude', { body: payload })
    },
    onSuccess: (_data, payload) => {
      queryClient.invalidateQueries({
        queryKey: pluginAutoUpgradeSettingsQueryKey(payload.category),
      })
    },
  })
}

export const useFetchPluginsInMarketPlaceByIds = (
  unique_identifiers: string[],
  options?: QueryOptions<{ data: PluginsFromMarketplaceResponse }>,
) => {
  return useQuery({
    ...options,
    queryKey: [NAME_SPACE, 'fetchPluginsInMarketPlaceByIds', unique_identifiers],
    queryFn: () =>
      postMarketplace<{ data: PluginsFromMarketplaceResponse }>('/plugins/identifier/batch', {
        body: {
          unique_identifiers,
        },
      }),
    enabled: unique_identifiers?.filter((i) => !!i).length > 0,
    retry: 0,
  })
}

export const useFetchPluginsInMarketPlaceByInfo = (infos: MarketplacePluginInfoRequest[]) => {
  return useQuery({
    queryKey: [NAME_SPACE, 'fetchPluginsInMarketPlaceByInfo', infos],
    queryFn: () =>
      postMarketplace<{ data: PluginsFromMarketplaceByInfoResponse }>('/plugins/versions/batch', {
        body: {
          plugin_tuples: infos.map((info) => ({
            org: info.organization,
            name: info.plugin,
            version: info.version,
          })),
        },
      }),
    enabled: infos?.filter((i) => !!i).length > 0,
    retry: 0,
  })
}

export const usePluginTaskList = (category?: PluginCategoryEnum | string) => {
  const initializedRef = useRef(false)
  const queryClient = useQueryClient()
  const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
  const canManagement = hasPermission(workspacePermissionKeys, 'plugin.install')
  const { refreshPluginList } = useRefreshPluginList()
  const query = useQuery<PluginTaskListResponse>({
    enabled: canManagement,
    queryKey: usePluginTaskListKey,
    queryFn: () =>
      get<{ tasks: PluginTask[] }>('/workspaces/current/plugin/tasks?page=1&page_size=100'),
    structuralSharing: (previousData, nextData) =>
      preserveLocalUnfinishedPluginTasks(
        previousData as PluginTaskListResponse | undefined,
        nextData as PluginTaskListResponse,
      ),
    refetchInterval: (lastQuery) => {
      const lastData = lastQuery.state.data
      const taskDone = lastData?.tasks.every(
        (task) => task.status === TaskStatus.success || task.status === TaskStatus.failed,
      )
      return taskDone ? false : 5000
    },
  })
  const { data, isFetched, isRefetching, refetch } = query

  useEffect(() => {
    // After first fetch, refresh plugin list each time all tasks are done
    // Skip initialization period, because the query cache is not updated yet
    if (!initializedRef.current) {
      initializedRef.current = true
      return
    }

    if (isRefetching) return

    const lastData = cloneDeep(data)
    const taskDone = lastData?.tasks.every(
      (task) => task.status === TaskStatus.success || task.status === TaskStatus.failed,
    )
    const taskAllFailed = lastData?.tasks.every((task) => task.status === TaskStatus.failed)
    if (taskDone && lastData?.tasks.length && !taskAllFailed)
      refreshPluginList(category ? { category } : undefined, !category)
  }, [category, data, isRefetching, refreshPluginList])

  const handleRefetch = useCallback(() => {
    refetch()
  }, [refetch])

  const handleInstallTaskStart = useCallback(
    (response: InstallPackageResponse) => {
      if (response.all_installed) return

      upsertStartedPluginTask(queryClient, response)
      refetch()
    },
    [queryClient, refetch],
  )

  return {
    data,
    pluginTasks: data?.tasks || [],
    isFetched,
    handleRefetch,
    handleInstallTaskStart,
  }
}

export const useMutationClearTaskPlugin = () => {
  return useMutation({
    mutationFn: ({ taskId, pluginId }: { taskId: string; pluginId: string }) => {
      const encodedPluginId = encodeURIComponent(pluginId)
      return post<{ success: boolean }>(
        `/workspaces/current/plugin/tasks/${taskId}/delete/${encodedPluginId}`,
      )
    },
  })
}

export const usePluginManifestInfo = (pluginUID: string) => {
  return useQuery({
    enabled: !!pluginUID,
    queryKey: [NAME_SPACE, 'manifest', pluginUID],
    queryFn: () =>
      getMarketplace<{ data: { plugin: PluginInfoFromMarketPlace; version: { version: string } } }>(
        `/plugins/${pluginUID}`,
      ),
    retry: 0,
  })
}

export const useMutationCheckDependencies = () => {
  return useMutation({
    mutationFn: (appId: string) => {
      return get<{ leaked_dependencies: Dependency[] }>(`/apps/imports/${appId}/check-dependencies`)
    },
  })
}

export const useModelInList = (currentProvider?: ModelProvider, modelId?: string) => {
  const provider = currentProvider?.provider
  return useQuery({
    queryKey: ['modelInList', provider, modelId],
    queryFn: async () => {
      if (!modelId || !provider) return false
      try {
        const modelsData = await fetchModelProviderModelList(
          `/workspaces/current/model-providers/${provider}/models`,
        )
        return !!modelId && modelsData.data.some((item) => item.model === modelId)
      } catch {
        return false
      }
    },
    enabled: !!modelId && !!provider,
  })
}

export const usePluginInfo = (providerName?: string) => {
  return useQuery({
    queryKey: ['pluginInfo', providerName],
    queryFn: async () => {
      if (!providerName) return null
      const parts = providerName.split('/')
      const org = parts[0]
      const name = parts[1]
      try {
        const response = await fetchPluginInfoFromMarketPlace({ org: org!, name: name! })
        return response.data.plugin.category === PluginCategoryEnum.model
          ? response.data.plugin
          : null
      } catch {
        return null
      }
    },
    enabled: !!providerName,
  })
}

export const useFetchDynamicOptions = (
  plugin_id: string,
  provider: string,
  action: string,
  parameter: string,
  provider_type?: 'tool' | 'trigger',
  extra?: Record<string, unknown>,
) => {
  return useMutation({
    mutationFn: () =>
      get<{ options: FormOption[] }>('/workspaces/current/plugin/parameters/dynamic-options', {
        params: {
          plugin_id,
          provider,
          action,
          parameter,
          provider_type,
          ...extra,
        },
      }),
  })
}

export const usePluginReadme = ({
  plugin_unique_identifier,
  language,
}: {
  plugin_unique_identifier: string
  language?: string
}) => {
  return useQuery({
    queryKey: ['pluginReadme', plugin_unique_identifier, language],
    queryFn: () =>
      get<{ readme: string }>(
        '/workspaces/current/plugin/readme',
        { params: { plugin_unique_identifier, language } },
        { silent: true },
      ),
    enabled: !!plugin_unique_identifier,
    retry: 0,
  })
}

export const usePluginReadmeAsset = ({
  file_name,
  plugin_unique_identifier,
}: {
  file_name?: string
  plugin_unique_identifier?: string
}) => {
  const normalizedFileName = file_name?.replace(/^\.\/_assets\//, '').replace(/^_assets\//, '')
  const isAssetFile = file_name?.startsWith('./_assets') || file_name?.startsWith('_assets')
  return useQuery({
    queryKey: ['pluginReadmeAsset', plugin_unique_identifier, normalizedFileName],
    queryFn: () =>
      get<Blob>(
        '/workspaces/current/plugin/asset',
        { params: { plugin_unique_identifier, file_name: normalizedFileName } },
        { silent: true },
      ),
    enabled: !!plugin_unique_identifier && !!isAssetFile,
  })
}
