<script setup lang="ts">
import type { Component } from 'vue';

import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';

import { $t } from '@easyflow/locales';
import { downloadFileFromBlob } from '@easyflow/utils';

import {
  CloseBold,
  Delete,
  Download,
  Files,
  Loading,
  Opportunity,
  Select,
  UploadFilled,
} from '@element-plus/icons-vue';
import {
  ElButton,
  ElIcon,
  ElImage,
  ElMessage,
  ElMessageBox,
  ElProgress,
  ElTable,
  ElTableColumn,
  ElTooltip,
} from 'element-plus';

import { buildKnowledgeShareUrl } from '#/api/knowledge-share';
import { api, SseClient } from '#/api/request';
import documentIcon from '#/assets/ai/knowledge/document.svg';
import PageData from '#/components/page/PageData.vue';
import { resolveDocumentTaskErrorText } from '#/views/ai/documentCollection/document-import-error';
import { buildKnowledgePath } from '#/views/ai/documentCollection/share-path';

interface DocumentStatusPayload {
  completedChunks?: number;
  documentId?: number | string;
  failedChunks?: number;
  knowledgeId?: number | string;
  lastTaskError?: string;
  lastTaskErrorCode?: string;
  parseCurrentStage?: string;
  parseStatusMessage?: string;
  processStatus?: string;
  progressPercent?: number;
  taskModifiedAt?: string;
  totalChunks?: number;
  type?: string;
}

interface DocumentTablePermissions {
  canCreateContent?: boolean;
  canDeleteContent?: boolean;
  canDownloadContent?: boolean;
}

type PermissionKey = keyof DocumentTablePermissions;

const props = defineProps({
  knowledgeId: {
    required: true,
    type: String,
  },
  requestClient: {
    type: Object as any,
    default: () => api,
  },
  endpointPrefix: {
    type: String,
    default: '',
  },
  permissions: {
    type: Object as () => DocumentTablePermissions,
    default: () => ({
      canCreateContent: true,
      canDeleteContent: true,
      canDownloadContent: true,
    }),
  },
});

const emits = defineEmits(['viewDoc', 'continueProcess']);

const STREAM_RECONNECT_DELAY = 1500;
const STREAM_RELOAD_DELAY = 3000;

const pageDataRef = ref();
const retryingDocumentIds = ref<Set<string>>(new Set());
const taskStatusStreamClient = new SseClient();
let reconnectTimer: null | ReturnType<typeof setTimeout> = null;
let reloadTimer: null | ReturnType<typeof setTimeout> = null;
let disposed = false;

defineExpose({
  reload() {
    pageDataRef.value?.reload?.();
  },
  search(searchText: string) {
    pageDataRef.value?.setQuery?.({
      title: searchText,
    });
  },
});

const processingStatuses = new Set(['INDEXING', 'PARSING', 'SPLITTING']);

const isProcessingStatus = (status?: string) =>
  processingStatuses.has(status || '');

const resolvedPermissions = computed(() => ({
  canCreateContent: props.permissions?.canCreateContent ?? true,
  canDeleteContent: props.permissions?.canDeleteContent ?? true,
  canDownloadContent: props.permissions?.canDownloadContent ?? true,
}));

const hasPermission = (key: PermissionKey) =>
  Boolean(resolvedPermissions.value[key]);

const statusMetaMap: Record<
  string,
  {
    icon: Component;
    toneClass: string;
  }
> = {
  COMPLETED: {
    icon: Select,
    toneClass: 'status-pill--success',
  },
  INDEX_FAILED: {
    icon: CloseBold,
    toneClass: 'status-pill--danger',
  },
  INDEXING: {
    icon: Loading,
    toneClass: 'status-pill--warning',
  },
  PARSE_FAILED: {
    icon: CloseBold,
    toneClass: 'status-pill--danger',
  },
  PARSING: {
    icon: Loading,
    toneClass: 'status-pill--warning',
  },
  SPLIT_FAILED: {
    icon: CloseBold,
    toneClass: 'status-pill--danger',
  },
  SPLITTING: {
    icon: Loading,
    toneClass: 'status-pill--warning',
  },
  READY_FOR_INDEX: {
    icon: Opportunity,
    toneClass: 'status-pill--primary',
  },
  READY_FOR_SEGMENT: {
    icon: Files,
    toneClass: 'status-pill--primary',
  },
  UPLOADED: {
    icon: UploadFilled,
    toneClass: 'status-pill--info',
  },
};

