<script setup lang="ts">
import {
  ClockCircleOutlined,
  CopyOutlined,
  DesktopOutlined,
  ExportOutlined,
  ReloadOutlined,
  SearchOutlined,
  SendOutlined,
} from '@ant-design/icons-vue'
import type { DesktopPublishTaskListResponse, DesktopTaskInfo, JsonValue } from '@geo/shared-types'
import { notification } from 'ant-design-vue'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'

import { normalizePublisherErrorMessage } from '../../shared/publisher-errors'
import StatusBadge from '../components/StatusBadge.vue'
import { useDesktopRuntime } from '../composables/useDesktopRuntime'
import { unwrapClientErrorMessage } from '../lib/client-errors'
import { formatDateTime, formatRelativeTime } from '../lib/formatters'
import {
  desktopPlatformShortName,
  desktopPublishMediaCatalog,
  translateDesktopPlatform,
} from '../lib/media-catalog'

const PAGE_SIZE = 10
const INLINE_ERROR_HEAD_CHARS = 48
const INLINE_ERROR_TAIL_CHARS = 10
const ACTIVE_TASK_POLL_INTERVAL_MS = 5_000

const publishPlatformIndex: Record<string, (typeof desktopPublishMediaCatalog)[number]> =
  Object.fromEntries(desktopPublishMediaCatalog.map((item) => [item.id, item]))

interface PublishTaskItem {
  id: string
  title: string
  articleId: number | null
  platform: string
  accountId: string
  accountName: string
  status: DesktopTaskInfo['status']
  summary: string
  attempts: number
  leaseExpiresAt: number | null
  createdAt: number
  updatedAt: number
  externalArticleUrl: string | null
  publishType: string | null
  fallbackReason: string | null
  errorMessage: string | null
  complianceBlockedRecordId: number | null
  complianceBlockedAt: number | null
}

let refreshTimer: ReturnType<typeof setInterval> | null = null
let runtimeInvalidationUnsubscribe: (() => void) | null = null

function platformMeta(platform: string) {
  const key = (platform ?? '').toLowerCase()
  return publishPlatformIndex[key] ?? null
}

function translatePlatform(platform: string) {
  return platformMeta(platform)?.label || translateDesktopPlatform(platform)
}

function platformLogo(platform: string): string | null {
  return platformMeta(platform)?.logoUrl ?? null
}

function platformAccent(platform: string): string {
  return platformMeta(platform)?.accent ?? '#64748b'
}

function platformShortName(platform: string): string {
  return platformMeta(platform)?.shortName ?? desktopPlatformShortName(platform)
}

function statusLabel(status: DesktopTaskInfo['status']): string {
  const map: Record<DesktopTaskInfo['status'], string> = {
    queued: '等待发布',
    in_progress: '正在发送',
    succeeded: '发送成功',
    failed: '发送失败',
    unknown: '发送失败',
    aborted: '已取消',
  }
  return map[status]
}

function isComplianceBlockedTask(task: PublishTaskItem): boolean {
  return task.status === 'aborted' && task.complianceBlockedRecordId !== null
}

function statusLabelForTask(task: PublishTaskItem): string {
  if (isComplianceBlockedTask(task)) {
    return '合规阻断'
  }
  if (task.status === 'succeeded' && task.publishType === 'draft') {
    return '已存草稿'
  }
  return statusLabel(task.status)
}

function isPendingStatus(status: DesktopTaskInfo['status']): boolean {
  return status === 'queued' || status === 'in_progress'
}

function hasActivePublishingTask(page: DesktopPublishTaskListResponse | null): boolean {
  return Boolean(
    page?.items.some((task) => normalizePublishTaskStatus(task.status) === 'in_progress'),
  )
}

function statusTone(status: DesktopTaskInfo['status']): 'info' | 'warn' | 'success' | 'danger' {
  switch (status) {
    case 'queued':
    case 'in_progress':
      return 'warn'
    case 'succeeded':
      return 'success'
    case 'failed':
    case 'unknown':
    case 'aborted':
    default:
      return 'danger'
  }
}

function statusToneForTask(task: PublishTaskItem): 'info' | 'warn' | 'success' | 'danger' {
  if (task.status === 'succeeded' && task.publishType === 'draft') {
    return 'warn'
  }
  return statusTone(task.status)
}

function extractNumber(value: JsonValue | null | undefined): number | null {
  if (typeof value === 'number' && Number.isFinite(value)) {
    return value
  }
  if (typeof value === 'string' && /^\d+$/.test(value.trim())) {
    return Number.parseInt(value.trim(), 10)
  }
  return null
}

