<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';

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

import { ElAlert, ElButton, ElProgress } from 'element-plus';

import { api } from '#/api/request';

interface BatchStatus {
  actualCompleted?: boolean;
  batchId: string;
  cancelledCount?: number;
  completedCount: number;
  failedCount: number;
  importMode: 'AUTO' | 'MANUAL';
  interruptCode?: string;
  interruptedAt?: string;
  interruptMessage?: string;
  pendingCount: number;
  processingCount: number;
  progressPercent: number;
  skippedCount: number;
  status:
    | 'CANCELLED'
    | 'COMPLETED'
    | 'INTERRUPTED'
    | 'PARTIAL_SUCCEEDED'
    | 'RUNNING';
  totalCount: number;
}

const props = defineProps({
  knowledgeId: {
    type: String,
    required: true,
  },
  refreshKey: {
    type: Number,
    default: 0,
  },
  manageable: {
    type: Boolean,
    default: false,
  },
});

const emit = defineEmits(['continued']);
const batch = ref<BatchStatus>();
const continuing = ref(false);
const loadError = ref('');
const refreshing = ref(false);
let pollTimer: null | ReturnType<typeof setTimeout> = null;
let disposed = false;
let refreshGeneration = 0;
let pollDelayMs = 3000;
const TERMINAL_RECHECK_DELAY_MS = 15_000;

const canContinue = computed(
  () =>
    props.manageable &&
    (batch.value?.status === 'INTERRUPTED' ||
      batch.value?.status === 'PARTIAL_SUCCEEDED') &&
    Number(batch.value?.failedCount || 0) > 0,
);

const processedCount = computed(() => {
  const processed =
    Number(batch.value?.completedCount || 0) +
    Number(batch.value?.processingCount || 0) +
    Number(batch.value?.failedCount || 0) +
    Number(batch.value?.skippedCount || 0) +
    Number(batch.value?.cancelledCount || 0);
  return Math.min(Number(batch.value?.totalCount || 0), processed);
});

const allFailed = computed(
  () =>
    Number(batch.value?.totalCount || 0) > 0 &&
    Number(batch.value?.failedCount || 0) ===
      Number(batch.value?.totalCount || 0),
);

const statusLabel = computed(() => {
  const status = batch.value?.status;
  if (status === 'COMPLETED') {
    return $t('documentCollection.importDoc.batchCompleted');
  }
  if (status === 'INTERRUPTED') {
    return $t('documentCollection.importDoc.batchInterrupted');
  }
  if (status === 'PARTIAL_SUCCEEDED') {
    return $t(
      allFailed.value
        ? 'documentCollection.importDoc.batchFailed'
        : 'documentCollection.importDoc.batchPartial',
    );
  }
  return $t('documentCollection.importDoc.batchRunning');
});

const interruptMetadata = computed(() => {
  if (batch.value?.status !== 'INTERRUPTED') {
    return '';
  }
  const details: string[] = [];
  if (batch.value.interruptCode) {
    details.push(
      `${$t('documentCollection.importDoc.interruptCode')} ${
        batch.value.interruptCode
      }`,
    );
  }
  if (batch.value.interruptedAt) {
    const interruptedAt = new Date(batch.value.interruptedAt);
    details.push(
      `${$t('documentCollection.importDoc.interruptedAt')} ${
        Number.isNaN(interruptedAt.getTime())
          ? batch.value.interruptedAt
          : interruptedAt.toLocaleString()
      }`,
    );
  }
  return details.join(' · ');
});

async function refresh() {
  if (!props.knowledgeId) return;
  const currentGeneration = ++refreshGeneration;
  refreshing.value = true;
  try {
    const response = await api.get('/api/v1/document/import/batch/current', {
      params: { knowledgeId: props.knowledgeId },
    });
    if (disposed || currentGeneration !== refreshGeneration) {
      return;
    }
    if (response.errorCode !== 0) {
      loadError.value =
        response.message ||
        $t('documentCollection.importDoc.batchStatusLoadFailed');
      return;
    }
    loadError.value = '';
    pollDelayMs = 3000;
    const restoredBatch = response.data || undefined;
    const completed =
      restoredBatch?.status === 'COMPLETED' || restoredBatch?.actualCompleted;
    batch.value = completed ? undefined : restoredBatch;
  } catch {
    if (!disposed && currentGeneration === refreshGeneration) {
      loadError.value = $t(
        'documentCollection.importDoc.batchStatusLoadFailed',
      );
    }
  } finally {
    if (!disposed && currentGeneration === refreshGeneration) {
      refreshing.value = false;
      schedulePoll();
    }
  }
}

function schedulePoll() {
  if (pollTimer) clearTimeout(pollTimer);
  pollTimer = null;
  if (disposed) {
    return;
  }
  if (loadError.value) {
    const retryDelay = pollDelayMs;
    pollDelayMs = Math.min(pollDelayMs * 2, 30_000);
    pollTimer = setTimeout(refresh, retryDelay);
    return;
  }
  if (
    !batch.value ||
    batch.value.status === 'COMPLETED' ||
    batch.value.status === 'CANCELLED'
  ) {
    return;
  }
  const delay =
    batch.value.status === 'PARTIAL_SUCCEEDED' ||
    batch.value.status === 'INTERRUPTED'
      ? TERMINAL_RECHECK_DELAY_MS
      : 3000;
  pollTimer = setTimeout(refresh, delay);
}

