<script lang="ts" setup>
import { IntegrationsType } from 'nocodb-sdk'
import type { IntegrationType, UserType, WorkspaceUserType } from 'nocodb-sdk'
import dayjs from 'dayjs'

withDefaults(
  defineProps<{
    showTitle?: boolean
  }>(),
  {
    showTitle: false,
  },
)

type SortFields = 'title' | 'sub_type' | 'created_at' | 'created_by' | 'source_count'

const { t } = useI18n()

const {
  integrations,
  isLoadingIntegrations,
  deleteConfirmText,
  integrationPaginationData,
  successConfirmModal,
  searchQuery,
  loadDynamicIntegrations,
  loadIntegrations,
  deleteIntegration,
  editIntegration,
  getIntegration,
} = useIntegrationStore()

const { $api, $e } = useNuxtApp()

const { allCollaborators } = storeToRefs(useWorkspace())

const { bases } = storeToRefs(useBases())

const { isFeatureEnabled } = useBetaFeatureToggle()

const connectionsSearchInputRef = ref<HTMLInputElement>()

const isDeleteIntegrationModalOpen = ref(false)
const toBeDeletedIntegration = ref<
  | (IntegrationType & {
      sources?: {
        id: string
        alias: string
        project_title: string
        base_id: string
      }[]
    })
  | null
>(null)

const isLoadingGetLinkedSources = ref(false)

// Base assignment dialog state
const isBaseAssignmentOpen = ref(false)
const baseAssignmentIntegration = ref<IntegrationType | null>(null)

function openBaseAssignment(integration: IntegrationType) {
  baseAssignmentIntegration.value = integration
  isBaseAssignmentOpen.value = true
}

function onBaseAssignmentUpdated() {
  loadIntegrations()
}

const tableWrapper = ref<HTMLDivElement>()

const orderBy = ref<Partial<Record<SortFields, 'asc' | 'desc' | undefined>>>({})

const localCollaborators = ref<User[] | UserType[]>([])

const collaboratorsMap = computed<Map<string, (WorkspaceUserType & { id: string }) | User | UserType>>(() => {
  const map = new Map()

  ;(isEeUI ? allCollaborators.value : localCollaborators.value)?.forEach((coll) => {
    if (coll?.id) {
      map.set(coll.id, coll)
    }
  })
  return map
})

const filteredIntegrations = computed(() =>
  (integrations.value || [])
    .filter((i) => IntegrationsType.Sync !== i.type)
    .sort((a, b) => {
      if (orderBy.value.title) {
        if (a.title && b.title) {
          return orderBy.value.title === 'asc' ? (a.title < b.title ? -1 : 1) : a.title > b.title ? -1 : 1
        }
      } else if (orderBy.value.sub_type) {
        if (a.sub_type && b.sub_type) {
          return orderBy.value.sub_type === 'asc' ? (a.sub_type < b.sub_type ? -1 : 1) : a.sub_type > b.sub_type ? -1 : 1
        }
      } else if (orderBy.value.created_at) {
        if (a?.created_at && b?.created_at) {
          return orderBy.value.created_at === 'asc'
            ? dayjs(a.created_at).local().diff(dayjs(b.created_at).local())
            : dayjs(b.created_at).local().diff(dayjs(a.created_at).local())
        }
      } else if (orderBy.value.created_by) {
        if (
          a.created_by &&
          b.created_by &&
          collaboratorsMap.value.get(a.created_by) &&
          collaboratorsMap.value.get(b.created_by)
        ) {
          return orderBy.value.created_by === 'asc'
            ? collaboratorsMap.value.get(a.created_by)?.email < collaboratorsMap.value.get(b.created_by)?.email
              ? -1
              : 1
            : collaboratorsMap.value.get(a.created_by)?.email > collaboratorsMap.value.get(b.created_by)?.email
            ? -1
            : 1
        }
      } else if (orderBy.value.source_count) {
        if (a.source_count !== undefined && b.source_count !== undefined) {
          return orderBy.value.source_count === 'asc' ? a.source_count - b.source_count : b.source_count - a.source_count
        }
      }
      return 0
    }),
)

async function loadConnections(
  page = integrationPaginationData.value.page,
  limit = integrationPaginationData.value.pageSize,
  updateCurrentPage = true,
) {
  try {
    if (updateCurrentPage) {
      integrationPaginationData.value.page = 1
    }

    await loadIntegrations(null, undefined, updateCurrentPage ? 1 : page, limit)
  } catch {}
}

const handleChangePage = async (page: number) => {
  integrationPaginationData.value.page = page
  await loadConnections(undefined, undefined, false)
}