function extractString(value: JsonValue | null | undefined): string | null {
  if (typeof value !== 'string') {
    return null
  }
  const trimmed = value.trim()
  return trimmed ? trimmed : null
}

function extractStringId(value: JsonValue | null | undefined): string | null {
  if (typeof value === 'number' && Number.isFinite(value)) {
    return String(Math.trunc(value))
  }
  return extractString(value)
}

function buildSohuArticleUrl(
  task: DesktopTaskInfo,
  result: Record<string, JsonValue>,
): string | null {
  if (task.platform !== 'sohuhao' || extractString(result.publish_type) === 'draft') {
    return null
  }

  const articleId = extractStringId(result.external_article_id)
  const accountUid = accountMap.value.get(task.target_account_id)?.platformUid?.trim() ?? ''
  if (!articleId || !accountUid) {
    return null
  }

  return `https://www.sohu.com/a/${encodeURIComponent(articleId)}_${encodeURIComponent(accountUid)}`
}

function buildWangyihaoArticleUrl(
  task: DesktopTaskInfo,
  result: Record<string, JsonValue>,
): string | null {
  if (task.platform !== 'wangyihao' || extractString(result.publish_type) === 'draft') {
    return null
  }

  const articleId = extractStringId(result.external_article_id)
  if (!articleId) {
    return null
  }

  return `https://www.163.com/dy/article/${encodeURIComponent(articleId)}.html`
}

function normalizePublishTaskStatus(status: DesktopTaskInfo['status']): DesktopTaskInfo['status'] {
  return status === 'unknown' ? 'failed' : status
}

function normalizeTaskErrorMessage(message: string | null): string | null {
  const normalized = extractString(message)
  if (!normalized) {
    return null
  }
  if (normalized.includes('scaffold-only') || normalized.includes('requires later reconcile')) {
    return '当前平台的桌面发布适配器未实现，任务已按失败处理。'
  }
  if (normalized.includes('adapter is not implemented for this platform')) {
    return '当前平台的桌面发布适配器未实现，任务已按失败处理。'
  }
  return normalizePublisherErrorMessage(normalized) ?? normalized
}

function complianceLevelLabel(level: string | null): string {
  const normalized = level?.trim().toLowerCase()
  switch (normalized) {
    case 'block':
      return '阻断'
    case 'high':
      return '高风险'
    case 'medium':
      return '中风险'
    case 'info':
      return '提示'
    default:
      return '风险'
  }
}

function summarizeComplianceBlockedReason(raw: string | null): string | null {
  const normalized = extractString(raw)
  if (!normalized) {
    return null
  }

  try {
    const parsed = JSON.parse(normalized) as unknown
    if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
      return normalized
    }
    const record = parsed as Record<string, unknown>
    const manualStatus = typeof record.manual_review_status === 'string' ? record.manual_review_status : ''
    if (manualStatus === 'pending') {
      return '文章版本正在等待运营人工审阅，旧定时任务已停止派发。'
    }
    if (manualStatus === 'rejected') {
      return '文章版本已被运营驳回，请修改文章后重新发布。'
    }

    const hitCount = typeof record.hit_count === 'number' ? record.hit_count : null
    const highestLevel =
      typeof record.highest_level === 'string' ? complianceLevelLabel(record.highest_level) : '风险'
    const violations = Array.isArray(record.violations) ? record.violations : []
    const matchedTerms = violations
      .map((item) => {
        if (!item || typeof item !== 'object' || Array.isArray(item)) {
          return null
        }
        const violation = item as Record<string, unknown>
        return typeof violation.matched_text === 'string' ? violation.matched_text.trim() : null
      })
      .filter((item): item is string => Boolean(item))
      .slice(0, 3)

    const hitPart =
      hitCount !== null && hitCount > 0 ? `命中 ${hitCount} 条${highestLevel}规则` : `命中${highestLevel}规则`
    return matchedTerms.length > 0
      ? `${hitPart}：${matchedTerms.join('、')}`
      : `${hitPart}，请修改文章后重新发布。`
  } catch {
    return normalized === 'compliance_blocked' ? null : normalized
  }
}

function inlineErrorPreview(message: string): string {
  const compact = message.replace(/\s+/g, ' ').trim()
  const maxLength = INLINE_ERROR_HEAD_CHARS + INLINE_ERROR_TAIL_CHARS + 3
  if (compact.length <= maxLength) {
    return compact
  }
  return `${compact.slice(0, INLINE_ERROR_HEAD_CHARS)}...${compact.slice(-INLINE_ERROR_TAIL_CHARS)}`
}