async function continueBatch() {
  if (!batch.value || continuing.value) return;
  continuing.value = true;
  try {
    const response = await api.post('/api/v1/document/import/batch/continue', {
      batchId: batch.value.batchId,
      knowledgeId: props.knowledgeId,
    });
    if (response.errorCode === 0) {
      batch.value = response.data;
      emit('continued');
      schedulePoll();
    }
  } finally {
    continuing.value = false;
  }
}

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

onBeforeUnmount(() => {
  disposed = true;
  refreshGeneration += 1;
  if (pollTimer) clearTimeout(pollTimer);
});

watch(
  () => [props.knowledgeId, props.refreshKey] as const,
  ([knowledgeId], [previousKnowledgeId]) => {
    const knowledgeChanged = knowledgeId !== previousKnowledgeId;
    if (knowledgeChanged) {
      batch.value = undefined;
      loadError.value = '';
      pollDelayMs = 3000;
    }
    refresh();
  },
);
</script>

<template>
  <section
    v-if="batch"
    class="batch-status"
    :aria-label="$t('documentCollection.importDoc.batchStatus')"
  >
    <div class="batch-status__summary">
      <div class="batch-status__headline">
        <span class="batch-status__title">
          {{ $t('documentCollection.importDoc.autoImport') }}
        </span>
        <span class="batch-status__count">
          {{ processedCount }} / {{ batch.totalCount }}
        </span>
        <span class="batch-status__state">{{ statusLabel }}</span>
      </div>
      <ElProgress
        class="batch-status__progress"
        :percentage="Number(batch.progressPercent || 0)"
        :show-text="false"
        :stroke-width="7"
        :status="
          batch.status === 'COMPLETED'
            ? 'success'
            : allFailed
              ? 'exception'
              : undefined
        "
      />
      <div class="batch-status__metrics">
        <span>
          {{ $t('documentCollection.importDoc.completedCount') }}
          {{ batch.completedCount }}
        </span>
        <span>
          {{ $t('documentCollection.importDoc.processingCount') }}
          {{ batch.processingCount }}
        </span>
        <span
          :class="{ 'batch-status__metric--danger': batch.failedCount > 0 }"
        >
          {{ $t('documentCollection.importDoc.failedCount') }}
          {{ batch.failedCount }}
        </span>
        <span>
          {{ $t('documentCollection.importDoc.pendingCount') }}
          {{ batch.pendingCount }}
        </span>
        <span v-if="batch.skippedCount > 0">
          {{ $t('documentCollection.importDoc.skippedCount') }}
          {{ batch.skippedCount }}
        </span>
      </div>
    </div>
    <ElButton
      v-if="canContinue"
      class="batch-status__continue"
      type="primary"
      link
      :loading="continuing"
      @click="continueBatch"
    >
      {{ $t('documentCollection.importDoc.continueBatch') }}
    </ElButton>
    <ElAlert
      v-if="batch.status === 'INTERRUPTED'"
      class="batch-status__alert"
      type="error"
      :closable="false"
      show-icon
      :title="
        batch.interruptMessage ||
        $t('documentCollection.importDoc.interruptFallback')
      "
      :description="interruptMetadata"
    />
    <div v-if="loadError" class="batch-status__load-error">
      <ElAlert
        class="batch-status__alert"
        type="error"
        :closable="false"
        show-icon
        :title="loadError"
      />
      <ElButton type="primary" link :loading="refreshing" @click="refresh()">
        {{ $t('documentCollection.importDoc.retry') }}
      </ElButton>
    </div>
  </section>
  <section
    v-else-if="loadError"
    class="batch-status batch-status--error"
    :aria-label="$t('documentCollection.importDoc.batchStatus')"
  >
    <ElAlert
      class="batch-status__alert"
      type="error"
      :closable="false"
      show-icon
      :title="loadError"
    />
    <ElButton type="primary" link :loading="refreshing" @click="refresh()">
      {{ $t('documentCollection.importDoc.retry') }}
    </ElButton>
  </section>
</template>

<style scoped>
.batch-status {
  display: flex;
  flex: 1 1 360px;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  min-width: min(360px, 100%);
  max-width: 620px;
  padding: 8px 12px;
  background: hsl(var(--surface-contrast-soft) / 74%);
  border: 1px solid var(--el-border-color-lighter);
  border-radius: 12px;
}

.batch-status__summary {
  flex: 1;
  min-width: 0;
}

.batch-status__headline,
.batch-status__metrics {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  align-items: center;
}

.batch-status__headline {
  margin-bottom: 5px;
  font-size: 12px;
}

.batch-status__title {
  font-weight: 600;
  color: var(--el-text-color-primary);
}

.batch-status__count {
  font-variant-numeric: tabular-nums;
  color: var(--el-color-primary);
}

.batch-status__state {
  color: var(--el-text-color-secondary);
}

.batch-status__progress {
  width: 100%;
}

.batch-status__metrics {
  margin-top: 4px;
  overflow: hidden;
  font-size: 11px;
  line-height: 16px;
  color: var(--el-text-color-secondary);
  white-space: nowrap;
}

.batch-status__metric--danger {
  color: var(--el-color-danger);
}

.batch-status__continue {
  flex-shrink: 0;
  min-width: 48px;
}

.batch-status__alert {
  flex: 1 0 100%;
}

.batch-status__load-error {
  display: flex;
  flex: 1 0 100%;
  gap: 8px;
  align-items: center;
}

.batch-status__load-error .batch-status__alert {
  flex-basis: auto;
}

.batch-status--error .batch-status__alert {
  flex-basis: 320px;
}

@media (max-width: 900px) {
  .batch-status {
    flex-basis: 100%;
    max-width: none;
  }
}
</style>