const defaultStatusMeta: {
  icon: Component;
  toneClass: string;
} = statusMetaMap.UPLOADED!;

const getStatusLabel = (status?: string) =>
  $t(`documentCollection.taskStatus.${status || 'UPLOADED'}`);

const getStatusMeta = (
  status?: string,
): {
  icon: Component;
  toneClass: string;
} => statusMetaMap[status || 'UPLOADED'] ?? defaultStatusMeta;

const getStatusToneClass = (status?: string) => getStatusMeta(status).toneClass;

const getStatusIcon = (status?: string) => getStatusMeta(status).icon;

const getChunkCount = (row: any) => {
  const totalChunks = Number(row.totalChunks || 0);
  if (totalChunks > 0) {
    return totalChunks;
  }
  return Number(row.chunkCount || 0);
};

const getProgressText = (row: any) => {
  const completed = Number(row.completedChunks || 0);
  const total = Number(row.totalChunks || 0);
  if (row.processStatus === 'PARSING') {
    return `${Number(row.progressPercent || 0)}%`;
  }
  if (total <= 0) {
    return `${Number(row.progressPercent || 0)}%`;
  }
  return `${Number(row.progressPercent || 0)}% · ${completed}/${total}`;
};

const parseStageLabels: Record<string, string> = {
  assembling: '汇总中',
  extracting: '提取中',
  ocr: 'OCR 中',
  preparing: '准备中',
  queued: '排队中',
};

const getProcessingHint = (row: any) =>
  row.parseStatusMessage || parseStageLabels[row.parseCurrentStage || ''] || '';

const getErrorText = (row: any) => {
  const taskError = resolveDocumentTaskErrorText(row, $t);
  if (taskError) {
    return taskError;
  }
  if (
    row.processStatus === 'SPLIT_FAILED' ||
    row.processStatus === 'INDEX_FAILED'
  ) {
    return $t('documentCollection.importDoc.splitOrIndexFailed');
  }
  return '';
};

const clearReconnectTimer = () => {
  if (!reconnectTimer) {
    return;
  }
  clearTimeout(reconnectTimer);
  reconnectTimer = null;
};

const clearReloadTimer = () => {
  if (!reloadTimer) {
    return;
  }
  clearTimeout(reloadTimer);
  reloadTimer = null;
};

const scheduleReload = () => {
  if (reloadTimer) {
    return;
  }
  reloadTimer = setTimeout(() => {
    reloadTimer = null;
    pageDataRef.value?.reload?.({ silent: true });
  }, STREAM_RELOAD_DELAY);
};

const patchDocumentRow = (payload: DocumentStatusPayload) => {
  if (!payload.documentId) {
    return false;
  }
  const nextPatch: Record<string, any> = {
    completedChunks: payload.completedChunks,
    failedChunks: payload.failedChunks,
    lastTaskError: payload.lastTaskError,
    lastTaskErrorCode: payload.lastTaskErrorCode,
    parseCurrentStage: payload.parseCurrentStage,
    parseStatusMessage: payload.parseStatusMessage,
    processStatus: payload.processStatus,
    progressPercent: payload.progressPercent,
    taskModifiedAt: payload.taskModifiedAt,
    totalChunks: payload.totalChunks,
  };
  if (payload.taskModifiedAt) {
    nextPatch.modified = payload.taskModifiedAt;
  }
  return (
    pageDataRef.value?.patchRowById?.(payload.documentId, nextPatch) ?? false
  );
};

const buildTaskStreamUrl = () => {
  const path = buildKnowledgePath(
    props.endpointPrefix,
    '/api/v1/document/import/task/stream',
  );
  return props.endpointPrefix ? buildKnowledgeShareUrl(path) : path;
};

const scheduleStreamReconnect = () => {
  if (disposed || reconnectTimer) {
    return;
  }
  reconnectTimer = setTimeout(() => {
    reconnectTimer = null;
    openTaskStatusStream();
  }, STREAM_RECONNECT_DELAY);
};

const handleTaskStatusMessage = (message: {
  data?: string;
  event?: string;
}) => {
  if (!message?.data) {
    return;
  }
  if (message.event && message.event !== 'document-status') {
    return;
  }
  try {
    const payload = JSON.parse(message.data) as DocumentStatusPayload;
    if (payload?.type !== 'document-status') {
      return;
    }
    if (String(payload.knowledgeId || '') !== String(props.knowledgeId || '')) {
      return;
    }
    if (patchDocumentRow(payload)) {
      return;
    }
  } catch {
    // 文档状态流约定始终返回 JSON；异常负载直接忽略，避免误触发列表刷新。
    return;
  }
  scheduleReload();
};