function summaryForTask(
  status: DesktopTaskInfo['status'],
  publishType: string | null = null,
  fallbackReason: string | null = null,
): string {
  if (status === 'aborted' && fallbackReason === 'compliance_blocked') {
    return '定时发布前的合规重检拦截了本次任务，请修改文章后重新发布。'
  }
  switch (status) {
    case 'queued':
      return '任务已进入队列，待前面的发布任务完成后继续执行。'
    case 'in_progress':
      return '当前正在由桌面端执行发送，请避免重复触发。'
    case 'succeeded':
      if (publishType === 'draft') {
        if (fallbackReason === 'publish_authorization_required') {
          return '草稿已保存，需在公众号后台完成微信验证/授权发布。'
        }
        return '文章已保存到草稿箱，需在平台后台完成发表。'
      }
      return '文章已成功发送到目标平台。'
    case 'failed':
      return '本次发送失败，可以手动再次发送。'
    case 'aborted':
      return '任务已被取消，如需继续可再次发送。'
    case 'unknown':
    default:
      return '发送结果异常，已按失败处理，可检查平台侧结果后再次发送。'
  }
}

function parseTimestamp(value: string | null): number {
  if (!value) {
    return Date.now()
  }
  const timestamp = Date.parse(value)
  return Number.isNaN(timestamp) ? Date.now() : timestamp
}

const { snapshot } = useDesktopRuntime()
const taskPage = ref<DesktopPublishTaskListResponse | null>(null)
const currentPage = ref(1)
const searchTitle = ref('')
const appliedTitle = ref('')
const loading = ref(false)
const error = ref<string | null>(null)
const actionPendingTaskId = ref<string | null>(null)
const consolePendingTaskId = ref<string | null>(null)
const copiedErrorTaskId = ref<string | null>(null)

function notifySuccess(message: string) {
  notification.success({
    message,
    placement: 'topRight',
    duration: 2.4,
  })
}

function notifyActionError(description: string) {
  notification.error({
    message: '操作失败',
    description,
    placement: 'topRight',
    duration: 4,
  })
}

async function refreshTasks(page = currentPage.value) {
  loading.value = true
  error.value = null

  try {
    const response = await window.desktopBridge.app.listPublishTasks({
      page,
      page_size: PAGE_SIZE,
      title: appliedTitle.value || undefined,
    })

    const lastPage = response.total > 0 ? Math.ceil(response.total / PAGE_SIZE) : 1
    if (response.total > 0 && page > lastPage) {
      currentPage.value = lastPage
      await refreshTasks(lastPage)
      return
    }

    taskPage.value = response
    currentPage.value = response.page
    syncActiveTaskPolling(response)
  } catch (err) {
    error.value = normalizeTaskErrorMessage(unwrapClientErrorMessage(err, '发布任务暂时不可用'))
    if (!hasActivePublishingTask(taskPage.value)) {
      stopActiveTaskPolling()
    }
  } finally {
    loading.value = false
  }
}

function startActiveTaskPolling() {
  if (refreshTimer) {
    return
  }
  refreshTimer = setInterval(() => {
    void refreshTasks()
  }, ACTIVE_TASK_POLL_INTERVAL_MS)
}

function stopActiveTaskPolling() {
  if (!refreshTimer) {
    return
  }
  clearInterval(refreshTimer)
  refreshTimer = null
}

function syncActiveTaskPolling(page = taskPage.value) {
  if (hasActivePublishingTask(page)) {
    startActiveTaskPolling()
    return
  }
  stopActiveTaskPolling()
}

function bindPublishLeaseListener() {
  if (runtimeInvalidationUnsubscribe || !window.desktopBridge?.app?.onRuntimeInvalidated) {
    return
  }

  runtimeInvalidationUnsubscribe = window.desktopBridge.app.onRuntimeInvalidated((event) => {
    if (event.reason !== 'publish-task-lease') {
      return
    }

    void refreshTasks(1)
  })
}

function unbindPublishLeaseListener() {
  runtimeInvalidationUnsubscribe?.()
  runtimeInvalidationUnsubscribe = null
}

async function retryTask(taskId: string) {
  actionPendingTaskId.value = taskId

  try {
    await window.desktopBridge.app.retryPublishTask(taskId)
    notifySuccess('已重新加入发布队列')
    await refreshTasks()
  } catch (err) {
    notifyActionError(
      normalizeTaskErrorMessage(unwrapClientErrorMessage(err, '重新发送失败')) ?? '重新发送失败',
    )
  } finally {
    actionPendingTaskId.value = null
  }
}