const { onLeft, onRight, onUp, onDown } = usePaginationShortcuts({
  paginationDataRef: integrationPaginationData,
  changePage: handleChangePage,
  isViewDataLoading: isLoadingIntegrations,
})

const openDeleteIntegration = async (source: IntegrationType) => {
  isLoadingGetLinkedSources.value = true

  $e('c:integration:delete')
  deleteConfirmText.value = null
  isDeleteIntegrationModalOpen.value = true
  toBeDeletedIntegration.value = source

  const connectionDetails = await getIntegration(source, {
    includeSources: true,
  })
  if (toBeDeletedIntegration.value) {
    toBeDeletedIntegration.value.sources = connectionDetails?.sources || []
  }

  isLoadingGetLinkedSources.value = false
}

const openEditIntegration = (integration: IntegrationType) => {
  if (!isFeatureEnabled(FEATURE_FLAG.DATA_REFLECTION) && integration.sub_type === SyncDataType.NOCODB) {
    return
  }

  editIntegration(integration)
}

const onDeleteConfirm = async () => {
  const isDeleted = await deleteIntegration(toBeDeletedIntegration.value, true)

  if (isDeleted) {
    for (const source of toBeDeletedIntegration.value?.sources || []) {
      if (!source.base_id || !source.id || (source.base_id && !bases.value.get(source.base_id))) {
        continue
      }

      const base = bases.value.get(source.base_id)

      if (!Array.isArray(base?.sources)) {
        continue
      }

      bases.value.set(source.base_id, {
        ...(base || {}),
        sources: [...base.sources.filter((s) => s.id !== source.id)],
      })
    }
  }
}

const loadOrgUsers = async () => {
  try {
    const response: any = await $api.orgUsers.list()

    if (!response?.list) return

    localCollaborators.value = response.list as UserType[]
  } catch (e: any) {
    message.error(await extractSdkResponseErrorMsg(e))
  }
}

const handleSearchConnection = useDebounceFn(() => {
  loadConnections()
}, 250)

const isUserDeleted = (userId?: string) => {
  if (!userId) return false

  if (isEeUI) {
    return !!collaboratorsMap.value.get(userId)?.deleted
  } else {
    return !collaboratorsMap.value.get(userId)?.email
  }
}

const getUserNameByCreatedBy = (createdBy: string) => {
  return (
    collaboratorsMap.value.get(createdBy)?.display_name ||
    collaboratorsMap.value.get(createdBy)?.email?.slice(0, collaboratorsMap.value.get(createdBy)?.email?.indexOf('@'))
  )
}

useEventListener(tableWrapper, 'scroll', () => {
  const stickyHeaderCell = tableWrapper.value?.querySelector('th.cell-title')
  const nonStickyHeaderFirstCell = tableWrapper.value?.querySelector('th.cell-type')

  if (!stickyHeaderCell?.getBoundingClientRect().right || !nonStickyHeaderFirstCell?.getBoundingClientRect().left) {
    return
  }

  if (nonStickyHeaderFirstCell?.getBoundingClientRect().left < stickyHeaderCell?.getBoundingClientRect().right) {
    tableWrapper.value?.classList.add('sticky-shadow')
  } else {
    tableWrapper.value?.classList.remove('sticky-shadow')
  }
})

onMounted(async () => {
  await loadDynamicIntegrations()
  if (!isEeUI) {
    await Promise.allSettled([!integrations.value.length && loadIntegrations(), loadOrgUsers()])
  } else if (!integrations.value.length) {
    await loadIntegrations()
  }
})

watch(connectionsSearchInputRef, (el) => {
  if (el) {
    forcedNextTick(() => {
      connectionsSearchInputRef.value?.focus()
    })
  }
})

// Keyboard shortcuts for pagination
onKeyStroke('ArrowLeft', onLeft)
onKeyStroke('ArrowRight', onRight)
onKeyStroke('ArrowUp', onUp)
onKeyStroke('ArrowDown', onDown)

const columns = [
  {
    key: 'title',
    title: t('general.name'),
    minWidth: 250,
    dataIndex: 'title',
    showOrderBy: true,
  },
  {
    key: 'sub_type',
    title: t('general.type'),
    minWidth: 98,
    width: 120,
    dataIndex: 'sub_type',
    showOrderBy: true,
  },
  {
    key: 'created_at',
    title: t('labels.dateAdded'),
    basis: '20%',
    minWidth: 200,

    dataIndex: 'created_at',
    showOrderBy: true,
  },
  {
    key: 'created_by',
    title: t('labels.addedBy'),
    minWidth: 250,
    basis: '20%',
    dataIndex: 'created_by',
    showOrderBy: true,
  },
  {
    key: 'source_count',
    title: t('general.usage'),
    width: 120,
    dataIndex: 'source_count',
    showOrderBy: true,
  },
  {
    key: 'base_access',
    title: t('labels.baseAccess'),
    minWidth: 140,
    width: 160,
  },
  {
    key: 'action',
    title: t('labels.actions'),
    minWidth: 100,
    width: 100,
    justify: 'justify-end',
  },
] as NcTableColumnProps[]