const openTaskStatusStream = async () => {
  if (!props.knowledgeId) {
    return;
  }
  taskStatusStreamClient.abort();
  clearReconnectTimer();
  await taskStatusStreamClient.post(
    buildTaskStreamUrl(),
    {
      knowledgeId: props.knowledgeId,
    },
    {
      onMessage: handleTaskStatusMessage,
      onError: () => {
        if (!disposed) {
          scheduleStreamReconnect();
        }
      },
      onFinished: () => {
        if (!disposed) {
          scheduleStreamReconnect();
        }
      },
    },
  );
};

const ensurePermission = (key: PermissionKey) => {
  if (hasPermission(key)) {
    return true;
  }
  ElMessage.warning($t('documentCollection.managePermissionHint'));
  return false;
};

const requestTaskAction = async (
  path: string,
  payload: Record<string, any>,
  successMessage: string,
) => {
  if (!ensurePermission('canCreateContent')) {
    return;
  }
  const res = await props.requestClient.post(
    buildKnowledgePath(props.endpointPrefix, path),
    payload,
  );
  if (res.errorCode === 0) {
    ElMessage.success(successMessage);
    pageDataRef.value?.reload?.();
  }
};

const handleContinue = (row: any) => {
  if (!ensurePermission('canCreateContent')) {
    return;
  }
  emits('continueProcess', row);
};

const handleRetry = async (row: any) => {
  const documentId = String(row.id);
  if (retryingDocumentIds.value.has(documentId)) {
    return;
  }
  retryingDocumentIds.value = new Set([
    documentId,
    ...retryingDocumentIds.value,
  ]);
  try {
    await requestTaskAction(
      '/api/v1/document/import/task/retry',
      {
        knowledgeId: props.knowledgeId,
        documentId: row.id,
      },
      getStatusLabel('PARSING'),
    );
  } finally {
    const next = new Set(retryingDocumentIds.value);
    next.delete(documentId);
    retryingDocumentIds.value = next;
  }
};

const isRetrying = (row: any) => retryingDocumentIds.value.has(String(row.id));

const handleView = (row: any) => {
  emits('viewDoc', row.id);
};

const handleDownload = async (row: any) => {
  const blob = await props.requestClient.download(
    buildKnowledgePath(
      props.endpointPrefix,
      `/api/v1/document/download?documentId=${row.id}`,
    ),
  );
  downloadFileFromBlob({
    fileName: row.title || 'document',
    source: blob,
  });
};

const handleDelete = (row: any) => {
  if (!ensurePermission('canDeleteContent')) {
    return;
  }
  if (processingStatuses.has(row.processStatus)) {
    ElMessage.warning($t('documentCollection.processingDeleteBlocked'));
    return;
  }
  ElMessageBox.confirm($t('message.deleteAlert'), $t('message.noticeTitle'), {
    confirmButtonText: $t('button.confirm'),
    cancelButtonText: $t('button.cancel'),
    type: 'warning',
  }).then(async () => {
    const res = await props.requestClient.post(
      buildKnowledgePath(props.endpointPrefix, '/api/v1/document/removeDoc'),
      { id: row.id },
    );
    if (res.errorCode === 0) {
      ElMessage.success($t('message.deleteOkMessage'));
      pageDataRef.value?.reload?.();
    }
  });
};

const primaryActionConfigs: Record<
  string,
  {
    handler: (row: any) => void;
    label: () => string;
    permission?: PermissionKey;
  }
> = {
  COMPLETED: {
    handler: handleView,
    label: () => $t('button.viewSegmentation'),
  },
  INDEX_FAILED: {
    handler: handleRetry,
    label: () => $t('documentCollection.importDoc.retry'),
    permission: 'canCreateContent',
  },
  PARSE_FAILED: {
    handler: handleRetry,
    label: () => $t('documentCollection.importDoc.retry'),
    permission: 'canCreateContent',
  },
  SPLIT_FAILED: {
    handler: handleRetry,
    label: () => $t('documentCollection.importDoc.retry'),
    permission: 'canCreateContent',
  },
  READY_FOR_INDEX: {
    handler: handleContinue,
    label: () => $t('button.continueProcess'),
    permission: 'canCreateContent',
  },
  READY_FOR_SEGMENT: {
    handler: handleContinue,
    label: () => $t('button.continueProcess'),
    permission: 'canCreateContent',
  },
};