async function openExternalUrl(url: string | null) {
  if (!url) {
    return
  }

  try {
    await window.desktopBridge.app.openExternalUrl(url)
  } catch (err) {
    notifyActionError(
      normalizeTaskErrorMessage(unwrapClientErrorMessage(err, '打开外链失败')) ?? '打开外链失败',
    )
  }
}

async function openAccountWorkbench(record: PublishTaskItem) {
  const account = accountMap.value.get(record.accountId) ?? null
  consolePendingTaskId.value = record.id

  try {
    await window.desktopBridge.app.openPublishAccountConsole({
      id: record.accountId,
      platform: record.platform,
      platformUid: account?.platformUid ?? '',
      displayName: account?.displayName ?? record.accountName,
    })
  } catch (err) {
    notifyActionError(
      normalizeTaskErrorMessage(unwrapClientErrorMessage(err, '打开账号工作台失败')) ??
        '打开账号工作台失败',
    )
  } finally {
    consolePendingTaskId.value = null
  }
}

async function copyTextToClipboard(text: string): Promise<void> {
  if (navigator.clipboard?.writeText) {
    await navigator.clipboard.writeText(text)
    return
  }

  const textarea = document.createElement('textarea')
  textarea.value = text
  textarea.setAttribute('readonly', 'true')
  textarea.style.position = 'fixed'
  textarea.style.left = '-9999px'
  document.body.appendChild(textarea)
  textarea.select()
  document.execCommand('copy')
  document.body.removeChild(textarea)
}

async function copyErrorMessage(record: PublishTaskItem) {
  if (!record.errorMessage) {
    return
  }

  try {
    await copyTextToClipboard(record.errorMessage)
    copiedErrorTaskId.value = record.id
    notifySuccess('错误详情已复制')
    window.setTimeout(() => {
      if (copiedErrorTaskId.value === record.id) {
        copiedErrorTaskId.value = null
      }
    }, 1_500)
  } catch (err) {
    notifyActionError(
      normalizeTaskErrorMessage(unwrapClientErrorMessage(err, '复制错误详情失败')) ??
        '复制错误详情失败',
    )
  }
}

function applySearch() {
  currentPage.value = 1
  appliedTitle.value = searchTitle.value.trim()
  void refreshTasks(1)
}

function handlePageChange(page: number) {
  currentPage.value = page
  void refreshTasks(page)
}

function rowClassName(record: PublishTaskItem) {
  return isPendingStatus(record.status) ? 'publish-row--pending' : ''
}

watch(searchTitle, (value) => {
  if (!value.trim() && appliedTitle.value) {
    appliedTitle.value = ''
    currentPage.value = 1
    void refreshTasks(1)
  }
})

onMounted(() => {
  bindPublishLeaseListener()
  void refreshTasks()
})

onUnmounted(() => {
  unbindPublishLeaseListener()
  stopActiveTaskPolling()
})

const accountNameMap = computed(() => {
  return new Map((snapshot.value?.accounts ?? []).map((item) => [item.id, item.displayName]))
})

const accountMap = computed(() => {
  return new Map((snapshot.value?.accounts ?? []).map((item) => [item.id, item]))
})

const publishTasks = computed<PublishTaskItem[]>(() =>
  (taskPage.value?.items ?? []).map((task) => {
    const normalizedStatus = normalizePublishTaskStatus(task.status)
    const payload = task.payload ?? {}
    const result = task.result ?? {}
    const taskError = task.error ?? {}
    const publishType = extractString(result.publish_type)
    const complianceBlockedRecordId =
      task.compliance_blocked_record_id ??
      extractNumber(taskError.record_id) ??
      extractNumber(taskError.check_record_id)
    const complianceBlockedReason = summarizeComplianceBlockedReason(
      task.compliance_blocked_reason ?? extractString(taskError.detail),
    )
    const fallbackReason =
      task.publish_job_status === 'blocked_by_compliance'
        ? 'compliance_blocked'
        : extractString(result.fallback_reason)
    const nestedContentRef =
      payload.content_ref &&
      typeof payload.content_ref === 'object' &&
      !Array.isArray(payload.content_ref)
        ? (payload.content_ref as Record<string, JsonValue>)
        : null
    const articleId =
      extractNumber(payload.article_id) ??
      extractNumber(nestedContentRef?.article_id ?? nestedContentRef?.id)

    return {
      id: task.id,
      title: extractString(payload.title) ?? `发布任务 · ${translatePlatform(task.platform)}`,
      articleId,
      platform: task.platform,
      accountId: task.target_account_id,
      accountName: accountNameMap.value.get(task.target_account_id) ?? '待同步账号',
      status: normalizedStatus,
      summary: summaryForTask(normalizedStatus, publishType, fallbackReason),
      attempts: task.attempts,
      leaseExpiresAt: task.lease_expires_at ? parseTimestamp(task.lease_expires_at) : null,
      createdAt: parseTimestamp(task.created_at),
      updatedAt: parseTimestamp(task.updated_at),
      externalArticleUrl:
        publishType === 'draft'
          ? null
          : (extractString(result.external_article_url) ??
            buildWangyihaoArticleUrl(task, result) ??
            buildSohuArticleUrl(task, result)),
      publishType,
      fallbackReason,
      errorMessage: normalizeTaskErrorMessage(
        complianceBlockedReason ??
          extractString(taskError.message) ??
          extractString(taskError.detail) ??
          extractString(taskError.code),
      ),
      complianceBlockedRecordId,
      complianceBlockedAt: task.compliance_blocked_at ? parseTimestamp(task.compliance_blocked_at) : null,
    }
  }),
)