const customRow = (record: Record<string, any>) => ({
  onclick: () => {
    openEditIntegration(record)
  },
})
</script>

<template>
  <div class="h-full flex flex-col gap-6 nc-workspace-connections nc-content-max-w mx-auto">
    <div class="flex flex-col justify-between gap-2">
      <h2 v-if="showTitle" class="text-lg font-semibold text-nc-content-gray mb-0">
        {{ $t('general.activeConnections') }}
      </h2>

      <div class="text-sm font-normal text-nc-content-gray-subtle2">
        <div>
          {{ $t('msg.manageConnections') }}
          <a
            target="_blank"
            href="https://nocodb.com/docs/product-docs/integrations/actions-on-connection"
            rel="noopener noreferrer"
          >
            {{ $t('msg.learnMore') }}
          </a>
        </div>
      </div>
      <div class="flex items-center gap-3 mt-2">
        <a-input
          ref="connectionsSearchInputRef"
          v-model:value="searchQuery"
          type="text"
          class="nc-search-integration-input !rounded-lg !py-2 !h-9 flex-1"
          :placeholder="`${$t('general.search')} ${$t('general.connections').toLowerCase()}`"
          allow-clear
          @input="handleSearchConnection"
        >
          <template #prefix>
            <GeneralIcon icon="search" class="mr-2 h-4 w-4 text-nc-content-gray-muted" />
          </template>
        </a-input>
      </div>
    </div>
    <NcTable
      v-model:order-by="orderBy"
      :columns="columns"
      :data="filteredIntegrations"
      :is-data-loading="isLoadingIntegrations"
      sticky-first-column
      :custom-row="customRow"
      class="h-full"
    >
      <template #bodyCell="{ column, record: integration }">
        <div v-if="column.key === 'title'" class="w-full flex items-center gap-3">
          <NcTooltip placement="bottom" class="truncate !text-nc-content-gray font-semibold" show-on-truncate-only>
            <template #title> {{ integration.title }}</template>
            {{ integration.title }}
          </NcTooltip>
          <span v-if="integration.is_private">
            <NcBadge :border="false" class="text-primary !h-4.5 bg-nc-bg-brand text-xs">{{ $t('general.private') }}</NcBadge>
          </span>
        </div>

        <NcTooltip
          v-if="column.key === 'sub_type'"
          placement="bottom"
          class="h-8 w-8 flex-none flex items-center justify-center children:flex-none"
        >
          <template #title> {{ clientTypesMap[integration?.sub_type]?.text || integration?.sub_type }}</template>

          <GeneralIntegrationIcon
            :type="integration.sub_type"
            :size="integration.sub_type === SyncDataType.NOCODB ? 'xxl' : 'lg'"
          />
        </NcTooltip>

        <NcTooltip v-if="column.key === 'created_at'" placement="bottom" show-on-truncate-only>
          <template #title> {{ dayjs(integration.created_at).local().format('DD MMM YYYY') }}</template>

          {{ dayjs(integration.created_at).local().format('DD MMM YYYY') }}
        </NcTooltip>
        <template v-if="column.key === 'created_by'">
          <div v-if="integration.sub_type === SyncDataType.NOCODB" class="flex items-center gap-3">
            <div class="h-8 w-8 grid place-items-center">
              <GeneralIcon icon="nocodb1" />
            </div>
            <div class="text-sm !leading-5 capitalize font-semibold truncate">NocoDB Cloud</div>
          </div>
          <NcTooltip v-else :disabled="!isUserDeleted(integration.created_by)" class="w-full">
            <template #title>
              {{ `User not part of this ${isEeUI ? 'workspace' : 'organisation'} anymore` }}
            </template>
            <div
              v-if="integration.created_by && collaboratorsMap.get(integration.created_by)?.email"
              class="w-full flex gap-3 items-center"
            >
              <GeneralUserIcon
                :user="collaboratorsMap.get(integration.created_by)"
                size="base"
                class="flex-none"
                :class="{
                  '!grayscale': isUserDeleted(integration.created_by),
                }"
                :style="
                  isUserDeleted(integration.created_by)
                    ? {
                        filter: 'grayscale(100%) brightness(115%)',
                      }
                    : {}
                "
              />
              <div class="flex-1 flex flex-col max-w-[calc(100%_-_44px)]">
                <div class="w-full flex gap-3">
                  <NcTooltip
                    class="text-sm !leading-5 capitalize font-semibold truncate"
                    :class="{
                      'text-nc-content-gray': !isUserDeleted(integration.created_by),
                      'text-nc-content-gray-muted': isUserDeleted(integration.created_by),
                    }"
                    :disabled="isUserDeleted(integration.created_by)"
                    show-on-truncate-only
                    placement="bottom"
                  >
                    <template #title>
                      {{ getUserNameByCreatedBy(integration.created_by) }}
                    </template>
                    {{ getUserNameByCreatedBy(integration.created_by) }}
                  </NcTooltip>
                </div>
                <NcTooltip
                  class="text-xs !leading-4 truncate"
                  :class="{
                    'text-nc-content-gray-subtle2': !isUserDeleted(integration.created_by),
                    'text-nc-content-gray-muted': isUserDeleted(integration.created_by),
                  }"
                  :disabled="isUserDeleted(integration.created_by)"
                  show-on-truncate-only
                  placement="bottom"
                >
                  <template #title>
                    {{ collaboratorsMap.get(integration.created_by)?.email }}
                  </template>
                  {{ collaboratorsMap.get(integration.created_by)?.email }}
                </NcTooltip>
              </div>
            </div>
            <div v-else class="w-full truncate text-nc-content-gray-muted">{{ integration.created_by }}</div>
          </NcTooltip>
        </template>

        <div v-if="column.key === 'base_access'" class="text-sm">
          <NcBadge
            v-if="!integration.is_restricted"
            size="xs"
            color="green"
            :border="false"
            class="cursor-pointer"
            @click.stop="openBaseAssignment(integration)"
          >
            {{ $t('activity.allBases') }}
          </NcBadge>
          <NcBadge
            v-else
            size="xs"
            color="gray"
            :border="false"
            class="cursor-pointer"
            @click.stop="openBaseAssignment(integration)"
          >
            {{ $t('labels.restricted') }}
          </NcBadge>
        </div>

        <div v-if="column.key === 'action'" @click.stop>
          <WorkspaceIntegrationsConnectionActionMenu
            :integration="integration"
            @delete="openDeleteIntegration"
            @base-assignment="openBaseAssignment"
          />
        </div>
      </template>

      <template #tableFooter>
        <div
          v-if="integrationPaginationData.totalRows"
          class="flex flex-row justify-center items-center bg-nc-bg-gray-extralight min-h-10"
          :class="{
            'pointer-events-none': isLoadingIntegrations,
          }"
        >
          <div class="flex justify-between items-center w-full px-6">
            <div>&nbsp;</div>
            <NcPagination
              v-model:current="integrationPaginationData.page"
              v-model:page-size="integrationPaginationData.pageSize"
              :total="+integrationPaginationData.totalRows"
              show-size-changer
              :use-stored-page-size="false"
              :prev-page-tooltip="`${renderAltOrOptlKey()}+←`"
              :next-page-tooltip="`${renderAltOrOptlKey()}+→`"
              :first-page-tooltip="`${renderAltOrOptlKey()}+↓`"
              :last-page-tooltip="`${renderAltOrOptlKey()}+↑`"
              @update:current="loadConnections(undefined, undefined, false)"
              @update:page-size="loadConnections(integrationPaginationData.page, $event, false)"
            />
            <div class="text-nc-content-gray-muted text-xs">
              {{ integrationPaginationData.totalRows }} {{ integrationPaginationData.totalRows === 1 ? 'record' : 'records' }}
            </div>
          </div>
        </div>
      </template>
    </NcTable>

    <GeneralDeleteModal
      v-model:visible="isDeleteIntegrationModalOpen"
      :entity-name="$t('general.connection')"
      :on-delete="onDeleteConfirm"
      :delete-label="$t('general.delete')"
      :show-default-delete-msg="!isLoadingGetLinkedSources && !toBeDeletedIntegration?.sources?.length"
    >
      <template #entity-preview>
        <template v-if="isLoadingGetLinkedSources">
          <div class="rounded-lg overflow-hidden">
            <a-skeleton-input active class="h-9 !rounded-md !w-full"></a-skeleton-input>
          </div>
          <div class="rounded-lg overflow-hidden mt-2">
            <a-skeleton-input active class="h-9 !rounded-md !w-full"></a-skeleton-input>
          </div>
        </template>
        <div v-else-if="toBeDeletedIntegration" class="w-full flex flex-col text-nc-content-gray">
          <div
            class="flex flex-row items-center py-2 px-3.25 bg-nc-bg-gray-extralight rounded-lg text-nc-content-inverted-secondary mb-4"
          >
            <GeneralIntegrationIcon :type="toBeDeletedIntegration.sub_type" />
            <div
              class="text-ellipsis overflow-hidden select-none w-full pl-3"
              :style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
            >
              {{ toBeDeletedIntegration.title }}
            </div>
          </div>
          <div
            v-if="toBeDeletedIntegration?.sources?.length"
            class="flex flex-col pb-2 text-small leading-[18px] text-nc-content-gray-muted"
          >
            <div class="mb-1">{{ $t('msg.deleteIntegrationSourcesWarning') }}</div>
            <ul class="!list-disc ml-6 mb-0">
              <li
                v-for="(source, idx) of toBeDeletedIntegration.sources"
                :key="idx"
                class="marker:text-nc-content-gray-muted !marker:(flex items-center !-mt-1)"
              >
                <div class="flex items-center gap-1">
                  <div class="flex items-center">
                    &nbsp;
                    <GeneralProjectIcon
                      type="database"
                      class="!grayscale min-w-5 flex-none -ml-1"
                      :style="{
                        filter: 'grayscale(100%) brightness(115%)',
                      }"
                    />
                  </div>

                  <NcTooltip class="!truncate !max-w-[45%] flex-none" show-on-truncate-only>
                    <template #title>
                      {{ source.project_title }}
                    </template>

                    {{ source.project_title }}
                  </NcTooltip>
                  >
                  <GeneralBaseLogo
                    class="!grayscale min-w-4 flex-none"
                    :style="{
                      filter: 'grayscale(100%) brightness(115%)',
                    }"
                  />

                  <NcTooltip class="truncate !max-w-[45%] capitalize" show-on-truncate-only>
                    <template #title>
                      {{ source.alias }}
                    </template>

                    {{ source.alias }}
                  </NcTooltip>
                </div>
              </li>
            </ul>
            <div class="mt-2">{{ $t('msg.deleteIntegrationProceedConfirm') }}</div>
          </div>
        </div>
      </template>
    </GeneralDeleteModal>

    <NcModal v-model:visible="successConfirmModal.isOpen" centered size="small" @keydown.esc="successConfirmModal.isOpen = false">
      <div class="flex gap-4">
        <div>
          <GeneralIcon icon="circleCheckSolid" class="flex-none !text-green-700 mt-0.5 !h-6 !w-6" />
        </div>

        <div class="flex flex-col gap-3">
          <div class="flex">
            <h3 class="!m-0 text-base font-weight-700 flex-1">
              {{ successConfirmModal.title }}
            </h3>
            <NcButton size="xsmall" type="text" @click="successConfirmModal.isOpen = false">
              <GeneralIcon icon="close" class="text-nc-content-gray-subtle2" />
            </NcButton>
          </div>
          <div class="text-sm text-nc-content-inverted-secondary">
            {{ successConfirmModal.description }}
          </div>

          <a
            target="_blank"
            href="https://nocodb.com/docs/product-docs/data-sources/connect-to-data-source"
            rel="noopener noreferrer"
          >
            {{ $t('msg.learnMore') }}
          </a>
        </div>
      </div>
    </NcModal>

    <!-- Base Assignment Dialog -->
    <WorkspaceIntegrationsBaseAssignment
      v-if="baseAssignmentIntegration"
      v-model:visible="isBaseAssignmentOpen"
      :integration="baseAssignmentIntegration"
      @updated="onBaseAssignmentUpdated"
    />
  </div>
</template>

<style lang="scss" scoped>
.source-card-link {
  @apply !text-nc-content-gray-extreme !no-underline;

  .nc-new-integration-type-title {
    @apply text-sm font-weight-600 text-nc-content-gray-subtle2;
  }
}

.source-card {
  @apply flex items-center border-1 rounded-lg p-3 cursor-pointer hover:bg-nc-bg-gray-extralight;
  width: 288px;

  .name {
    @apply ml-4 text-md font-semibold;
  }
}

:deep(.ant-input-affix-wrapper.nc-search-integration-input) {
  &:not(:has(.ant-input-clear-icon-hidden)):has(.ant-input-clear-icon) {
    @apply border-[var(--ant-primary-5)];
  }
}

.nc-new-integration-type-wrapper {
  @apply flex flex-col gap-3;
}
</style>