const getPrimaryActionLabel = (row: any) => {
  const config = primaryActionConfigs[row.processStatus || ''];
  if (!config) {
    return '';
  }
  if (config.permission && !hasPermission(config.permission)) {
    return '';
  }
  return config.label();
};

const handlePrimaryAction = (row: any) => {
  const config = primaryActionConfigs[row.processStatus || ''];
  if (!config) {
    return;
  }
  if (config.permission && !hasPermission(config.permission)) {
    return;
  }
  config.handler(row);
};

onMounted(() => {
  disposed = false;
  openTaskStatusStream();
});

onBeforeUnmount(() => {
  disposed = true;
  clearReconnectTimer();
  clearReloadTimer();
  taskStatusStreamClient.abort();
});

watch(
  () => `${props.endpointPrefix}:${props.knowledgeId}`,
  () => {
    if (disposed) {
      return;
    }
    clearReloadTimer();
    openTaskStatusStream();
  },
);
</script>

<template>
  <PageData
    :page-url="
      buildKnowledgePath(props.endpointPrefix, '/api/v1/document/documentList')
    "
    ref="pageDataRef"
    :page-size="10"
    :request-client="props.requestClient"
    :extra-query-params="{
      id: props.knowledgeId,
      sort: 'desc',
      sortKey: 'created',
    }"
  >
    <template #default="{ pageList }">
      <ElTable :data="pageList" style="width: 100%" size="large">
        <ElTableColumn
          prop="fileName"
          :label="$t('documentCollection.fileName')"
          min-width="220"
          align="left"
          header-align="left"
        >
          <template #default="{ row }">
            <span class="file-name-container">
              <ElImage :src="documentIcon" class="mr-1" />
              <span class="title">
                {{ row.title }}
              </span>
            </span>
          </template>
        </ElTableColumn>

        <ElTableColumn
          :label="$t('documentCollection.chunkCount')"
          width="96"
          align="left"
          header-align="left"
        >
          <template #default="{ row }">
            {{ getChunkCount(row) }}
          </template>
        </ElTableColumn>

        <ElTableColumn
          :label="$t('documentCollection.createdModifyTime')"
          width="176"
          align="left"
          header-align="left"
        >
          <template #default="{ row }">
            <div class="time-container">
              <span>{{ row.created }}</span>
              <span>{{ row.modified }}</span>
            </div>
          </template>
        </ElTableColumn>

        <ElTableColumn
          :label="$t('documentCollection.processStatus')"
          min-width="156"
          align="left"
          header-align="left"
        >
          <template #default="{ row }">
            <div class="status-cell">
              <div
                class="status-pill"
                :class="getStatusToneClass(row.processStatus)"
              >
                <span class="status-pill__icon-shell">
                  <ElIcon
                    class="status-pill__icon"
                    :class="
                      isProcessingStatus(row.processStatus)
                        ? 'status-pill__icon--spinning'
                        : ''
                    "
                  >
                    <component :is="getStatusIcon(row.processStatus)" />
                  </ElIcon>
                </span>
                <span class="status-pill__label">
                  {{ getStatusLabel(row.processStatus) }}
                </span>
              </div>
              <div
                v-if="
                  row.processStatus === 'INDEXING' ||
                  row.processStatus === 'PARSING' ||
                  row.processStatus === 'SPLITTING'
                "
                class="status-progress"
              >
                <ElProgress
                  :percentage="Number(row.progressPercent || 0)"
                  :stroke-width="8"
                />
                <span class="status-progress__text">
                  {{ getProgressText(row) }}
                </span>
                <span
                  v-if="
                    row.processStatus === 'PARSING' && getProcessingHint(row)
                  "
                  class="status-progress__hint"
                >
                  {{ getProcessingHint(row) }}
                </span>
              </div>
              <ElTooltip
                v-else-if="getErrorText(row)"
                :content="getErrorText(row)"
                placement="top"
                :show-after="300"
              >
                <div class="status-error">
                  {{ getErrorText(row) }}
                </div>
              </ElTooltip>
            </div>
          </template>
        </ElTableColumn>

        <ElTableColumn
          :label="$t('common.handle')"
          width="148"
          align="left"
          header-align="left"
        >
          <template #default="{ row }">
            <div class="action-cell">
              <ElButton
                v-if="getPrimaryActionLabel(row)"
                link
                type="primary"
                :disabled="isRetrying(row)"
                :loading="isRetrying(row)"
                @click="handlePrimaryAction(row)"
              >
                {{ getPrimaryActionLabel(row) }}
              </ElButton>

              <ElTooltip
                v-if="hasPermission('canDownloadContent')"
                :content="$t('button.download')"
                placement="top"
              >
                <ElButton
                  link
                  :icon="Download"
                  :aria-label="$t('button.download')"
                  @click="handleDownload(row)"
                />
              </ElTooltip>

              <ElTooltip
                v-if="hasPermission('canDeleteContent')"
                :content="$t('button.delete')"
                placement="top"
              >
                <ElButton
                  link
                  type="danger"
                  :icon="Delete"
                  :aria-label="$t('button.delete')"
                  @click="handleDelete(row)"
                />
              </ElTooltip>
            </div>
          </template>
        </ElTableColumn>
      </ElTable>
    </template>
  </PageData>