const pendingCount = computed(() => taskPage.value?.pending_count ?? 0)
const historyTotal = computed(() => taskPage.value?.history_total ?? 0)
const totalCount = computed(() => taskPage.value?.total ?? 0)

const tableColumns = [
  { title: '文章', key: 'title', dataIndex: 'title', width: 260, ellipsis: true },
  { title: '账号 / 平台', key: 'account', dataIndex: 'account', width: 168 },
  { title: '状态', key: 'status', dataIndex: 'status', width: 96 },
  { title: '任务信息', key: 'meta', dataIndex: 'meta', width: 280 },
  { title: '时间', key: 'time', dataIndex: 'time', width: 132 },
  { title: '操作', key: 'actions', align: 'right' as const, width: 132, fixed: 'right' as const },
]

const tableScroll = { x: 1080 } as const
</script>

<template>
  <section class="page-container">
    <section class="hero-card">
      <div class="hero-header">
        <div class="hero-title">
          <p class="eyebrow">发布执行台</p>
          <h2>发布管理</h2>
        </div>
        <div class="hero-actions">
          <a-button
            type="primary"
            ghost
            class="modern-btn"
            :loading="loading"
            @click="refreshTasks()"
          >
            <template #icon><ReloadOutlined /></template>
            刷新状态
          </a-button>
        </div>
      </div>
      <div class="stats-strip">
        <div class="stat-card">
          <div class="stat-label">等待发布</div>
          <div class="stat-value" :class="{ 'stat-value--accent': pendingCount > 0 }">
            {{ pendingCount }}
          </div>
        </div>
        <div class="stat-card">
          <div class="stat-label">已发送</div>
          <div class="stat-value">{{ historyTotal }}</div>
        </div>
        <div class="stat-card">
          <div class="stat-label">历史总数</div>
          <div class="stat-value">{{ totalCount }}</div>
        </div>
      </div>
    </section>

    <div v-if="error" class="feedback-banner error">
      <div class="error-text">{{ error }}</div>
    </div>

    <section class="content-section">
      <div class="hero-card" style="padding-bottom: 0">
        <div class="hero-header table-toolbar">
          <div class="toolbar-meta">
            <h3 class="section-title">待发布与已发送记录</h3>
            <p class="section-desc">
              待发布任务始终排在最前，每页 10 条；分页与总数以服务端返回的发布任务为准。
            </p>
          </div>
          <div class="toolbar-filters">
            <a-input
              v-model:value="searchTitle"
              allow-clear
              placeholder="搜索文章标题"
              class="search-input"
              @pressEnter="applySearch"
            >
              <template #prefix>
                <SearchOutlined style="color: #94a3b8" />
              </template>
            </a-input>
            <a-button type="primary" class="modern-btn" @click="applySearch">搜索</a-button>
          </div>
        </div>

        <a-table
          row-key="id"
          class="modern-table publish-table"
          :columns="tableColumns"
          :data-source="publishTasks"
          :pagination="false"
          :loading="loading"
          :scroll="tableScroll"
          :row-class-name="rowClassName"
        >
          <template #emptyText>
            <div class="empty-cell">
              <a-empty description="暂无匹配的发布任务" />
            </div>
          </template>

          <template #bodyCell="{ column, record }">
            <template v-if="column.key === 'title'">
              <div class="info-stack">
                <a-tooltip :title="record.title" placement="topLeft">
                  <span class="cell-title">{{ record.title }}</span>
                </a-tooltip>
                <span class="cell-sub">
                  <template v-if="record.articleId">#{{ record.articleId }}</template>
                  <template v-else>未关联文章</template>
                </span>
              </div>
            </template>

            <template v-else-if="column.key === 'account'">
              <div class="info-stack">
                <span class="cell-title cell-title--mono">{{ record.accountName }}</span>
                <span class="cell-sub platform-line">
                  <span class="platform-logo-mini">
                    <img
                      v-if="platformLogo(record.platform)"
                      :src="platformLogo(record.platform) || ''"
                      :alt="translatePlatform(record.platform)"
                    />
                    <span
                      v-else
                      class="platform-logo-fallback"
                      :style="{ color: platformAccent(record.platform) }"
                    >
                      {{ platformShortName(record.platform) }}
                    </span>
                  </span>
                  <span>{{ translatePlatform(record.platform) }}</span>
                </span>
              </div>
            </template>

            <template v-else-if="column.key === 'status'">
              <StatusBadge :tone="statusToneForTask(record)" :label="statusLabelForTask(record)" />
            </template>

            <template v-else-if="column.key === 'meta'">
              <div class="info-stack meta-cell">
                <span v-if="record.errorMessage" class="error-message-row">
                  <a-popover
                    placement="topLeft"
                    overlay-class-name="publish-error-popover"
                    trigger="hover"
                  >
                    <template #content>
                      <div class="error-popover-panel" @click.stop>
                        <div class="error-popover-header">发布错误</div>
                        <pre class="error-popover-content">{{ record.errorMessage }}</pre>
                        <button
                          class="popover-copy-btn"
                          type="button"
                          :aria-label="
                            copiedErrorTaskId === record.id ? '错误详情已复制' : '复制错误详情'
                          "
                          @click.stop="copyErrorMessage(record)"
                        >
                          <CopyOutlined />
                          <span>
                            {{ copiedErrorTaskId === record.id ? '已复制' : '复制错误详情' }}
                          </span>
                        </button>
                      </div>
                    </template>
                    <span class="error-text error-text--truncate">
                      {{ inlineErrorPreview(record.errorMessage) }}
                    </span>
                  </a-popover>
                </span>
                <span v-else class="cell-sub strong-text">{{ record.summary }}</span>
                <span class="cell-sub meta-footer">
                  <span class="attempt-chip">尝试 {{ record.attempts }} 次</span>
                  <template v-if="record.complianceBlockedRecordId">
                    <span class="meta-divider">·</span>
                    <span>合规记录 #{{ record.complianceBlockedRecordId }}</span>
                  </template>
                  <template v-if="record.complianceBlockedAt">
                    <span class="meta-divider">·</span>
                    <span>{{ formatDateTime(record.complianceBlockedAt) }} 拦截</span>
                  </template>
                  <template v-if="record.leaseExpiresAt">
                    <span class="meta-divider">·</span>
                    <span>租约 {{ formatRelativeTime(record.leaseExpiresAt) }} 到期</span>
                  </template>
                </span>
              </div>
            </template>

            <template v-else-if="column.key === 'time'">
              <a-tooltip
                :title="`创建 ${formatDateTime(record.createdAt)} · 更新 ${formatDateTime(record.updatedAt)}`"
                placement="topLeft"
              >
                <div class="info-stack time-stack">
                  <span class="cell-title cell-title--mono">
                    {{ formatRelativeTime(record.updatedAt) }}
                  </span>
                  <span class="cell-sub">{{ formatDateTime(record.updatedAt) }}</span>
                </div>
              </a-tooltip>
            </template>

            <template v-else-if="column.key === 'actions'">
              <div class="action-buttons">
                <a-tooltip v-if="record.externalArticleUrl" title="打开外链" placement="top">
                  <a-button
                    type="text"
                    class="icon-action-btn"
                    :aria-label="`打开外链：${record.title}`"
                    @click.stop="openExternalUrl(record.externalArticleUrl)"
                  >
                    <template #icon><ExportOutlined /></template>
                  </a-button>
                </a-tooltip>
                <a-tooltip
                  :title="`打开 ${record.accountName} 的${translatePlatform(record.platform)}工作台`"
                  placement="top"
                >
                  <a-button
                    type="text"
                    class="icon-action-btn"
                    :loading="consolePendingTaskId === record.id"
                    :aria-label="`打开 ${record.accountName} 的${translatePlatform(record.platform)}工作台`"
                    @click.stop="openAccountWorkbench(record)"
                  >
                    <template #icon><DesktopOutlined /></template>
                  </a-button>
                </a-tooltip>
                <a-popconfirm
                  v-if="!isPendingStatus(record.status)"
                  title="确定要重试本次发布吗？"
                  placement="topRight"
                  ok-text="确定"
                  cancel-text="取消"
                  @confirm="retryTask(record.id)"
                >
                  <a-tooltip title="再次发送" placement="top">
                    <a-button
                      type="text"
                      class="icon-action-btn"
                      :loading="actionPendingTaskId === record.id"
                      :aria-label="`再次发送：${record.title}`"
                    >
                      <template #icon><SendOutlined /></template>
                    </a-button>
                  </a-tooltip>
                </a-popconfirm>
                <a-tooltip v-else title="排队中...">
                  <div class="action-placeholder-icon">
                    <ClockCircleOutlined />
                  </div>
                </a-tooltip>
              </div>
            </template>
          </template>
        </a-table>

        <div class="table-footer">
          <a-pagination
            :current="currentPage"
            :page-size="PAGE_SIZE"
            :total="totalCount"
            :show-size-changer="false"
            @change="handlePageChange"
          />
        </div>
      </div>
    </section>
  </section>
