import type { CalendarType, FilterType, GalleryType, KanbanType, MapType, RowColoringInfo, SortType, ViewType } from 'nocodb-sdk'
import {
  ProjectRoles,
  ViewSettingOverrideOptions,
  ViewTypes,
  WorkspaceUserRoles,
  ViewTypes as _ViewTypes,
  getFirstNonPersonalView,
} from 'nocodb-sdk'
import { acceptHMRUpdate, defineStore } from 'pinia'
import { useTitle } from '@vueuse/core'
import type { ViewPageType } from '~/lib/types'
import { getFormattedViewTabTitle } from '~/helpers/parsers/parserHelpers'
import { DlgViewCopyViewConfigFromAnotherView, DlgViewCreate } from '#components'
import { userLocalStorageInfoManager } from '#imports'

// Types and Interfaces
interface RecentView {
  viewName: string
  viewId: string | undefined
  viewType: ViewTypes
  tableID: string
  isDefault: boolean
  baseName: string
  tableName: string
  workspaceId: string
  baseId: string
}

export const useViewsStore = defineStore('viewsStore', () => {
  const { $api, $e, $eventBus } = useNuxtApp()

  const { t } = useI18n()

  const { ncNavigateTo, user } = useGlobal()

  const router = useRouter()

  const { meta: metaKey, control } = useMagicKeys()

  const bases = useBases()

  const { getMeta } = useMetas()

  const tablesStore = useTablesStore()

  const workspaceStore = useWorkspace()

  const { refreshCommandPalette } = useCommandPalette()

  const { isUIAllowed } = useRoles()

  const { sharedView } = useSharedView()

  const { openedProject, activeProjectId } = storeToRefs(bases)

  const { activeWorkspaceId } = storeToRefs(workspaceStore)

  const { activeTable } = storeToRefs(tablesStore)

  const { isFeatureEnabled } = useBetaFeatureToggle()

  const { blockCardFieldHeaderVisibility } = useEeConfig()

  const route = router.currentRoute

  const allRecentViews = ref<RecentView[]>([])

  // Helper function to create composite key: baseId:tableId
  const getViewsKey = (baseId: string, tableId: string) => `${baseId}:${tableId}`

  const viewsByTable = ref<Map<string, ViewType[]>>(new Map())

  const activeSorts = ref<SortType[]>([])

  const activeNestedFilters = ref<FilterType[]>([])

  const isViewsLoading = ref(true)

  const isViewDataLoading = ref(true)

  const isPaginationLoading = ref(false)

  const lastOpenedViewId = ref<string | undefined>(undefined)

  const preFillFormSearchParams = ref('')

  const activeViewRowColorInfo = ref<RowColoringInfo>(defaultRowColorInfo)

  // Computed properties
  const isPublic = computed(() => route.value.meta?.public)

  const recentViews = computed<RecentView[]>(() =>
    allRecentViews.value.filter((f) => f.workspaceId === activeWorkspaceId.value).splice(0, 10),
  )

  const views = computed({
    get: () => {
      if (!tablesStore.activeTableId) return []

      // Try to get the base_id from activeTable
      if (activeTable.value?.base_id) {
        const key = getViewsKey(activeTable.value.base_id, tablesStore.activeTableId)
        return viewsByTable.value.get(key) ?? []
      }

      // Fallback: Search through all keys to find a match for this tableId
      // This handles the case when activeTable.base_id is not yet available (e.g., direct URL load)
      for (const [k, views] of viewsByTable.value) {
        if (k.endsWith(`:${tablesStore.activeTableId}`)) {
          return views
        }
      }

      return []
    },
    set: (value) => {
      if (!tablesStore.activeTableId) return

      // For setting, we need the base_id
      if (!activeTable.value?.base_id) {
        console.warn('Cannot set views without base_id')
        return
      }

      const key = getViewsKey(activeTable.value.base_id, tablesStore.activeTableId)
      if (!value) return viewsByTable.value.delete(key)

      viewsByTable.value.set(key, value)
    },
  })

  const activeViewTitleOrId = computed(() => {
    if (!route.value.params.viewTitle?.length) {
      // find the default view and navigate to it, if not found navigate to the first one
      const defaultView = getFirstNonPersonalView(views.value)

      return defaultView?.id
    }

    return route.value.params.viewTitle
  })

  const openedViewsTab = computed<ViewPageType>(() => {
    // For types in ViewPageType type
    if (!route.value.params?.slugs || route.value.params.slugs?.length <= 1) return 'view'

    if (['field', 'permissions', 'relation', 'api', 'webhook'].includes(route.value.params.slugs[1] as ViewPageType)) {
      return route.value.params.slugs[1] as ViewPageType
    }

    return 'view'
  })

  const activeView = computed<ViewType | undefined>({
    get() {
      if (sharedView.value) return sharedView.value

      if (!activeTable.value) return undefined

      if (!activeViewTitleOrId.value) return undefined

      return (
        views.value.find((v) => v.id === activeViewTitleOrId.value) ??
        views.value.find((v) => v.title === activeViewTitleOrId.value)
      )
    },
    set(_view: ViewType | undefined) {
      if (sharedView.value) {
        sharedView.value = _view
        return
      }

      if (!activeTable.value) return
      if (!_view) return

      const viewIndex =
        views.value.findIndex((v) => v.id === activeViewTitleOrId.value) ??
        views.value.findIndex((v) => v.title === activeViewTitleOrId.value)
      if (viewIndex === -1) return

      views.value[viewIndex] = _view
    },
  })

  const activeViewUrlSlug = computed(() => {
    return route.value.params.slugs?.[0] || ''
  })

  const activeViewReadableUrlSlug = computed(() => {
    if (sharedView.value || !activeView.value) return ''

    return getViewReadableUrlSlug({ tableTitle: activeTable.value?.title, viewOrViewTitle: activeView.value })
  })

  const isActiveViewLocked = computed(() => activeView.value?.lock_type === 'locked')
  const isLockedView = computed(() => activeView.value?.lock_type === 'locked')

  const isActiveViewFieldHeaderVisible = computed(() => {
    // If card field header visibility is not enabled or blocked, return true to show header by default
    if (blockCardFieldHeaderVisibility.value || !isEeUI) return true

    return parseProp((activeView.value?.view as GalleryType | KanbanType)?.meta)?.is_field_header_visible ?? true
  })

  const isShowEveryonePersonalViewsEnabled = computed({
    get: () => {
      if (!isEeUI || !isFeatureEnabled(FEATURE_FLAG.SHOW_EVERYONES_PERSONAL_VIEWS)) {
        return true
      }

      return !!userLocalStorageInfoManager.get(user.value?.id, activeWorkspaceId.value, 'showOtherUserPersonalViews', true)
    },
    set: (value: boolean) => {
      if (!isEeUI || !isFeatureEnabled(FEATURE_FLAG.SHOW_EVERYONES_PERSONAL_VIEWS)) {
        return
      }

      userLocalStorageInfoManager.set(user.value?.id, activeWorkspaceId.value, 'showOtherUserPersonalViews', value)
    },
  })

  const refreshViewTabTitle = createEventHook<void>()

  const loadViews = async ({
    tableId,
    baseId,
    ignoreLoading,
    force,
  }: { tableId?: string; baseId?: string; ignoreLoading?: boolean; force?: boolean } = {}) => {
    const effectiveBaseId = baseId || activeProjectId.value

    if (!effectiveBaseId) {
      console.error('[loadViews] baseId is required but was not provided')
      return
    }

    tableId = tableId ?? tablesStore.activeTableId

    let response
    if (tableId) {
      // Wait for tables to be loaded if they're not available yet
      await until(() => tablesStore.baseTables.get(effectiveBaseId)?.length).toBeTruthy({ timeout: 10000 })

      const table = tablesStore.baseTables.get(effectiveBaseId)?.find((t) => t.id === tableId)
      if (!table) {
        console.warn('Could not find table:', tableId, 'in base:', effectiveBaseId)
        return
      }

      // Use the table's actual base_id (handles cross-base scenarios)
      const tableBaseId = table.base_id || effectiveBaseId

      const key = getViewsKey(tableBaseId, tableId)

      if (!force && viewsByTable.value.get(key)) {
        viewsByTable.value.set(
          key,
          (viewsByTable.value.get(key) ?? []).sort((a, b) => a.order! - b.order!),
        )
        isViewsLoading.value = false
        return viewsByTable.value.get(key)
      }
      if (!ignoreLoading) isViewsLoading.value = true

      response = (
        await $api.internal.getOperation(activeWorkspaceId.value!, tableBaseId, {
          operation: 'viewList',
          tableId,
        })
      ).list as ViewType[]
      if (response) {
        viewsByTable.value.set(
          key,
          response.sort((a, b) => a.order! - b.order!),
        )
      }

      if (!ignoreLoading) isViewsLoading.value = false
    }

    return response
  }

  const navigateToView = async ({
    view,
    baseId,
    tableId,
    tableTitle,
    hardReload,
    doNotSwitchTab,
  }: {
    view: ViewType
    baseId: string
    tableId: string
    tableTitle?: string
    hardReload?: boolean
    doNotSwitchTab?: boolean
  }) => {
    const cmdOrCtrl = isMac() ? metaKey.value : control.value

    const routeName = 'index-typeOrId-baseId-index-index-viewId-viewTitle-slugs'

    let baseIdOrBaseId = baseId

    if (['base'].includes(route.value.params.typeOrId as string)) {
      baseIdOrBaseId = route.value.params.baseId as string
    }

    const slugs = doNotSwitchTab ? router.currentRoute.value.params.slugs || [] : []

    if (ncIsArray(slugs)) {
      ;(slugs as string[])[0] = getViewReadableUrlSlug({ tableTitle, viewOrViewTitle: view })
    }

    if (
      router.currentRoute.value.query &&
      router.currentRoute.value.query.page &&
      router.currentRoute.value.query.page === 'fields'
    ) {
      if (cmdOrCtrl) {
        await navigateTo(
          router.resolve({
            name: routeName,
            params: {
              viewTitle: view.id || '',
              viewId: tableId,
              baseId: baseIdOrBaseId,
              slugs,
            },
            query: router.currentRoute.value.query,
          }).href,
          {
            open: navigateToBlankTargetOpenOption,
          },
        )
      } else {
        await router.push({
          name: routeName,
          params: {
            viewTitle: view.id || '',
            viewId: tableId,
            baseId: baseIdOrBaseId,
            slugs,
          },
          query: router.currentRoute.value.query,
        })
      }
    } else {
      if (cmdOrCtrl) {
        await navigateTo(
          router.resolve({
            name: routeName,
            params: {
              viewTitle: view.id || '',
              viewId: tableId,
              baseId: baseIdOrBaseId,
              slugs,
            },
          }).href,
          {
            open: navigateToBlankTargetOpenOption,
          },
        )
      } else {
        await router.push({
          name: routeName,
          params: {
            viewTitle: view.id || '',
            viewId: tableId,
            baseId: baseIdOrBaseId,
            slugs,
          },
        })
      }
    }

    if (!cmdOrCtrl && hardReload) {
      await router
        .replace({
          name: routeName,
          query: { reload: 'true' },
          params: {
            viewId: tableId,
            baseId: baseIdOrBaseId,
            viewTitle: view.id || '',
            slugs,
          },
        })
        .then(() => {
          router.replace({
            name: routeName,
            query: {},
            params: {
              viewId: tableId,
              viewTitle: view.id || '',
              baseId: baseIdOrBaseId,
              slugs,
            },
          })
        })
    }
  }

  const createView = async (tableId: string, form: CreateViewForm): Promise<ViewType | null> => {
    if (!tableId) return null

    try {
      let data: ViewType | null = null

      switch (form.type) {
        case ViewTypes.GRID:
          data = await $api.internal.postOperation(
            activeWorkspaceId.value!,
            openedProject.value!.id!,
            {
              operation: 'gridViewCreate',
              tableId,
            },
            form,
          )
          break
        case ViewTypes.GALLERY:
          data = await $api.internal.postOperation(
            activeWorkspaceId.value!,
            openedProject.value!.id!,
            {
              operation: 'galleryViewCreate',
              tableId,
            },
            form,
          )
          break
        case ViewTypes.FORM:
          data = await $api.internal.postOperation(
            activeWorkspaceId.value!,
            openedProject.value!.id!,
            {
              operation: 'formViewCreate',
              tableId,
            },
            {
              ...form,
              ...getDefaultViewMetas(ViewTypes.FORM),
            },
          )
          break
        case ViewTypes.KANBAN:
          data = await $api.internal.postOperation(
            activeWorkspaceId.value!,
            openedProject.value!.id!,
            {
              operation: 'kanbanViewCreate',
              tableId,
            },
            form,
          )
          break
        case ViewTypes.MAP:
          data = await $api.internal.postOperation(
            activeWorkspaceId.value!,
            openedProject.value!.id!,
            {
              operation: 'mapViewCreate',
              tableId,
            },
            form,
          )
          break
        case ViewTypes.CALENDAR:
          data = await $api.internal.postOperation(
            activeWorkspaceId.value!,
            openedProject.value!.id!,
            {
              operation: 'calendarViewCreate',
              tableId,
            },
            {
              ...form,
              calendar_range: form.calendar_range.map((range) => ({
                fk_from_column_id: range.fk_from_column_id,
                fk_to_column_id: range.fk_to_column_id,
              })),
            },
          )
          break
      }

      if (data) {
        // Get the base_id for the table
        const table = tablesStore.baseTables.get(activeProjectId.value!)?.find((t) => t.id === tableId)
        if (!table?.base_id) {
          console.warn('Could not find base_id for table:', tableId)
          return null
        }

        const key = getViewsKey(table.base_id, tableId)

        // Add the new view to the store
        const tableViews = viewsByTable.value.get(key) || []

        // Get the first collaborative grid view before
        const oldFirstCollabGridView = getFirstNonPersonalView(tableViews, {
          includeViewType: ViewTypes.GRID,
        })

        viewsByTable.value.set(key, [...tableViews, data])

        // Get the new first collaborative grid view after adding
        const newFirstCollabGridView = getFirstNonPersonalView([...tableViews, data], {
          includeViewType: ViewTypes.GRID,
        })

        // If the first collaborative grid view changed, trigger getMeta
        if (newFirstCollabGridView?.id !== oldFirstCollabGridView?.id && tableId) {
          await getMeta(table.base_id, tableId, true)
        }

        // Refresh command palette
        refreshCommandPalette()

        // Telemetry event
        $e(form.copy_from_id ? 'a:view:duplicate' : 'a:view:create', { view: data.type })

        return data
      }

      return null
    } catch (e: any) {
      console.error(e)
      message.error(await extractSdkResponseErrorMsg(e))
      throw e
    }
  }

  const duplicateView = async (view: ViewType): Promise<ViewType | null> => {
    if (!view?.id || !view?.base_id) return null

    const key = getViewsKey(view.base_id, view.fk_model_id)
    const views = viewsByTable.value.get(key) || []
    const uniqueTitle = generateUniqueTitle(`${view.title} copy`, views, 'title', '_', true)

    const getViewSpecificProps = (sourceView: ViewType) => {
      const baseProps = {
        fk_grp_col_id: null,
        fk_geo_data_col_id: null,
        fk_cover_image_col_id: null,
        calendar_range: [] as Array<{
          fk_from_column_id: string
          fk_to_column_id: string | null
        }>,
      }

      switch (sourceView.type) {
        case ViewTypes.GALLERY:
          return {
            ...baseProps,
            fk_cover_image_col_id: (sourceView.view as GalleryType)?.fk_cover_image_col_id || null,
          }
        case ViewTypes.KANBAN:
          return {
            ...baseProps,
            fk_cover_image_col_id: (sourceView.view as KanbanType)?.fk_cover_image_col_id || null,
            fk_grp_col_id: (sourceView.view as KanbanType)?.fk_grp_col_id || null,
          }
        case ViewTypes.MAP:
          return {
            ...baseProps,
            fk_geo_data_col_id: (sourceView.view as MapType)?.fk_geo_data_col_id || null,
          }
        case ViewTypes.CALENDAR:
          return {
            ...baseProps,
            calendar_range:
              (sourceView.view as CalendarType)?.calendar_range?.map((range) => ({
                fk_from_column_id: range.fk_from_column_id as string,
                fk_to_column_id: range.fk_to_column_id as string,
              })) || [],
          }
        default:
          return baseProps
      }
    }

    const viewSpecificProps = getViewSpecificProps(view)

    const duplicateForm: CreateViewForm = {
      title: uniqueTitle,
      type: view.type,
      description: view.description || '',
      copy_from_id: view.id!,
      row_coloring_mode: view.row_coloring_mode!,
      meta: parseProp(view.meta)?.rowColoringInfo ? { rowColoringInfo: parseProp(view.meta).rowColoringInfo } : undefined,
      ...viewSpecificProps,
    }

    return await createView(view.fk_model_id, duplicateForm)
  }

  const deleteView = async (view: ViewType) => {
    if (!view?.id || !view?.base_id) return

    const activeViewId = activeView.value?.id

    try {
      await $api.internal.postOperation(
        activeWorkspaceId.value!,
        openedProject.value!.id!,
        {
          operation: 'viewDelete',
          viewId: view.id,
        },
        {},
      )

      const key = getViewsKey(view.base_id, view.fk_model_id)

      // Remove view from the viewsByTable map
      const tableViews = viewsByTable.value.get(key) || []

      // Get the first collaborative grid view before delete
      const oldFirstCollabGridView = getFirstNonPersonalView(tableViews, {
        includeViewType: ViewTypes.GRID,
      })

      const updatedViews = tableViews.filter((v) => v.id !== view.id)
      viewsByTable.value.set(key, updatedViews)

      // Get the new first collaborative grid view after delete
      const newFirstCollabGridView = getFirstNonPersonalView(updatedViews, {
        includeViewType: ViewTypes.GRID,
      })

      // If the first collaborative grid view changed after deletion, trigger getMeta
      if (newFirstCollabGridView?.id !== oldFirstCollabGridView?.id && view.fk_model_id) {
        await getMeta(view.base_id, view.fk_model_id, true)
      }

      // Remove from recent views
      removeFromRecentViews({
        viewId: view.id,
        tableId: view.fk_model_id,
        baseId: view.base_id,
      })

      // Refresh command palette
      refreshCommandPalette()

      // Telemetry event
      $e('a:view:delete', { view: view.type })

      // If we deleted the active view, navigate to default or first view
      if (activeViewId === view.id) {
        const key = getViewsKey(view.base_id, view.fk_model_id)
        const remainingViews = viewsByTable.value.get(key) || []
        const defaultView = remainingViews[0]

        if (defaultView && activeTable.value) {
          await navigateToView({
            view: defaultView,
            baseId: activeTable.value.base_id!,
            tableId: view.fk_model_id,
          })
        } else {
          ncNavigateTo({
            workspaceId: activeWorkspaceId.value,
            baseId: view.base_id,
          })
        }
      }

      return true
    } catch (e: any) {
      console.error(e)
      message.error(await extractSdkResponseErrorMsg(e))
      throw e
    }
  }

  const updateView = async (
    viewId: string,
    updates: Partial<ViewType>,
    extra?: {
      is_default_view?: boolean
    },
  ): Promise<ViewType | null> => {
    try {
      const updatedView = await $api.internal.postOperation(
        activeWorkspaceId.value!,
        openedProject.value!.id!,
        {
          operation: 'viewUpdate',
          viewId,
        },
        updates,
      )

      // Find the table and update the view in the store
      const tableId = activeView.value?.fk_model_id
      const baseId = activeView.value?.base_id
      if (tableId && baseId) {
        const key = getViewsKey(baseId, tableId)
        const tableViews = viewsByTable.value.get(key) || []
        const viewIndex = tableViews.findIndex((v) => v.id === viewId)

        if (viewIndex !== -1) {
          if (extra?.is_default_view && tableId) {
            await getMeta(baseId, tableId, true)
          }

          // Replace with the response from API
          tableViews[viewIndex] = updatedView
          viewsByTable.value.set(key, [...tableViews])

          // Update recent views if title changed
          if (updatedView.title) {
            allRecentViews.value = allRecentViews.value.map((rv) => {
              if (rv.viewId === viewId && rv.tableID === tableId) {
                rv.viewName = updatedView.title
              }
              return rv
            })
          }

          refreshCommandPalette()

          return updatedView
        }
      }

      return updatedView
    } catch (e: any) {
      console.error(e)
      throw e
    }
  }

  const updateViewMeta = async (
    viewId: string,
    viewType: ViewTypes,
    updates: Record<string, any>,
    args?: {
      skipNetworkCall?: boolean
    },
  ): Promise<ViewType | null> => {
    try {
      let updatedView

      if (!args?.skipNetworkCall) {
        switch (viewType) {
          case ViewTypes.GRID:
            updatedView = await $api.internal.postOperation(
              activeView.value!.fk_workspace_id!,
              activeView.value!.base_id!,
              { operation: 'gridViewUpdate', viewId },
              updates,
            )
            break
          case ViewTypes.GALLERY:
            updatedView = await $api.internal.postOperation(
              activeView.value!.fk_workspace_id!,
              activeView.value!.base_id!,
              { operation: 'galleryViewUpdate', viewId },
              updates,
            )
            break
          case ViewTypes.KANBAN:
            updatedView = await $api.internal.postOperation(
              activeView.value!.fk_workspace_id!,
              activeView.value!.base_id!,
              { operation: 'kanbanViewUpdate', viewId },
              updates,
            )
            break
          case ViewTypes.MAP:
            updatedView = await $api.internal.postOperation(
              activeView.value!.fk_workspace_id!,
              activeView.value!.base_id!,
              { operation: 'mapViewUpdate', viewId },
              updates,
            )
            break
          case ViewTypes.CALENDAR:
            updatedView = await $api.internal.postOperation(
              activeView.value!.fk_workspace_id!,
              activeView.value!.base_id!,
              { operation: 'calendarViewUpdate', viewId },
              updates,
            )
            break
          case ViewTypes.FORM:
            updatedView = await $api.internal.postOperation(
              activeView.value!.fk_workspace_id!,
              activeView.value!.base_id!,
              { operation: 'formViewUpdate', viewId },
              updates,
            )
            break
          default:
            throw new Error(`Unsupported view type for meta update: ${viewType}`)
        }
      } else {
        updatedView = {
          ...activeView.value,
          view: {
            ...(activeView.value || {}).view,
            ...updates,
          },
        }
      }

      // Find the table and update the view in the store
      const tableId = activeView.value?.fk_model_id
      const baseId = activeView.value?.base_id
      if (tableId && baseId) {
        const key = getViewsKey(baseId, tableId)
        const tableViews = viewsByTable.value.get(key) || []
        const viewIndex = tableViews.findIndex((v) => v.id === viewId)

        if (viewIndex !== -1) {
          tableViews[viewIndex] = updatedView
          viewsByTable.value.set(key, [...tableViews])
        }
      }

      if (isPublic.value) {
        sharedView.value = {
          ...sharedView.value,
          view: {
            ...(sharedView.value?.view || {}),
            ...updates,
          },
        }
      }

      refreshCommandPalette()

      return updatedView
    } catch (e: any) {
      console.error(e)
      throw e
    }
  }

  const onViewsTabChange = (page: ViewPageType) => {
    router.push({
      name: 'index-typeOrId-baseId-index-index-viewId-viewTitle-slugs',
      params: {
        typeOrId: route.value.params.typeOrId,
        baseId: route.value.params.baseId,
        viewId: route.value.params.viewId,
        viewTitle: activeViewTitleOrId.value,
        slugs: [activeViewReadableUrlSlug.value, ...(page !== 'view' ? [page] : [])],
      },
    })
  }

  const changeView = async ({ viewId, tableId, baseId }: { viewId: string | null; tableId: string; baseId: string }) => {
    const routeName = 'index-typeOrId-baseId-index-index-viewId-viewTitle'
    await router.push({ name: routeName, params: { viewTitle: viewId || '', viewId: tableId, baseId } })
  }

  function removeFromRecentViews({ viewId, tableId, baseId }: { viewId?: string | undefined; tableId: string; baseId?: string }) {
    if (baseId && !viewId && !tableId) {
      allRecentViews.value = allRecentViews.value.filter((f) => f.baseId !== baseId)
    } else if (baseId && tableId && !viewId) {
      allRecentViews.value = allRecentViews.value.filter((f) => f.baseId !== baseId || f.tableID !== tableId)
    } else if (tableId && viewId) {
      allRecentViews.value = allRecentViews.value.filter((f) => f.viewId !== viewId || f.tableID !== tableId)
    }
  }

  const updateTabTitle = () => {
    if (!activeView.value || !activeView.value.base_id) {
      if (openedProject.value?.title) {
        useTitle(openedProject.value?.title)
      }
      return
    }

    const tableName = tablesStore.baseTables
      .get(activeView.value.base_id)
      ?.find((t) => t.id === activeView.value.fk_model_id)?.title

    const baseName = bases.basesList.find((p) => p.id === activeView.value.base_id)?.title

    useTitle(
      getFormattedViewTabTitle({
        viewName: activeView.value.title,
        tableName: tableName || '',
        baseName: baseName || '',
        isSharedView: !!sharedView.value?.id,
      }),
    )
  }

  const updateViewCoverImageColumnId = ({
    columnIds,
    metaId,
    baseId,
  }: {
    columnIds: Set<string>
    metaId: string
    baseId: string
  }) => {
    const key = getViewsKey(baseId, metaId)
    if (!viewsByTable.value.get(key)) return

    let isColumnUsedAsCoverImage = false

    for (const view of viewsByTable.value.get(key) || []) {
      if (
        [_ViewTypes.GALLERY, _ViewTypes.KANBAN].includes(view.type) &&
        view.view?.fk_cover_image_col_id &&
        columnIds.has(view.view?.fk_cover_image_col_id)
      ) {
        isColumnUsedAsCoverImage = true
        break
      }
    }

    if (!isColumnUsedAsCoverImage) return

    viewsByTable.value.set(
      key,
      (viewsByTable.value.get(key) || [])
        .map((view) => {
          if (
            [_ViewTypes.GALLERY, _ViewTypes.KANBAN].includes(view.type) &&
            view.view?.fk_cover_image_col_id &&
            columnIds.has(view.view?.fk_cover_image_col_id)
          ) {
            view.view.fk_cover_image_col_id = null
          }
          return view
        })
        .sort((a, b) => a.order! - b.order!),
    )
  }

  const setCurrentViewExpandedFormMode = async (viewId: string, mode: 'field' | 'attachment', columnId?: string) => {
    /**
     * Update value only if it is EeUI and active view
     */
    if (!isEeUI || !viewId || activeView.value?.id !== viewId) return

    try {
      if (isUIAllowed('viewCreateOrEdit')) {
        await updateView(viewId, {
          expanded_record_mode: mode,
          attachment_mode_column_id: columnId,
        })
      }
    } catch (e: any) {
      console.error(e)
      message.error(await extractSdkResponseErrorMsg(e))
    }
  }

  const setCurrentViewExpandedFormAttachmentColumn = async (viewId: string, columnId: string) => {
    /**
     * Update value only if it is EeUI and active view
     */
    if (!isEeUI || !viewId || activeView.value?.id !== viewId) return

    try {
      if (isUIAllowed('viewCreateOrEdit')) {
        await updateView(viewId, {
          attachment_mode_column_id: columnId,
        })
      }

      Object.assign(activeView.value, { attachment_mode_column_id: columnId })
    } catch (e: any) {
      console.error(e)
      message.error(await extractSdkResponseErrorMsg(e))
    }
  }

  const onOpenViewCreateModal = ({
    title = '',
    type,
    copyViewId,
    groupingFieldColumnId,
    calendarRange,
    coverImageColumnId,
    baseId,
    tableId,
    sourceId,
  }: {
    title?: string
    type: ViewTypes | 'AI'
    copyViewId?: string
    groupingFieldColumnId?: string
    calendarRange?: Array<{
      fk_from_column_id: string
      fk_to_column_id: string | null
    }>
    coverImageColumnId?: string
    baseId: string
    tableId: string
    sourceId?: string
  }) => {
    if (!baseId || !tableId) return

    const isDlgOpen = ref(true)

    const { close } = useDialog(DlgViewCreate, {
      'modelValue': isDlgOpen,
      title,
      type,
      'tableId': tableId,
      'selectedViewId': copyViewId,
      calendarRange,
      groupingFieldColumnId,
      coverImageColumnId,
      'onUpdate:modelValue': closeDialog,
      'baseId': baseId,
      'sourceId': sourceId,
      'onCreated': async (view?: ViewType) => {
        closeDialog()

        refreshCommandPalette()

        if (view) {
          navigateToView({
            view,
            tableId,
            baseId,
            doNotSwitchTab: true,
          })
        }

        $e('a:view:create', { view: view?.type || type })
      },
    })

    function closeDialog() {
      isDlgOpen.value = false

      close(1000)
    }
  }

  const isUserViewOwner = (view?: ViewType, _user: User | null = user.value) => {
    if (!view || !_user) return false

    return (
      view?.owned_by === _user?.id ||
      !!(!view?.owned_by && (_user?.base_roles?.[ProjectRoles.OWNER] || _user?.workspace_roles?.[WorkspaceUserRoles.OWNER]))
    )
  }

  const getCopyViewConfigBtnAccessStatus = (view: ViewType, from: 'view-action-menu' | 'toolbar' = 'view-action-menu') => {
    const result = {
      isDisabled: false,
      tooltip: '',
      isVisible: isEeUI && isUIAllowed('viewCreateOrEdit'),
    }

    if (!view) return result

    if (view?.lock_type === LockType.Personal && !isUserViewOwner(view)) {
      result.isDisabled = true
      result.tooltip = t('tooltip.onlyViewOwnerCanCopyViewConfig')

      if (from === 'toolbar') {
        result.isVisible = false
      }
    } else if (view?.lock_type === LockType.Locked) {
      result.isDisabled = true
      result.tooltip = t('title.thisViewIsLockType', {
        type: t(viewLockIcons[view?.lock_type]?.title).toLowerCase(),
      })

      if (from === 'toolbar') {
        result.isVisible = false
      }
    } else if (view.base_id) {
      const key = getViewsKey(view.base_id, view.fk_model_id)
      if ((viewsByTable.value.get(key) || []).length < 2) {
        result.isDisabled = true
        result.tooltip = t('tooltip.youNeedAtLeastOneExistingViewToCopyConfigurations')

        if (from === 'toolbar') {
          result.isVisible = false
        }
      }
    }

    return result
  }

  const onOpenCopyViewConfigFromAnotherViewModal = ({
    defaultOptions,
    destView = activeView.value,
    onCopy = (_: ViewSettingOverrideOptions[]) => undefined,
  }: {
    defaultOptions?: ViewSettingOverrideOptions[]
    destView?: ViewType
    onCopy?: (selectedCopyViewConfigTypes: ViewSettingOverrideOptions[]) => void
  } = {}) => {
    if (!destView || !isEeUI || !isUIAllowed('viewCreateOrEdit')) return

    // If destination view is locked or if personal and user is not the owner or if table has only one view then return
    if (getCopyViewConfigBtnAccessStatus(destView).isDisabled) {
      return
    }

    const isOpen = ref(true)

    const { close } = useDialog(DlgViewCopyViewConfigFromAnotherView, {
      'modelValue': isOpen,
      'onUpdate:modelValue': closeDialog,
      'destView': destView,
      'defaultSelectedCopyViewConfigTypes': defaultOptions,
      'onCopy': onCopy,
    })

    function closeDialog() {
      isOpen.value = false
      close(1000)
    }
  }

  const copyViewConfigurationFromAnotherView = async (
    destView: ViewType,
    sourceViewId: string,
    settingToOverride: ViewSettingOverrideOptions[],
  ) => {
    if (!sourceViewId || settingToOverride.length === 0 || !destView) {
      return
    }

    try {
      const res = await $api.internal.postOperation(
        activeWorkspaceId.value!,
        destView.base_id!,
        {
          operation: 'viewSettingOverride',
        },
        {
          destinationViewId: destView.id!,
          sourceViewId,
          settingToOverride,
        },
      )

      const defaultView = getFirstNonPersonalView(views.value, {
        includeViewType: ViewTypes.GRID,
      })

      if (
        defaultView?.id === destView.id &&
        [ViewSettingOverrideOptions.FIELD_ORDER, ViewSettingOverrideOptions.FIELD_VISIBILITY].some((type) =>
          settingToOverride.includes(type),
        )
      ) {
        // default view col order and visibility is stored in column meta so we have to load it again
        await getMeta(destView.base_id!, destView.fk_model_id!, true)
      }

      if (res?.view && destView.fk_model_id && destView.base_id) {
        const key = getViewsKey(destView.base_id, destView.fk_model_id)
        const tableViews = viewsByTable.value.get(key) || []
        const viewIndex = tableViews.findIndex((v) => v.id === destView.id)

        if (viewIndex !== -1) {
          // Replace with the response from API
          tableViews[viewIndex] = res.view
          viewsByTable.value.set(key, [...tableViews])

          refreshCommandPalette()
        }
      }

      // Reload view meta as well as data if the destination view is the active view
      if (destView.id === activeView.value?.id) {
        $eventBus.smartsheetStoreEventBus.emit(SmartsheetStoreEvents.COPIED_VIEW_CONFIG, {
          viewId: destView.id,
          copiedOptions: settingToOverride,
        })

        $eventBus.smartsheetStoreEventBus.emit(SmartsheetStoreEvents.FIELD_RELOAD, {
          callback: () => {
            // Load data after fields reload
            forcedNextTick(() => {
              $eventBus.smartsheetStoreEventBus.emit(SmartsheetStoreEvents.DATA_RELOAD)
            })
          },
        })
      }

      message.toast(t('objects.copyViewConfig.viewConfigurationCopied'))

      return true
    } catch (e: any) {
      console.error(e)
      const errorInfo = await extractSdkResponseErrorMsgv2(e)

      if (errorInfo.error === NcErrorType.ERR_FEATURE_NOT_SUPPORTED) {
        message.error(errorInfo.message)
      } else {
        message.error(t('objects.copyViewConfig.errorOccuredWhileCopyingViewConfiguration'), undefined, {
          copyText: errorInfo.message,
        })
      }
    }
  }

  function getViewReadableUrlSlug({ tableTitle, viewOrViewTitle }: { tableTitle?: string; viewOrViewTitle: ViewType | string }) {
    const viewTitle = ncIsObject(viewOrViewTitle) ? viewOrViewTitle.title : viewOrViewTitle

    return toReadableUrlSlug([tableTitle, viewTitle])
  }

  async function hasOnlyOneGridViewInTable(tableId: string) {
    await loadViews({
      tableId,
    })

    // Find the key in viewsByTable that matches this tableId
    let key: string | undefined
    for (const [k] of viewsByTable.value) {
      if (k.endsWith(`:${tableId}`)) {
        key = k
        break
      }
    }

    if (!key) return false

    const grids = viewsByTable.value.get(key)?.filter((v) => v.type === ViewTypes.GRID && v.lock_type !== LockType.Personal)
    return grids?.length === 1
  }

  watch(
    () => tablesStore.activeTableId,
    async (newId, oldId) => {
      if (newId === oldId) return
      if (isPublic.value) {
        isViewsLoading.value = false
        return
      }

      isViewDataLoading.value = true

      try {
        if (tablesStore.activeTable) tablesStore.activeTable.isViewsLoading = true

        await loadViews()
      } catch (e) {
        console.error(e)
      } finally {
        if (tablesStore.activeTable) tablesStore.activeTable.isViewsLoading = false
      }
    },
    { immediate: true },
  )

  watch(activeView, (view) => {
    if (!view) return
    if (!view.base_id) return

    const tableName = tablesStore.baseTables.get(view.base_id)?.find((t) => t.id === view.fk_model_id)?.title

    const baseName = bases.basesList.find((p) => p.id === view.base_id)?.title
    allRecentViews.value = [
      {
        viewId: view.id,
        baseId: view.base_id as string,
        tableID: view.fk_model_id,
        viewName: view.title,
        viewType: view.type,
        workspaceId: activeWorkspaceId.value,
        tableName: tableName as string,
        baseName: baseName as string,
      },
      ...allRecentViews.value.filter((f) => f.viewId !== view.id || f.tableID !== view.fk_model_id),
    ]
  })

  watch(
    () => [activeView.value?.title, activeView.value?.id],
    () => {
      updateTabTitle()
    },
    {
      flush: 'post',
    },
  )

  /**
   * Keeps the browser URL slug in sync with the view's readable slug.
   * Triggers only when:
   * - The current browser URL slug is missing, OR
   * - The browser URL slug does not match the view's readable slug.
   */
  watch(
    [activeViewReadableUrlSlug, activeViewUrlSlug],
    ([newactiveViewReadableUrlSlug, newActiveViewUrlSlug]) => {
      if (!newactiveViewReadableUrlSlug || newActiveViewUrlSlug === newactiveViewReadableUrlSlug) return

      const slugs = (route.value.params.slugs as string[]) || []

      const newSlug = [newactiveViewReadableUrlSlug]

      if (slugs.length > 1) {
        newSlug.push(...slugs.slice(1))
      }

      router.replace({
        name: 'index-typeOrId-baseId-index-index-viewId-viewTitle-slugs',
        params: {
          ...route.value.params,
          viewTitle: route.value.params.viewTitle || activeView.value?.id,
          slugs: newSlug,
        },
        query: route.value.query,
        force: true,
      })
    },
    {
      immediate: true,
      flush: 'post',
    },
  )

  refreshViewTabTitle.on(() => {
    updateTabTitle()
  })

  return {
    // State
    isLockedView,
    isViewsLoading,
    isViewDataLoading,
    isPaginationLoading,
    recentViews,
    allRecentViews,
    views,
    activeView,
    openedViewsTab,
    viewsByTable,
    activeViewTitleOrId,
    activeSorts,
    activeNestedFilters,
    isActiveViewLocked,
    preFillFormSearchParams,
    lastOpenedViewId,
    activeViewRowColorInfo,
    sharedView,
    isActiveViewFieldHeaderVisible,

    // Methods
    createView,
    updateView,
    updateViewMeta,
    deleteView,
    loadViews,
    onViewsTabChange,
    navigateToView,
    changeView,
    hasOnlyOneGridViewInTable,
    removeFromRecentViews,
    refreshViewTabTitle: refreshViewTabTitle.trigger,
    updateViewCoverImageColumnId,
    duplicateView,
    setCurrentViewExpandedFormMode,
    setCurrentViewExpandedFormAttachmentColumn,
    onOpenViewCreateModal,
    getViewReadableUrlSlug,
    onOpenCopyViewConfigFromAnotherViewModal,
    copyViewConfigurationFromAnotherView,
    isUserViewOwner,
    getCopyViewConfigBtnAccessStatus,
    isShowEveryonePersonalViewsEnabled,
  }
})

if (import.meta.hot) {
  import.meta.hot.accept(acceptHMRUpdate(useViewsStore as any, import.meta.hot))
}