</template>

<style scoped>
.time-container {
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: flex-start;
}

.file-name-container {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
}

.title {
  font-size: 14px;
  font-weight: 500;
  line-height: 20px;
  color: var(--el-text-color-primary);
}

.status-cell {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-start;
}

.status-progress {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: min(168px, 100%);
}

.status-progress__text {
  font-size: 12px;
  color: var(--el-text-color-secondary);
  text-align: left;
}

.status-progress__hint {
  font-size: 12px;
  color: var(--el-text-color-secondary);
  text-align: left;
}

.status-error {
  max-width: 176px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--el-color-danger);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
}

.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  width: fit-content;
  max-width: 100%;
  min-height: 30px;
  padding: 4px 12px 4px 8px;
  border: 1px solid var(--status-pill-border);
  border-radius: 999px;
  background: var(--status-pill-bg);
  color: var(--status-pill-text);
  font-size: 12px;
  font-weight: 600;
  line-height: 18px;
  white-space: nowrap;
  transition:
    border-color 0.2s ease,
    background-color 0.2s ease,
    color 0.2s ease;
}

.status-pill__icon-shell {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 999px;
  background: var(--status-pill-icon-bg);
}

.status-pill__icon {
  display: inline-flex;
  flex-shrink: 0;
  font-size: 12px;
  color: var(--status-pill-icon-color);
}

.status-pill__label {
  overflow: hidden;
  text-overflow: ellipsis;
}

.status-pill__icon--spinning {
  animation: status-spin 1.15s linear infinite;
}

.status-pill--primary {
  --status-pill-bg: var(--el-color-primary-light-9);
  --status-pill-border: var(--el-color-primary-light-7);
  --status-pill-icon-bg: var(--el-color-primary-light-8);
  --status-pill-icon-color: var(--el-color-primary);
  --status-pill-text: var(--el-color-primary);
}

.status-pill--success {
  --status-pill-bg: var(--el-color-success-light-9);
  --status-pill-border: var(--el-color-success-light-7);
  --status-pill-icon-bg: var(--el-color-success-light-8);
  --status-pill-icon-color: var(--el-color-success);
  --status-pill-text: var(--el-color-success);
}

.status-pill--warning {
  --status-pill-bg: var(--el-color-warning-light-9);
  --status-pill-border: var(--el-color-warning-light-7);
  --status-pill-icon-bg: var(--el-color-warning-light-8);
  --status-pill-icon-color: var(--el-color-warning);
  --status-pill-text: var(--el-color-warning);
}

.status-pill--danger {
  --status-pill-bg: var(--el-color-danger-light-9);
  --status-pill-border: var(--el-color-danger-light-7);
  --status-pill-icon-bg: var(--el-color-danger-light-8);
  --status-pill-icon-color: var(--el-color-danger);
  --status-pill-text: var(--el-color-danger);
}

.status-pill--info {
  --status-pill-bg: var(--el-fill-color-light);
  --status-pill-border: var(--el-border-color-light);
  --status-pill-icon-bg: var(--el-fill-color);
  --status-pill-icon-color: var(--el-text-color-secondary);
  --status-pill-text: var(--el-text-color-secondary);
}

.action-cell {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}

.action-cell :deep(.el-button) {
  white-space: nowrap;
}

.action-cell :deep(.el-button + .el-button) {
  margin-left: 0;
}

@keyframes status-spin {
  from {
    transform: rotate(0deg);
  }

  to {
    transform: rotate(360deg);
  }
}
</style>