</template>

<style scoped>
.page-container {
  display: flex;
  flex-direction: column;
  gap: 32px;
  padding-bottom: 40px;
  max-width: 1400px;
}

/* Hero Section (aligned with AccountsView) */
.hero-card {
  background: #ffffff;
  border-radius: 20px;
  border: 1px solid #eef2f6;
  box-shadow:
    0 4px 6px -1px rgba(0, 0, 0, 0.02),
    0 2px 4px -2px rgba(0, 0, 0, 0.01);
  overflow: hidden;
}

.hero-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  padding: 36px 40px;
}

.eyebrow {
  margin: 0 0 12px;
  color: #64748b;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.hero-title h2 {
  margin: 0;
  font-size: 32px;
  font-weight: 800;
  color: #0f172a;
  letter-spacing: -0.02em;
}

.modern-btn {
  border-radius: 8px;
  height: 40px;
  padding: 0 20px;
  font-weight: 600;
}

/* Stats Strip */
.stats-strip {
  display: flex;
  padding: 24px 40px;
  background: #f8fafc;
  border-top: 1px solid #eef2f6;
  gap: 40px;
}

.stat-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1;
}

.stat-label {
  font-size: 14px;
  font-weight: 500;
  color: #64748b;
}

.stat-value {
  font-size: 40px;
  font-weight: 700;
  color: #0f172a;
  line-height: 1;
  font-feature-settings: 'tnum';
}

.stat-value--accent {
  color: #b45309;
}

/* Content Section / Table Toolbar */
.content-section {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.table-toolbar {
  padding: 24px 32px 16px;
  border-bottom: 1px solid #eef2f6;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

.toolbar-meta {
  flex: 1 1 240px;
  min-width: 0;
}

.section-title {
  margin: 0;
  font-size: 20px;
  font-weight: 700;
  color: #0f172a;
}

.section-desc {
  margin: 6px 0 0;
  color: #64748b;
  font-size: 13px;
  line-height: 1.5;
}

.toolbar-filters {
  display: flex;
  gap: 12px;
  align-items: center;
  flex-shrink: 0;
}

.search-input {
  width: 240px;
  border-radius: 8px;
}

/* Modern Enterprise Table (aligned with AccountsView) */
:deep(.publish-table) {
  padding: 0 32px 16px;
}

:deep(.publish-table .ant-table) {
  background: transparent;
}

:deep(.publish-table .ant-table-thead > tr > th) {
  background-color: transparent !important;
  color: #64748b;
  font-weight: 600;
  font-size: 13px;
  border-bottom: 1px solid #e2e8f0;
  padding: 16px 12px;
  white-space: nowrap;
}

:deep(.publish-table .ant-table-tbody > tr > td) {
  padding: 16px 12px;
  border-bottom: 1px solid #f1f5f9;
  color: #0f172a;
  vertical-align: middle;
  transition: background-color 0.15s ease;
  font-size: 13px;
}

:deep(.publish-table .ant-table-tbody > tr:last-child > td) {
  border-bottom: none;
}

:deep(.publish-table .ant-table-tbody > tr:hover > td) {
  background-color: #f8fafc !important;
}

:deep(.publish-table .ant-table-cell-fix-right) {
  background-color: #ffffff !important;
}

:deep(.publish-table .ant-table-tbody > tr:hover > td.ant-table-cell-fix-right) {
  background-color: #f8fafc !important;
}

:deep(.publish-table .publish-row--pending > td) {
  background: #fffbeb !important;
}

:deep(.publish-table .publish-row--pending > td.ant-table-cell-fix-right) {
  background: #fffbeb !important;
}

.empty-cell {
  padding: 56px 20px;
  color: #94a3b8;
}

/* Cell content */
.info-stack {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  max-width: 100%;
}

.cell-title {
  font-weight: 600;
  color: #0f172a;
  font-size: 14px;
  line-height: 1.4;
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cell-title--mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-weight: 500;
  font-size: 13px;
}

.cell-sub {
  color: #64748b;
  font-size: 13px;
  line-height: 1.5;
  display: block;
  margin-top: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cell-sub.strong-text {
  color: #1e293b;
  font-weight: 500;
}

.platform-line {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.platform-logo-mini {
  width: 16px;
  height: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: #ffffff;
  border-radius: 4px;
  box-shadow: inset 0 0 0 1px #e2e8f0;
  overflow: hidden;
  flex: 0 0 auto;
}

.platform-logo-mini img {
  width: 12px;
  height: 12px;
  object-fit: contain;
}

.platform-logo-fallback {
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
}

.time-stack {
  cursor: default;
}

.meta-cell {
  width: 100%;
}

.meta-footer {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  white-space: normal;
  font-size: 11.5px;
  color: #94a3b8;
}

.attempt-chip {
  display: inline-flex;
  align-items: center;
  height: 18px;
  padding: 0 6px;
  border-radius: 4px;
  background: #f1f5f9;
  color: #475569;
  font-size: 11px;
  font-weight: 500;
  line-height: 1;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}

.meta-divider {
  color: #cbd5e1;
}

.error-message-row {
  display: block;
  width: 100%;
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
}

.error-text {
  color: #dc2626;
  font-size: 12.5px;
  font-weight: 500;
}

.error-text--truncate {
  display: block;
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.error-message-row :deep(.ant-popover-open) {
  min-width: 0;
  display: block;
  max-width: 100%;
}

/* Action Buttons */
.action-buttons {
  display: flex;
  justify-content: flex-end;
  gap: 4px;
}

.icon-action-btn {
  color: #64748b;
  border-radius: 6px;
  transition: all 0.15s ease;
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  padding: 0;
}

.icon-action-btn:hover {
  background: #eff6ff;
  color: #2563eb;
  border-color: #dbeafe;
}

.action-placeholder-icon {
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #cbd5e1;
  font-size: 14px;
}

.table-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 16px 32px 24px;
  background: transparent;
}

.feedback-banner {
  padding: 12px 16px;
  border-radius: 10px;
  background: #fff;
  border: 1px solid transparent;
}
.feedback-banner.error {
  border-color: #fca5a5;
  background: #fef2f2;
}

:global(.publish-error-popover .ant-popover-inner) {
  max-width: min(720px, 82vw);
  border: 1px solid #fecaca;
  border-radius: 8px;
  box-shadow: 0 18px 38px rgba(15, 23, 42, 0.16);
}

:global(.publish-error-popover .ant-popover-inner-content) {
  padding: 12px;
}

:global(.publish-error-popover .ant-popover-arrow::before) {
  background: #fff;
  border-color: #fecaca;
}

:global(.publish-error-popover .error-popover-panel) {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: min(520px, 76vw);
  max-width: min(720px, 82vw);
}

:global(.publish-error-popover .error-popover-header) {
  display: flex;
  align-items: center;
  gap: 6px;
  color: #dc2626;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.4;
}

:global(.publish-error-popover .error-popover-header::before) {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #ef4444;
  content: '';
}

:global(.publish-error-popover .error-popover-content) {
  max-height: 360px;
  margin: 0;
  overflow: auto;
  padding: 10px;
  border: 1px solid #fee2e2;
  border-radius: 6px;
  background: #fff7f7;
  color: #991b1b;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 12px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-break: break-word;
}

:global(.publish-error-popover .popover-copy-btn) {
  display: inline-flex;
  align-self: flex-end;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 28px;
  padding: 0 10px;
  border: 1px solid #fecaca;
  border-radius: 6px;
  background: #fff;
  color: #dc2626;
  cursor: pointer;
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
  transition:
    background-color 0.2s ease,
    border-color 0.2s ease;
}

:global(.publish-error-popover .popover-copy-btn:hover),
:global(.publish-error-popover .popover-copy-btn:focus-visible) {
  background: #fef2f2;
  border-color: #f87171;
  outline: none;
}

/* Responsiveness */
@media (max-width: 1024px) {
  .hero-header {
    flex-direction: column;
    gap: 24px;
  }
  .stats-strip {
    flex-wrap: wrap;
    gap: 24px;
  }
}
</style>
