<script setup lang="ts">
import {
  CloseCircleFilled,
  InfoCircleFilled,
  MinusCircleFilled,
  SyncOutlined,
} from '@ant-design/icons-vue'
import { ApiClientError } from '@geo/http-client'
import type {
  ArticleDetail,
  ComplianceCheckResult,
  ComplianceRuntimeStatus,
  CreatePublishJobResponse,
  EnterpriseSiteCategory,
  EnterpriseSiteConnection,
  EnterpriseSitePublishResponse,
  GateDecisionLiteral,
  PublishRecord,
} from '@geo/shared-types'
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
import { message, notification } from 'ant-design-vue'
import { computed, ref, watch, watchEffect } from 'vue'
import { useI18n } from 'vue-i18n'

import CoverPickerModal from '@/components/CoverPickerModal.vue'
import {
  articlesApi,
  complianceApi,
  enterpriseSitesApi,
  mediaApi,
  publishJobsApi,
  resolveApiURL,
  tenantAccountsApi,
} from '@/lib/api'
import { coverUploadRequired, deriveCoverFileName } from '@/lib/cover-requirements'
import { formatError, formatStoredErrorMessage } from '@/lib/errors'
import {
  accountInitial,
  buildPublishAccountCards,
  buildPublishPlatformMap,
  clientStatusLabel,
  clientStatusTooltip,
  healthLabel,
  publishStateLabel,
  type PublishAccountCard,
} from '@/lib/publish-account-cards'
import { normalizePublishPlatformId } from '@/lib/publish-platforms'
import { useCompanyStore } from '@/stores/company'

type PublishTargetTab = 'media_accounts' | 'enterprise_sites'
type PublishType = 'publish' | 'draft'

interface EnterpriseSiteCard {
  id: number
  name: string
  siteUrl: string
  cmsType: string
  status: string
  statusText: string
  selectable: boolean
  disabledReason: string
  categoryCount: number
  defaultCategoryId: string
  hasPublishFailure: boolean
}

const props = defineProps<{
  open: boolean
  articleId: number | null
}>()

const emit = defineEmits<{
  'update:open': [value: boolean]
  published: []
}>()

const { t } = useI18n()
const queryClient = useQueryClient()
const companyStore = useCompanyStore()

const activeTargetTab = ref<PublishTargetTab>('media_accounts')
const selectedAccountIds = ref<string[]>([])
const selectedEnterpriseSiteIds = ref<number[]>([])
const enterpriseCategoriesBySiteId = ref<Record<number, EnterpriseSiteCategory[]>>({})
const enterpriseSiteCategoryIds = ref<Record<number, string>>({})
const enterprisePublishType = ref<PublishType>('publish')
const coverEnabled = ref(false)
const coverAssetUrl = ref('')
const coverFileName = ref('')
const coverImageAssetId = ref<number | null>(null)
const coverPickerOpen = ref(false)
const selectionHydrated = ref(false)
const coverHydrated = ref(false)
const complianceResult = ref<ComplianceCheckResult | null>(null)
const publishAdmissionResult = ref<ComplianceCheckResult | null>(null)
const publishAdmissionLoading = ref(false)
const publishAdmissionError = ref<string | null>(null)
let publishAdmissionRunId = 0
const ackInProgress = ref(false)

const detailQuery = useQuery({
  queryKey: computed(() => [
    'articles',
    'detail',
    companyStore.currentBrandId,
    props.articleId,
    'publish-modal',
  ]),
  enabled: computed(
    () => props.open && Boolean(props.articleId) && Boolean(companyStore.currentBrandId),
  ),
  queryFn: () => articlesApi.detail(props.articleId as number),
})

const accountsQuery = useQuery({
  queryKey: ['tenant', 'desktop-accounts', 'publish-modal'],
  enabled: computed(() => props.open),
  queryFn: () => tenantAccountsApi.list(),
})

const publishRecordsQuery = useQuery({
  queryKey: computed(() => [
    'articles',
    'publish-records',
    companyStore.currentBrandId,
    props.articleId,
    'publish-modal',
  ]),
  enabled: computed(
    () => props.open && Boolean(props.articleId) && Boolean(companyStore.currentBrandId),
  ),
  queryFn: () => articlesApi.publishRecords(props.articleId as number),
})

const platformsQuery = useQuery({
  queryKey: ['media', 'platforms', 'publish-modal'],
  enabled: computed(() => props.open),
  queryFn: () => mediaApi.platforms(),
})

const enterpriseSitesQuery = useQuery({
  queryKey: ['tenant', 'enterprise-sites', 'publish-modal'],
  enabled: computed(() => props.open),
  queryFn: () => enterpriseSitesApi.list(),
})

const complianceStatusQuery = useQuery({
  queryKey: ['compliance', 'runtime-status', 'publish-modal'],
  enabled: computed(() => props.open),
  queryFn: () => complianceApi.runtimeStatus(),
})

watch(
  () => props.open,
  async (open) => {
    if (!open) {
      activeTargetTab.value = 'media_accounts'
      selectedAccountIds.value = []
      selectedEnterpriseSiteIds.value = []
      enterpriseCategoriesBySiteId.value = {}
      enterpriseSiteCategoryIds.value = {}
      enterprisePublishType.value = 'publish'
      coverAssetUrl.value = ''
      coverFileName.value = ''
      coverImageAssetId.value = null
      coverEnabled.value = false
      selectionHydrated.value = false
      coverHydrated.value = false
      complianceResult.value = null
      publishAdmissionResult.value = null
      publishAdmissionLoading.value = false
      publishAdmissionError.value = null
      return
    }

    selectionHydrated.value = false
    coverHydrated.value = false

    await Promise.allSettled([
      props.articleId ? detailQuery.refetch() : Promise.resolve(),
      props.articleId ? publishRecordsQuery.refetch() : Promise.resolve(),
      accountsQuery.refetch(),
      platformsQuery.refetch(),
      enterpriseSitesQuery.refetch(),
      complianceStatusQuery.refetch(),
    ])
  },
  { immediate: true },
)

const platformMap = computed(() => buildPublishPlatformMap(platformsQuery.data.value ?? []))
const successfulPublishRecordKeys = computed(
  () =>
    new Set(
      (publishRecordsQuery.data.value ?? [])
        .filter((record) => isSuccessfulPublishRecord(record))
        .map((record) => successfulPublishRecordKey(record.desktop_account_id, record.platform_id))
        .filter((key): key is string => Boolean(key)),
    ),
)
const activePublishRecordStatusByKey = computed(() => {
  const statusByKey = new Map<string, string>()
  for (const record of publishRecordsQuery.data.value ?? []) {
    if (!isActivePublishRecord(record)) {
      continue
    }
    const key = successfulPublishRecordKey(record.desktop_account_id, record.platform_id)
    if (!key || statusByKey.has(key)) {
      continue
    }
    statusByKey.set(key, record.status)
  }
  return statusByKey
})
const publishedAccountIds = computed(
  () =>
    new Set(
      (publishRecordsQuery.data.value ?? [])
        .filter((record) => isSuccessfulPublishRecord(record))
        .map((record) => record.desktop_account_id?.trim())
        .filter((id): id is string => Boolean(id)),
    ),
)
const publishedAccountCount = computed(() => publishedAccountIds.value.size)
const successfulEnterpriseSiteIds = computed(
  () =>
    new Set(
      (publishRecordsQuery.data.value ?? [])
        .filter((record) => isSuccessfulEnterpriseSiteRecord(record))
        .map((record) => record.target_connection_id)
        .filter((id): id is number => typeof id === 'number'),
    ),
)
const activeEnterpriseRecordStatusBySiteId = computed(() => {
  const statusBySiteId = new Map<number, string>()
  for (const record of publishRecordsQuery.data.value ?? []) {
    if (!isActiveEnterpriseSiteRecord(record) || typeof record.target_connection_id !== 'number') {
      continue
    }
    if (statusBySiteId.has(record.target_connection_id)) {
      continue
    }
    statusBySiteId.set(record.target_connection_id, record.status)
  }
  return statusBySiteId
})
const publishedEnterpriseSiteCount = computed(() => successfulEnterpriseSiteIds.value.size)

const accountCards = computed<PublishAccountCard[]>(() =>
  buildPublishAccountCards(accountsQuery.data.value ?? [], platformMap.value).map((card) => {
    const alreadyPublished = successfulPublishRecordKeys.value.has(
      successfulPublishRecordKey(card.id, card.platformId) ?? '',
    )
    if (!alreadyPublished) {
      return card
    }
    return {
      ...card,
      selectable: false,
      statusText:
        '这篇文章已用该账号在该平台发布成功，不能重复发布；如需重新发布，请复制或新建一篇文章。',
    }
  }),
)
const enterpriseSites = computed(() => enterpriseSitesQuery.data.value ?? [])
const enterpriseSiteCards = computed<EnterpriseSiteCard[]>(() =>
  enterpriseSites.value.map((site) => {
    const alreadyPublished = successfulEnterpriseSiteIds.value.has(site.id)
    const activeStatus = activeEnterpriseRecordStatusBySiteId.value.get(site.id)
    const connectionStatus = enterpriseSiteConnectionStatus(site)
    const publishError = enterpriseSiteLatestPublishError(site)
    const disabledReason = resolveEnterpriseSiteDisabledReason(
      site,
      alreadyPublished,
      activeStatus,
      connectionStatus,
    )
    return {
      id: site.id,
      name: site.name,
      siteUrl: site.site_url,
      cmsType: cmsTypeLabel(site.cms_type),
      status: connectionStatus,
      statusText: alreadyPublished
        ? '已发布'
        : activeStatus
          ? activeEnterpriseRecordLabel(activeStatus)
          : publishError
            ? '发布失败'
            : enterpriseSiteStatusLabel(connectionStatus),
      selectable: !disabledReason,
      disabledReason,
      categoryCount: site.category_count,
      defaultCategoryId: site.default_scode?.trim() || '',
      hasPublishFailure: !disabledReason && Boolean(publishError),
    }
  }),
)

watch(
  accountCards,
  (cards) => {
    const selectableIds = new Set(cards.filter((card) => card.selectable).map((card) => card.id))
    selectedAccountIds.value = selectedAccountIds.value.filter((id) => selectableIds.has(id))
  },
  { immediate: true },
)

watch(
  enterpriseSiteCards,
  (cards) => {
    const selectableIds = new Set(cards.filter((card) => card.selectable).map((card) => card.id))
    selectedEnterpriseSiteIds.value = selectedEnterpriseSiteIds.value.filter((id) =>
      selectableIds.has(id),
    )
  },
  { immediate: true },
)

watchEffect(() => {
  if (!props.open) {
    return
  }

  if (!coverHydrated.value && !detailQuery.isPending.value && !detailQuery.isFetching.value) {
    const initialUrl = resolveApiURL(detailQuery.data.value?.cover_asset_url)
    coverAssetUrl.value = initialUrl
    coverFileName.value = deriveCoverFileName(initialUrl)
    coverImageAssetId.value = detailQuery.data.value?.cover_image_asset_id ?? null
    coverEnabled.value = Boolean(initialUrl)
    coverHydrated.value = true
  }

  if (selectionHydrated.value) {
    return
  }

  if (
    detailQuery.isPending.value ||
    detailQuery.isFetching.value ||
    accountsQuery.isPending.value ||
    accountsQuery.isFetching.value ||
    enterpriseSitesQuery.isPending.value ||
    enterpriseSitesQuery.isFetching.value ||
    publishRecordsQuery.isPending.value ||
    publishRecordsQuery.isFetching.value ||
    platformsQuery.isPending.value ||
    platformsQuery.isFetching.value
  ) {
    return
  }

  selectedAccountIds.value = []
  selectionHydrated.value = true
})

const selectedCards = computed(() => {
  const selected = new Set(selectedAccountIds.value)
  return accountCards.value.filter((card) => selected.has(card.id))
})
const selectedEnterpriseSiteCards = computed(() => {
  const selected = new Set(selectedEnterpriseSiteIds.value)
  return enterpriseSiteCards.value.filter((card) => selected.has(card.id))
})

const modalTitle = computed(() => detailQuery.data.value?.title || t('article.untitled'))
const enterpriseTargetPlatformId = computed(() =>
  selectedEnterpriseSiteCards.value.length > 0 ? 'pbootcms' : '',
)
const selectedPlatformIds = computed(() =>
  activeTargetTab.value === 'enterprise_sites'
    ? enterpriseTargetPlatformId.value
      ? [enterpriseTargetPlatformId.value]
      : []
    : Array.from(new Set(selectedCards.value.map((card) => card.platformId))),
)
watch([selectedPlatformIds, activeTargetTab], () => {
  complianceResult.value = null
})
const selectedImmediateCount = computed(
  () => selectedCards.value.filter((card) => card.publishState === 'immediate').length,
)
const selectedQueuedCount = computed(
  () => selectedCards.value.filter((card) => card.publishState === 'queued').length,
)
const selectedTargetCount = computed(() =>
  activeTargetTab.value === 'enterprise_sites'
    ? selectedEnterpriseSiteCards.value.length
    : selectedCards.value.length,
)
const selectedPublishNowCount = computed(() =>
  activeTargetTab.value === 'enterprise_sites'
    ? selectedEnterpriseSiteCards.value.length
    : selectedImmediateCount.value,
)
const selectedQueueCount = computed(() =>
  activeTargetTab.value === 'enterprise_sites' ? 0 : selectedQueuedCount.value,
)
const selectedTargetLabel = computed(() =>
  activeTargetTab.value === 'enterprise_sites' ? '已选站点' : '已选账号',
)
const publishNowLabel = computed(() =>
  activeTargetTab.value === 'enterprise_sites' ? '立即发文' : '立即发布',
)
const queueLabel = computed(() =>
  activeTargetTab.value === 'enterprise_sites' ? '离线队列' : '离线排队',
)
const publishModalVisible = computed(() => props.open && !coverPickerOpen.value)
const coverRequired = computed(() => coverUploadRequired(selectedPlatformIds.value))
const effectiveCoverEnabled = computed(() => coverRequired.value || coverEnabled.value)
const normalizedCoverValue = computed(() =>
  effectiveCoverEnabled.value ? coverAssetUrl.value.trim() : '',
)
const publishMutation = useMutation({
  mutationFn: () => runPublishFlow(),
  onSuccess: handlePublishSuccess,
  onError: handlePublishError,
})
const syncEnterpriseCategoriesMutation = useMutation({
  mutationFn: (siteId: number) => enterpriseSitesApi.syncCategories(siteId),
  onSuccess: async (categories, siteId) => {
    message.success(`已同步 ${categories.length} 个栏目`)
    setEnterpriseCategories(siteId, categories)
    await enterpriseSitesQuery.refetch()
  },
  onError: (error) => message.error(formatError(error)),
})
const complianceStatus = computed<ComplianceRuntimeStatus | null>(
  () => complianceStatusQuery.data.value ?? null,
)
const publishComplianceEnabled = computed(
  () =>
    complianceStatus.value?.enabled === true &&
    complianceStatus.value.enforcement_mode !== 'disabled',
)
const publishComplianceMode = computed(() => complianceStatus.value?.enforcement_mode ?? 'disabled')
const publishComplianceMandatory = computed(
  () => publishComplianceEnabled.value && publishComplianceMode.value === 'mandatory',
)
const publishAdmissionPassed = computed(
  () => complianceDisplayDecision(publishAdmissionResult.value) === 'pass',
)

function enterpriseSelectPopupContainer(triggerNode: HTMLElement): HTMLElement {
  return (triggerNode.closest('.ant-modal-content') as HTMLElement | null) ?? document.body
}
const mandatoryAdmissionBlocking = computed(
  () => publishComplianceMandatory.value && !publishAdmissionPassed.value,
)
const accountSelectionLocked = computed(
  () => publishMutation.isPending.value || ackInProgress.value || mandatoryAdmissionBlocking.value,
)
const publishBusy = computed(() => publishMutation.isPending.value || ackInProgress.value)
const accountSelectionLockSpinning = computed(
  () =>
    publishAdmissionLoading.value ||
    (publishComplianceMandatory.value &&
      !publishAdmissionResult.value &&
      !publishAdmissionError.value &&
      !publishAdmissionPassed.value),
)
const accountSelectionLockMessage = computed(() => {
  if (publishMutation.isPending.value || ackInProgress.value) {
    return '发布任务提交中，请稍候。'
  }
  if (accountSelectionLockSpinning.value) {
    return '强制敏感词检测中，检测通过后可选择目标账号。\n请回到文章编辑页，使用合规检测查看全部命中词并逐项修改。'
  }
  if (publishAdmissionError.value) {
    return '强制敏感词检测暂时不可用，当前不能选择目标账号。\n请回到文章编辑页，使用合规检测查看全部命中词并逐项修改。'
  }
  return '当前文章未通过强制敏感词检测，暂不能选择目标账号。\n请回到文章编辑页，使用合规检测查看全部命中词并逐项修改。'
})
const okText = computed(() => {
  if (publishComplianceMandatory.value && publishAdmissionLoading.value) {
    return '检测中'
  }
  if (publishComplianceMandatory.value && publishAdmissionError.value) {
    return '检测失败'
  }
  if (
    publishComplianceMandatory.value &&
    publishAdmissionResult.value &&
    complianceDisplayDecision(publishAdmissionResult.value) !== 'pass'
  ) {
    return '检测未通过'
  }
  if (
    publishComplianceMandatory.value &&
    complianceResult.value &&
    complianceDisplayDecision(complianceResult.value) !== 'pass'
  ) {
    return '检测未通过'
  }
  if (publishComplianceMandatory.value && !publishAdmissionPassed.value) {
    return '检测中'
  }
  if (complianceDisplayDecision(complianceResult.value) === 'needs_ack') {
    return '已了解风险，继续发布'
  }
  if (complianceDisplayDecision(complianceResult.value) === 'block') {
    return '无法发布'
  }
  return '提交发布'
})
const okDisabled = computed(() => {
  if (activeTargetTab.value === 'enterprise_sites' && !selectedEnterpriseSiteIds.value.length) {
    return true
  }
  if (
    activeTargetTab.value === 'enterprise_sites' &&
    selectedEnterpriseSiteCards.value.some(
      (site) => !enterpriseSiteCategoryIds.value[site.id]?.trim(),
    )
  ) {
    return true
  }
  if (coverRequired.value && !normalizedCoverValue.value) {
    return true
  }
  if (mandatoryAdmissionBlocking.value) {
    return true
  }
  if (
    publishComplianceMandatory.value &&
    complianceResult.value &&
    complianceDisplayDecision(complianceResult.value) !== 'pass'
  ) {
    return true
  }
  return complianceDisplayDecision(complianceResult.value) === 'block'
})
const compliancePanelTitle = computed(() => {
  if (complianceDisplayDecision(complianceResult.value) === 'needs_ack') {
    return '发布前需要确认合规风险'
  }
  if (complianceDisplayDecision(complianceResult.value) === 'block') {
    return '检测到敏感词，已阻断发布'
  }
  return ''
})
const publishCompliancePanelVisible = computed(
  () =>
    publishComplianceEnabled.value &&
    (publishAdmissionLoading.value ||
      Boolean(publishAdmissionResult.value) ||
      Boolean(publishAdmissionError.value) ||
      Boolean(complianceResult.value)),
)
const publishCompliancePanelTone = computed(() => {
  const finalDecision = complianceDisplayDecision(complianceResult.value)
  const admissionDecision = complianceDisplayDecision(publishAdmissionResult.value)
  if (publishAdmissionError.value || finalDecision === 'block' || admissionDecision === 'block') {
    return 'error'
  }
  if (publishAdmissionLoading.value) {
    return 'info'
  }
  if (admissionDecision === 'needs_ack' || finalDecision === 'needs_ack') {
    return publishComplianceMandatory.value ? 'error' : 'warning'
  }
  return 'success'
})
const publishCompliancePanelTitle = computed(() => {
  if (publishAdmissionLoading.value) {
    return publishComplianceMandatory.value ? '正在进行强制敏感词检测' : '正在加载敏感词检测状态'
  }
  if (publishAdmissionError.value) {
    return '敏感词检测暂时不可用'
  }
  const finalDecision = complianceDisplayDecision(complianceResult.value)
  const admissionDecision = complianceDisplayDecision(publishAdmissionResult.value)
  if (finalDecision === 'block') {
    return publishComplianceMandatory.value ? '检测到敏感词，已阻断发布' : '检测到敏感词风险'
  }
  if (finalDecision === 'needs_ack') {
    return publishComplianceMandatory.value ? '检测到敏感词，已阻断发布' : '检测到敏感词风险'
  }
  if (finalDecision === 'pass') {
    return '敏感词检测通过'
  }
  if (admissionDecision === 'block') {
    return publishComplianceMandatory.value ? '检测到敏感词，已阻断发布' : '检测到敏感词风险'
  }
  if (admissionDecision === 'needs_ack') {
    return publishComplianceMandatory.value ? '检测到敏感词，已阻断发布' : '检测到敏感词风险'
  }
  if (admissionDecision === 'pass') {
    return '敏感词检测通过'
  }
  return compliancePanelTitle.value
})
const publishCompliancePanelDescription = computed(() => {
  if (publishAdmissionLoading.value) {
    return publishComplianceMandatory.value
      ? '检测完成前暂时不能选择目标账号或提交发布。'
      : '仅提示确认模式不会阻断目标账号选择和发布操作。'
  }
  if (publishAdmissionError.value) {
    return publishComplianceMandatory.value
      ? '当前为强制阻断模式，请稍后重试或联系运营处理。'
      : '当前为仅提示确认模式，仍可继续选择账号并发布。'
  }
  const finalDecision = complianceDisplayDecision(complianceResult.value)
  const admissionDecision = complianceDisplayDecision(publishAdmissionResult.value)
  if (finalDecision === 'block') {
    return publishComplianceMandatory.value
      ? '请回到文章编辑页，使用合规检测查看全部命中词并逐项修改。'
      : '请回到文章编辑页，使用合规检测查看全部命中词。'
  }
  if (finalDecision === 'needs_ack') {
    return publishComplianceMandatory.value
      ? '请回到文章编辑页，使用合规检测查看全部命中词并逐项修改。'
      : '请回到文章编辑页，使用合规检测查看全部命中词；确认风险后仍可继续发布。'
  }
  if (finalDecision === 'pass') {
    return '当前文章未命中敏感词风险。'
  }
  if (admissionDecision === 'block') {
    return publishComplianceMandatory.value
      ? '请回到文章编辑页，使用合规检测查看全部命中词并逐项修改。'
      : '请回到文章编辑页，使用合规检测查看全部命中词。'
  }
  if (admissionDecision === 'needs_ack') {
    return publishComplianceMandatory.value
      ? '请回到文章编辑页，使用合规检测查看全部命中词并逐项修改。'
      : '请回到文章编辑页，使用合规检测查看全部命中词；确认风险后仍可继续发布。'
  }
  if (admissionDecision === 'pass') {
    return '当前文章未命中敏感词风险。'
  }
  return '请回到文章编辑页，使用合规检测查看全部命中词。'
})
const displayedComplianceResult = computed(
  () => complianceResult.value ?? publishAdmissionResult.value,
)
const complianceAcknowledgedViolationIds = computed(() =>
  (complianceResult.value?.violations ?? []).map((item) => item.id),
)

watch(
  coverRequired,
  (required) => {
    if (required) {
      coverEnabled.value = true
    }
  },
  { immediate: true },
)

watch(
  [
    () => props.open,
    () => detailQuery.data.value?.id,
    () => detailQuery.data.value?.current_version_id,
    () => complianceStatusQuery.data.value?.enabled,
    () => complianceStatusQuery.data.value?.enforcement_mode,
  ],
  () => {
    void runPublishAdmissionCheck()
  },
  { immediate: true },
)

async function runPublishFlow(ackRecordId?: number) {
  if (!props.articleId || !detailQuery.data.value) {
    throw new Error('missing_article')
  }
  if (activeTargetTab.value === 'enterprise_sites') {
    return runEnterpriseSitePublishFlow(ackRecordId)
  }
  if (!selectedAccountIds.value.length) {
    throw new Error('no_accounts_selected')
  }
  if (coverRequired.value && !normalizedCoverValue.value) {
    throw new Error('cover_required_for_selected_platforms')
  }

  const freshDetail = await loadLatestPublishArticleDetail()
  const article = await persistCoverIfNeeded(freshDetail)
  const articleVersionId = article.current_version_id ?? detailQuery.data.value.current_version_id
  if (!articleVersionId) {
    throw new Error('invalid_article_version')
  }
  if (publishComplianceEnabled.value && !ackRecordId) {
    const result = await complianceApi.checkArticle(article.id, {
      article_version_id: articleVersionId,
      title: article.title,
      plaintext: article.markdown_content ?? '',
      target_platforms: selectedPlatformIds.value,
      trigger_source: 'publish_gate',
    })
    complianceResult.value = result
    const decision = complianceDisplayDecision(result)
    if (decision === 'block' || decision === 'needs_ack') {
      throw new Error(`compliance_${decision}`)
    }
  }
  return publishJobsApi.create({
    title: article.title?.trim() || t('article.untitled'),
    content_ref: {
      article_id: article.id,
    },
    article_version_id: articleVersionId,
    ack_record_id: ackRecordId,
    accounts: selectedAccountIds.value.map((accountId) => ({
      account_id: accountId,
    })),
  })
}

async function runEnterpriseSitePublishFlow(
  ackRecordId?: number,
): Promise<EnterpriseSitePublishResponse[]> {
  if (!props.articleId || !detailQuery.data.value) {
    throw new Error('missing_article')
  }
  if (!selectedEnterpriseSiteIds.value.length) {
    throw new Error('no_enterprise_sites_selected')
  }
  const missingCategorySite = selectedEnterpriseSiteCards.value.find(
    (site) => !enterpriseSiteCategoryIds.value[site.id]?.trim(),
  )
  if (missingCategorySite) {
    throw new Error('enterprise_site_category_required')
  }

  const freshDetail = await loadLatestPublishArticleDetail()
  const article = await persistCoverIfNeeded(freshDetail)
  const articleVersionId = article.current_version_id ?? detailQuery.data.value.current_version_id
  if (!articleVersionId) {
    throw new Error('invalid_article_version')
  }
  if (publishComplianceEnabled.value && !ackRecordId) {
    const result = await complianceApi.checkArticle(article.id, {
      article_version_id: articleVersionId,
      title: article.title,
      plaintext: article.markdown_content ?? '',
      target_platforms: selectedPlatformIds.value,
      trigger_source: 'publish_gate',
    })
    complianceResult.value = result
    const decision = complianceDisplayDecision(result)
    if (decision === 'block' || decision === 'needs_ack') {
      throw new Error(`compliance_${decision}`)
    }
  }

  const results: EnterpriseSitePublishResponse[] = []
  for (const [index, site] of selectedEnterpriseSiteCards.value.entries()) {
    const categoryId = enterpriseSiteCategoryIds.value[site.id]?.trim()
    if (!categoryId) {
      throw new Error('enterprise_site_category_required')
    }
    let siteAckRecordId = ackRecordId
    if (ackRecordId && index > 0 && complianceResult.value?.record_id) {
      const ack = await complianceApi.createAck(complianceResult.value.record_id, {
        acknowledged_violation_ids: complianceAcknowledgedViolationIds.value,
      })
      siteAckRecordId = ack.id
    }
    results.push(
      await enterpriseSitesApi.publish(site.id, {
        article_id: article.id,
        category_id: categoryId,
        publish_type: enterprisePublishType.value,
        author: 'Geo SaaS',
        source: 'Geo SaaS',
        ack_record_id: siteAckRecordId,
        article_version_id: articleVersionId,
        cover_asset_url: normalizedCoverValue.value || null,
        cover_image_asset_id: normalizedCoverValue.value ? coverImageAssetId.value : null,
      }),
    )
  }
  return results
}

async function loadLatestPublishArticleDetail(): Promise<ArticleDetail> {
  if (!props.articleId) {
    throw new Error('missing_article')
  }
  const result = await detailQuery.refetch()
  const article = result.data ?? detailQuery.data.value
  if (!article) {
    throw new Error('missing_article')
  }
  return article
}

async function handlePublishSuccess(
  result: CreatePublishJobResponse | EnterpriseSitePublishResponse[],
) {
  if (Array.isArray(result)) {
    await handleEnterprisePublishSuccess(result)
    return
  }
  const description = successDescription(result)
  const options = {
    message: result.created_task_ids.length === 0 ? '发布任务已存在' : '已提交发布任务',
    description,
    placement: 'topRight',
    duration: 5,
  } as const
  if (result.created_task_ids.length === 0 && result.existing_task_ids.length > 0) {
    notification.info(options)
  } else {
    notification.success(options)
  }

  await invalidatePublishModalQueries()

  emit('published')
  emit('update:open', false)
}

async function invalidatePublishModalQueries(): Promise<void> {
  await Promise.all([
    queryClient.invalidateQueries({ queryKey: ['articles'] }),
    queryClient.invalidateQueries({ queryKey: ['workspace'] }),
    queryClient.invalidateQueries({ queryKey: ['tenant', 'desktop-accounts'] }),
    queryClient.invalidateQueries({ queryKey: ['tenant', 'enterprise-sites'] }),
    queryClient.invalidateQueries({ queryKey: ['articles', 'detail', props.articleId] }),
    queryClient.invalidateQueries({
      queryKey: ['articles', 'detail', props.articleId, 'publish-modal'],
    }),
    queryClient.invalidateQueries({ queryKey: ['articles', 'publish-records', props.articleId] }),
  ])
}

function handlePublishError(error: unknown) {
  const gate = parseComplianceResultFromError(error)
  if (gate) {
    complianceResult.value = gate
    return
  }
  const normalized = error instanceof Error ? error.message : ''
  if (normalized === 'no_accounts_selected') {
    message.warning('请先选择至少一个可发布账号')
    return
  }
  if (normalized === 'no_enterprise_sites_selected') {
    message.warning('请先选择至少一个企业自建站点')
    return
  }
  if (normalized === 'enterprise_site_category_required') {
    message.warning('请选择每个企业站点的 CMS 栏目')
    return
  }
  if (normalized === 'cover_required_for_selected_platforms') {
    message.warning(t('media.publish.messages.coverRequired'))
    return
  }
  if (normalized === 'compliance_needs_ack' || normalized === 'compliance_block') {
    return
  }
  if (error instanceof ApiClientError && error.message === 'desktop_publish_duplicate') {
    message.warning('内容已在发布队列中，未重复提交')
    return
  }
  message.error(formatError(error))
}

async function handleEnterprisePublishSuccess(
  results: EnterpriseSitePublishResponse[],
): Promise<void> {
  const succeeded = results.filter((item) => item.status === 'success')
  const failed = results.filter((item) => item.status !== 'success')
  if (failed.length > 0 && succeeded.length > 0) {
    notification.warning({
      message: '企业站发布部分成功',
      description: `成功 ${succeeded.length} 个站点，失败 ${failed.length} 个站点。`,
      placement: 'topRight',
      duration: 6,
    })
  } else if (failed.length > 0) {
    notification.error({
      message: '企业站发布失败',
      description:
        formatStoredErrorMessage(failed[0]?.error_message) ||
        '请检查插件、栏目和 PBootCMS API 配置。',
      placement: 'topRight',
      duration: 6,
    })
  } else {
    notification.success({
      message: enterprisePublishType.value === 'draft' ? '企业站草稿已保存' : '企业站发布成功',
      description:
        succeeded.length === 1
          ? succeeded[0]?.external_article_url || 'PBootCMS 已返回成功。'
          : `共 ${succeeded.length} 个企业站点已完成。`,
      placement: 'topRight',
      duration: 5,
    })
  }

  await invalidatePublishModalQueries()

  if (failed.length === 0) {
    emit('published')
    emit('update:open', false)
  }
}

async function persistCoverIfNeeded(detail: ArticleDetail): Promise<ArticleDetail> {
  const currentUrl = resolveApiURL(detail.cover_asset_url).trim()
  const nextUrl = normalizedCoverValue.value.trim()
  const currentAssetId = detail.cover_image_asset_id ?? null

  if (currentUrl === nextUrl && currentAssetId === coverImageAssetId.value) {
    return detail
  }

  return articlesApi.update(detail.id, {
    title: detail.title?.trim() || t('article.untitled'),
    markdown_content: detail.markdown_content ?? '',
    cover_asset_url: nextUrl || null,
    cover_image_asset_id: coverImageAssetId.value,
  })
}

function successDescription(result: CreatePublishJobResponse): string {
  const created = result.created_task_ids.length
  const existing = result.existing_task_ids.length
  const total = created + existing
  if (total > 0) {
    if (created === 0) {
      return `共 ${existing} 个账号已有发布记录或任务，未重复提交；如结果未知，请先核实平台侧结果。`
    }
    if (existing > 0) {
      return `新建 ${created} 个发布任务；${existing} 个账号已有发布记录或任务，未重复提交。`
    }
  }

  const selectedTotal = selectedCards.value.length
  if (selectedTotal === 0) {
    return '任务已进入发布队列。'
  }
  if (selectedQueuedCount.value === 0) {
    return `共 ${selectedTotal} 个账号，在线客户端会立即开始消费。`
  }
  if (selectedImmediateCount.value === 0) {
    return `共 ${selectedTotal} 个账号已进入离线队列，客户端上线后会自动继续发布。`
  }
  return `共 ${selectedTotal} 个账号：${selectedImmediateCount.value} 个立即发布，${selectedQueuedCount.value} 个离线排队。`
}

function toggleAccount(accountId: string, selectable: boolean): void {
  if (!selectable || accountSelectionLocked.value) {
    return
  }

  if (selectedAccountIds.value.includes(accountId)) {
    selectedAccountIds.value = selectedAccountIds.value.filter((id) => id !== accountId)
    return
  }

  selectedAccountIds.value = [...selectedAccountIds.value, accountId]
}

function toggleEnterpriseSite(site: EnterpriseSiteCard): void {
  if (!site.selectable || accountSelectionLocked.value) {
    return
  }

  if (selectedEnterpriseSiteIds.value.includes(site.id)) {
    selectedEnterpriseSiteIds.value = selectedEnterpriseSiteIds.value.filter((id) => id !== site.id)
    return
  }

  selectedEnterpriseSiteIds.value = [...selectedEnterpriseSiteIds.value, site.id]
  if (!enterpriseSiteCategoryIds.value[site.id]) {
    enterpriseSiteCategoryIds.value = {
      ...enterpriseSiteCategoryIds.value,
      [site.id]: site.defaultCategoryId,
    }
  }
  void loadEnterpriseCategories(site.id)
}

function isSelected(accountId: string): boolean {
  return selectedAccountIds.value.includes(accountId)
}

function isEnterpriseSiteSelected(siteId: number): boolean {
  return selectedEnterpriseSiteIds.value.includes(siteId)
}

function isAlreadyPublished(account: PublishAccountCard): boolean {
  return successfulPublishRecordKeys.value.has(
    successfulPublishRecordKey(account.id, account.platformId) ?? '',
  )
}

function activePublishRecordStatus(account: PublishAccountCard): string | null {
  return (
    activePublishRecordStatusByKey.value.get(
      successfulPublishRecordKey(account.id, account.platformId) ?? '',
    ) ?? null
  )
}

function activePublishRecordLabel(account: PublishAccountCard): string | null {
  switch (activePublishRecordStatus(account)) {
    case 'publishing':
    case 'running':
      return '发布中'
    case 'queued':
    case 'pending':
      return '已排队'
    default:
      return null
  }
}

function activeEnterpriseRecordLabel(status: string): string {
  switch (String(status ?? '').trim()) {
    case 'publishing':
    case 'running':
      return '发布中'
    case 'queued':
    case 'pending':
      return '已排队'
    default:
      return '已提交'
  }
}

function successfulPublishRecordKey(
  desktopAccountId?: string | null,
  platformId?: string | null,
): string | null {
  const accountID = String(desktopAccountId ?? '').trim()
  const platform = normalizePublishPlatformId(platformId)
  if (!accountID || !platform) {
    return null
  }
  return `${accountID}:${platform}`
}

function isSuccessfulPublishRecord(record: PublishRecord): boolean {
  switch (String(record.status ?? '').trim()) {
    case 'success':
      return Boolean(record.desktop_account_id)
    default:
      return false
  }
}

function isSuccessfulEnterpriseSiteRecord(record: PublishRecord): boolean {
  return (
    record.target_type === 'enterprise_site' && String(record.status ?? '').trim() === 'success'
  )
}

function isActivePublishRecord(record: PublishRecord): boolean {
  switch (String(record.status ?? '').trim()) {
    case 'queued':
    case 'pending':
    case 'publishing':
    case 'running':
      return Boolean(record.desktop_account_id)
    default:
      return false
  }
}

function isActiveEnterpriseSiteRecord(record: PublishRecord): boolean {
  if (record.target_type !== 'enterprise_site') {
    return false
  }
  switch (String(record.status ?? '').trim()) {
    case 'queued':
    case 'pending':
    case 'publishing':
    case 'running':
      return true
    default:
      return false
  }
}

function resolveEnterpriseSiteDisabledReason(
  site: EnterpriseSiteConnection,
  alreadyPublished: boolean,
  activeStatus?: string,
  connectionStatus = site.status,
): string {
  if (alreadyPublished) {
    return '这篇文章已成功发布到该企业站点；如需重新发布，请复制或新建一篇文章。'
  }
  if (activeStatus) {
    return '该企业站点已有当前文章的发布任务，暂不能重复提交。'
  }
  if (site.status === 'disabled') {
    return '该企业站点已禁用。'
  }
  if (connectionStatus === 'error') {
    return formatStoredErrorMessage(site.last_error) || '该企业站点连接异常，请先测试连通性。'
  }
  return ''
}

function enterpriseSiteConnectionStatus(site: EnterpriseSiteConnection): string {
  const status = String(site.status ?? '').trim()
  const lastError = formatStoredErrorMessage(site.last_error)
  const publishError = enterpriseSiteLatestPublishError(site)
  if (status === 'error' && lastError && publishError && lastError === publishError) {
    return 'connected'
  }
  return status
}

function enterpriseSiteLatestPublishError(site: EnterpriseSiteConnection): string {
  const record = site.latest_record
  if (!record) {
    return ''
  }
  const status = String(record.status ?? '').trim()
  if (status !== 'failed' && status !== 'publish_failed') {
    return ''
  }
  return formatStoredErrorMessage(record.error_message)
}

function cmsTypeLabel(cmsType: string): string {
  switch (String(cmsType ?? '').trim()) {
    case 'pbootcms':
      return 'PBootCMS'
    case 'wordpress':
      return 'WordPress'
    case 'dedecms':
      return 'DedeCMS'
    case 'phpcms':
      return 'PHPCMS'
    default:
      return cmsType || 'CMS'
  }
}

function enterpriseSiteStatusLabel(status: string): string {
  switch (String(status ?? '').trim()) {
    case 'connected':
      return '已连通'
    case 'error':
      return '连接异常'
    case 'disabled':
      return '已禁用'
    default:
      return '待校验'
  }
}

function enterpriseSiteStatusColor(status: string): string {
  switch (String(status ?? '').trim()) {
    case 'connected':
      return 'green'
    case 'error':
      return 'red'
    case 'disabled':
      return 'default'
    default:
      return 'gold'
  }
}

function enterpriseCategoryOptions(siteId: number) {
  return (enterpriseCategoriesBySiteId.value[siteId] ?? []).map((category) => ({
    label: enterpriseCategoryLabel(category),
    value: category.remote_id,
  }))
}

function enterpriseCategoryLabel(category: EnterpriseSiteCategory): string {
  const slug = category.slug?.trim()
  return slug ? `${category.name} / ${slug}` : category.name
}

function setEnterpriseCategories(siteId: number, categories: EnterpriseSiteCategory[]): void {
  enterpriseCategoriesBySiteId.value = {
    ...enterpriseCategoriesBySiteId.value,
    [siteId]: categories,
  }
  const current = enterpriseSiteCategoryIds.value[siteId]?.trim()
  if (!current && categories.length > 0) {
    enterpriseSiteCategoryIds.value = {
      ...enterpriseSiteCategoryIds.value,
      [siteId]: categories[0].remote_id,
    }
  }
}

async function loadEnterpriseCategories(siteId: number): Promise<void> {
  if (enterpriseCategoriesBySiteId.value[siteId]) {
    return
  }
  try {
    const categories = await enterpriseSitesApi.categories(siteId)
    setEnterpriseCategories(siteId, categories)
  } catch {
    enterpriseCategoriesBySiteId.value = {
      ...enterpriseCategoriesBySiteId.value,
      [siteId]: [],
    }
  }
}

function syncEnterpriseCategories(siteId: number): void {
  syncEnterpriseCategoriesMutation.mutate(siteId)
}

function handleCoverToggle(checked: boolean): void {
  if (coverRequired.value) {
    coverEnabled.value = true
    return
  }
  coverEnabled.value = checked
}

function handleCoverPicked(payload: {
  url: string
  fileName: string
  assetId?: number | null
}): void {
  coverAssetUrl.value = payload.url
  coverFileName.value = payload.fileName
  coverImageAssetId.value = payload.assetId ?? null
  coverEnabled.value = true
}

function handleRemoveCover(): void {
  coverAssetUrl.value = ''
  coverFileName.value = ''
  coverImageAssetId.value = null
}

async function handleModalOk(): Promise<void> {
  if (okDisabled.value) {
    return
  }
  if (complianceDisplayDecision(complianceResult.value) === 'needs_ack') {
    await publishWithAck()
    return
  }
  publishMutation.mutate()
}

async function runPublishAdmissionCheck(): Promise<void> {
  const runId = ++publishAdmissionRunId
  if (!props.open) {
    return
  }
  const status = complianceStatusQuery.data.value
  if (!status || status.enabled !== true || status.enforcement_mode === 'disabled') {
    publishAdmissionResult.value = null
    publishAdmissionError.value = null
    publishAdmissionLoading.value = false
    return
  }
  const article = detailQuery.data.value
  const articleVersionId = article?.current_version_id
  if (!article || !props.articleId || !articleVersionId) {
    publishAdmissionResult.value = null
    publishAdmissionError.value = null
    publishAdmissionLoading.value =
      detailQuery.isPending.value ||
      detailQuery.isFetching.value ||
      complianceStatusQuery.isFetching.value
    return
  }

  publishAdmissionLoading.value = true
  publishAdmissionError.value = null
  try {
    const result = await complianceApi.checkArticle(article.id, {
      article_version_id: articleVersionId,
      title: article.title,
      plaintext: article.markdown_content ?? '',
      target_platforms: [],
      trigger_source: 'publish_gate',
    })
    if (runId === publishAdmissionRunId && props.open) {
      publishAdmissionResult.value = result
    }
  } catch (error) {
    if (runId === publishAdmissionRunId && props.open) {
      publishAdmissionError.value = formatError(error)
      publishAdmissionResult.value = null
    }
  } finally {
    if (runId === publishAdmissionRunId && props.open) {
      publishAdmissionLoading.value = false
    }
  }
}

async function publishWithAck(): Promise<void> {
  const result = complianceResult.value
  if (!result || complianceDisplayDecision(result) !== 'needs_ack' || !result.record_id) {
    publishMutation.mutate()
    return
  }
  ackInProgress.value = true
  try {
    const ack = await complianceApi.createAck(result.record_id, {
      acknowledged_violation_ids: complianceAcknowledgedViolationIds.value,
    })
    const publishResult = await runPublishFlow(ack.id)
    await handlePublishSuccess(publishResult)
  } catch (error) {
    handlePublishError(error)
  } finally {
    ackInProgress.value = false
  }
}

function parseComplianceResultFromError(error: unknown): ComplianceCheckResult | null {
  if (!(error instanceof ApiClientError) || typeof error.detail !== 'string') {
    return null
  }
  try {
    const parsed = JSON.parse(error.detail) as Partial<ComplianceCheckResult>
    if (typeof parsed.record_id === 'number' && typeof parsed.decision === 'string') {
      return parsed as ComplianceCheckResult
    }
  } catch {
    return null
  }
  return null
}

function hasComplianceHits(result: ComplianceCheckResult | null | undefined): boolean {
  return Boolean(result && ((result.hit_count ?? 0) > 0 || (result.violations?.length ?? 0) > 0))
}

function complianceDisplayDecision(
  result: ComplianceCheckResult | null | undefined,
): GateDecisionLiteral | undefined {
  if (!result) {
    return undefined
  }
  if (result.decision === 'pass' && (result.passed === false || hasComplianceHits(result))) {
    if (result.enforcement_mode === 'mandatory') {
      return 'block'
    }
    return 'needs_ack'
  }
  return result.decision
}

function decisionAlertType(result: ComplianceCheckResult | null | undefined) {
  const decision = complianceDisplayDecision(result)
  if (decision === 'block') {
    return 'error'
  }
  if (decision === 'needs_ack') {
    return 'warning'
  }
  return 'info'
}
</script>

<template>
  <a-modal
    :open="publishModalVisible"
    title="发布"
    :width="980"
    :confirm-loading="publishBusy"
    :ok-text="okText"
    :ok-button-props="{ disabled: okDisabled }"
    :cancel-text="t('common.cancel')"
    @ok="handleModalOk"
    @cancel="emit('update:open', false)"
  >
    <div class="publish-modal">
      <section class="publish-modal__hero">
        <div>
          <span class="publish-modal__eyebrow">发布标题</span>
          <h3>{{ modalTitle }}</h3>
          <p>
            {{
              activeTargetTab === 'enterprise_sites'
                ? '企业自建站点由服务端直接调用 CMS 插件发布，不占用桌面端账号。'
                : '在线账号会立即消费，离线账号会自动排队；数据库保留任务真相，RabbitMQ 负责唤醒在线客户端。'
            }}
          </p>
        </div>

        <div class="publish-modal__hero-metrics">
          <div class="publish-modal__metric">
            <strong>{{ selectedTargetCount }}</strong>
            <span>{{ selectedTargetLabel }}</span>
          </div>
          <div class="publish-modal__metric">
            <strong>{{ selectedPublishNowCount }}</strong>
            <span>{{ publishNowLabel }}</span>
          </div>
          <div class="publish-modal__metric">
            <strong>{{ selectedQueueCount }}</strong>
            <span>{{ queueLabel }}</span>
          </div>
        </div>
      </section>

      <section class="publish-modal__section">
        <div class="publish-modal__section-header">
          <div class="publish-modal__title-row">
            <h3>
              <span class="required-star">*</span>
              发布目标
            </h3>
            <div
              v-if="
                accountSelectionLocked &&
                !accountsQuery.isPending.value &&
                !platformsQuery.isPending.value &&
                !enterpriseSitesQuery.isPending.value
              "
              class="publish-modal__account-lock publish-modal__account-lock--inline"
              :class="{
                'publish-modal__account-lock--error':
                  !accountSelectionLockSpinning &&
                  (mandatoryAdmissionBlocking || publishAdmissionError),
              }"
            >
              <a-spin v-if="accountSelectionLockSpinning" size="small" />
              <CloseCircleFilled
                v-else-if="mandatoryAdmissionBlocking || publishAdmissionError"
                class="publish-modal__account-lock-icon"
              />
              <InfoCircleFilled v-else class="publish-modal__account-lock-icon" />
              <span>{{ accountSelectionLockMessage }}</span>
            </div>
          </div>
          <p class="muted">
            选择普通媒体账号会进入桌面端发布队列；选择企业自建站点会直接调用 CMS 插件发文。
            <span
              v-if="activeTargetTab === 'media_accounts' && publishedAccountCount > 0"
              class="publish-modal__published-note"
            >
              其中 {{ publishedAccountCount }} 个因已成功发布禁用。
            </span>
            <span
              v-else-if="activeTargetTab === 'enterprise_sites' && publishedEnterpriseSiteCount > 0"
              class="publish-modal__published-note"
            >
              其中 {{ publishedEnterpriseSiteCount }} 个站点因已成功发布禁用。
            </span>
          </p>
        </div>

        <a-tabs v-model:activeKey="activeTargetTab" class="publish-modal__target-tabs">
          <a-tab-pane key="media_accounts" tab="平台账号">
            <div
              v-if="
                accountsQuery.isPending.value ||
                platformsQuery.isPending.value ||
                publishRecordsQuery.isPending.value
              "
              class="publish-modal__loading"
            >
              <a-skeleton active :paragraph="{ rows: 5 }" />
            </div>

            <template v-else>
              <div v-if="accountCards.length" class="publish-modal__list">
                <button
                  v-for="account in accountCards"
                  :key="account.id"
                  type="button"
                  class="publish-modal__card"
                  :class="{
                    'publish-modal__card--active': isSelected(account.id),
                    'publish-modal__card--disabled': !account.selectable || accountSelectionLocked,
                  }"
                  :aria-disabled="!account.selectable || accountSelectionLocked"
                  @click="toggleAccount(account.id, account.selectable)"
                >
                  <div class="publish-modal__card-header">
                    <div class="publish-modal__card-identity">
                      <span
                        class="publish-modal__avatar"
                        :style="{ background: account.platformAccent }"
                      >
                        <img
                          v-if="account.avatarUrl"
                          :src="account.avatarUrl"
                          :alt="account.displayName"
                          referrerpolicy="no-referrer"
                        />
                        <span v-else>{{ accountInitial(account) }}</span>
                      </span>
                      <div class="publish-modal__identity-copy">
                        <div class="publish-modal__account-name-row">
                          <a-tooltip :title="account.displayName" placement="top">
                            <strong>{{ account.displayName }}</strong>
                          </a-tooltip>
                          <a-tooltip :title="account.statusText" placement="top">
                            <span
                              class="publish-modal__state-pill publish-modal__state-pill--compact"
                              :class="
                                isAlreadyPublished(account)
                                  ? 'publish-modal__state-pill--published'
                                  : `publish-modal__state-pill--${account.publishState}`
                              "
                            >
                              {{
                                isAlreadyPublished(account)
                                  ? '已发布'
                                  : publishStateLabel(account.publishState)
                              }}
                            </span>
                          </a-tooltip>
                        </div>
                        <a-tooltip
                          :title="`${account.platformName} · ${account.platformUid}`"
                          placement="top"
                        >
                          <span class="publish-modal__platform-line">
                            <img
                              v-if="account.platformLogoUrl"
                              class="publish-modal__platform-logo"
                              :src="account.platformLogoUrl"
                              :alt="account.platformName"
                              referrerpolicy="no-referrer"
                            />
                            <span
                              v-else
                              class="publish-modal__platform-logo publish-modal__platform-logo--fallback"
                              :style="{ background: account.platformAccent }"
                            >
                              {{ account.platformShortName }}
                            </span>
                            <span class="publish-modal__platform-text">
                              {{ account.platformName }} · {{ account.platformUid }}
                            </span>
                          </span>
                        </a-tooltip>
                      </div>
                    </div>
                    <span class="publish-modal__check">
                      <span v-if="isSelected(account.id)" class="publish-modal__check-inner"></span>
                    </span>
                  </div>

                  <div class="publish-modal__card-footer">
                    <div class="publish-modal__tags">
                      <span
                        class="status-badge"
                        :class="
                          account.health === 'live'
                            ? 'status-badge--success'
                            : account.health === 'risk'
                              ? 'status-badge--warning'
                              : 'status-badge--error'
                        "
                      >
                        <span class="status-badge__dot"></span>
                        {{ healthLabel(account.health) }}
                      </span>

                      <a-tooltip :title="clientStatusTooltip(account)" placement="top">
                        <span
                          class="status-badge publish-modal__client-status-tag"
                          :class="
                            account.clientOnline
                              ? 'status-badge--success'
                              : account.clientId
                                ? 'status-badge--neutral'
                                : 'status-badge--error'
                          "
                        >
                          <span class="status-badge__dot"></span>
                          {{ clientStatusLabel(account) }}
                        </span>
                      </a-tooltip>

                      <span
                        v-if="isAlreadyPublished(account)"
                        class="status-badge status-badge--published"
                      >
                        <span class="status-badge__dot"></span>
                        已发布
                      </span>
                      <span
                        v-else-if="activePublishRecordLabel(account)"
                        class="status-badge status-badge--processing"
                      >
                        <span class="status-badge__dot"></span>
                        {{ activePublishRecordLabel(account) }}
                      </span>
                    </div>
                  </div>
                </button>
              </div>

              <a-empty v-else description="当前还没有可用的桌面媒体账号，请先在桌面端绑定。" />
            </template>
          </a-tab-pane>

          <a-tab-pane key="enterprise_sites" tab="企业自建站点">
            <div
              v-if="enterpriseSitesQuery.isPending.value || publishRecordsQuery.isPending.value"
              class="publish-modal__loading"
            >
              <a-skeleton active :paragraph="{ rows: 5 }" />
            </div>

            <template v-else>
              <div class="publish-modal__enterprise-toolbar">
                <span>发布方式</span>
                <a-segmented
                  v-model:value="enterprisePublishType"
                  :options="[
                    { label: '直接发布', value: 'publish' },
                    { label: '保存草稿', value: 'draft' },
                  ]"
                />
              </div>

              <div v-if="enterpriseSiteCards.length" class="publish-modal__list">
                <article
                  v-for="site in enterpriseSiteCards"
                  :key="site.id"
                  class="publish-modal__card publish-modal__card--enterprise"
                  :class="{
                    'publish-modal__card--active': isEnterpriseSiteSelected(site.id),
                    'publish-modal__card--disabled': !site.selectable || accountSelectionLocked,
                  }"
                >
                  <button
                    type="button"
                    class="publish-modal__enterprise-card-main"
                    :aria-disabled="!site.selectable || accountSelectionLocked"
                    @click="toggleEnterpriseSite(site)"
                  >
                    <div class="publish-modal__card-header">
                      <div class="publish-modal__card-identity">
                        <span class="publish-modal__avatar publish-modal__avatar--site">
                          {{ site.name.slice(0, 1).toUpperCase() }}
                        </span>
                        <div class="publish-modal__identity-copy">
                          <div class="publish-modal__account-name-row">
                            <a-tooltip :title="site.name" placement="top">
                              <strong>{{ site.name }}</strong>
                            </a-tooltip>
                            <a-tooltip
                              :title="site.disabledReason || site.statusText"
                              placement="top"
                            >
                              <span
                                class="publish-modal__state-pill publish-modal__state-pill--compact"
                                :class="
                                  successfulEnterpriseSiteIds.has(site.id)
                                    ? 'publish-modal__state-pill--published'
                                    : site.hasPublishFailure
                                      ? 'publish-modal__state-pill--queued'
                                      : site.status === 'connected'
                                        ? 'publish-modal__state-pill--immediate'
                                        : site.status === 'error'
                                          ? 'publish-modal__state-pill--unavailable'
                                          : 'publish-modal__state-pill--queued'
                                "
                              >
                                {{ site.statusText }}
                              </span>
                            </a-tooltip>
                          </div>
                          <a-tooltip :title="site.siteUrl" placement="top">
                            <span class="publish-modal__platform-line">
                              <span
                                class="publish-modal__platform-logo publish-modal__platform-logo--fallback publish-modal__platform-logo--cms"
                              >
                                CMS
                              </span>
                              <span class="publish-modal__platform-text">
                                {{ site.cmsType }} · {{ site.siteUrl }}
                              </span>
                            </span>
                          </a-tooltip>
                        </div>
                      </div>
                      <span class="publish-modal__check">
                        <span
                          v-if="isEnterpriseSiteSelected(site.id)"
                          class="publish-modal__check-inner"
                        ></span>
                      </span>
                    </div>

                    <div class="publish-modal__card-footer">
                      <div class="publish-modal__tags">
                        <span
                          class="status-badge"
                          :class="
                            site.status === 'connected'
                              ? 'status-badge--success'
                              : 'status-badge--error'
                          "
                        >
                          <span class="status-badge__dot"></span>
                          {{ site.status === 'connected' ? '已连通' : '连通失败' }}
                        </span>

                        <span class="status-badge status-badge--info">
                          {{ site.categoryCount }} 个栏目
                        </span>

                        <span
                          v-if="successfulEnterpriseSiteIds.has(site.id)"
                          class="status-badge status-badge--published"
                        >
                          <span class="status-badge__dot"></span>
                          已发布
                        </span>
                        <span
                          v-else-if="activeEnterpriseRecordStatusBySiteId.has(site.id)"
                          class="status-badge status-badge--processing"
                        >
                          <span class="status-badge__dot"></span>
                          {{
                            activeEnterpriseRecordLabel(
                              activeEnterpriseRecordStatusBySiteId.get(site.id) ?? '',
                            )
                          }}
                        </span>
                      </div>
                    </div>
                  </button>

                  <div
                    v-if="isEnterpriseSiteSelected(site.id)"
                    class="publish-modal__enterprise-config"
                    @click.stop
                  >
                    <div class="publish-modal__config-title">
                      <svg
                        class="publish-modal__config-icon"
                        viewBox="0 0 24 24"
                        width="14"
                        height="14"
                        fill="none"
                        stroke="currentColor"
                        stroke-width="2"
                        stroke-linecap="round"
                        stroke-linejoin="round"
                      >
                        <rect x="3" y="3" width="7" height="9" />
                        <rect x="14" y="3" width="7" height="5" />
                        <rect x="14" y="12" width="7" height="9" />
                        <rect x="3" y="16" width="7" height="5" />
                      </svg>
                      <span>配置发布栏目</span>
                    </div>
                    <div class="publish-modal__config-body">
                      <a-select
                        v-model:value="enterpriseSiteCategoryIds[site.id]"
                        :options="enterpriseCategoryOptions(site.id)"
                        :loading="syncEnterpriseCategoriesMutation.isPending.value"
                        :get-popup-container="enterpriseSelectPopupContainer"
                        popup-class-name="publish-modal__enterprise-select-dropdown"
                        class="publish-modal__enterprise-select"
                        placeholder="选择栏目"
                        @focus="loadEnterpriseCategories(site.id)"
                        @click.stop
                      />
                      <a-button
                        class="publish-modal__sync-btn"
                        :loading="syncEnterpriseCategoriesMutation.isPending.value"
                        @click="syncEnterpriseCategories(site.id)"
                      >
                        <template #icon><SyncOutlined /></template>
                        同步栏目
                      </a-button>
                    </div>
                  </div>

                  <p v-if="site.disabledReason" class="publish-modal__site-warning">
                    {{ site.disabledReason }}
                  </p>
                </article>
              </div>

              <a-empty
                v-else
                description="当前还没有企业自建站点，请先到媒体管理添加 PBootCMS 站点。"
              />
            </template>
          </a-tab-pane>
        </a-tabs>
      </section>

      <section class="publish-modal__section">
        <div class="publish-modal__section-header publish-modal__section-header--inline">
          <h3>封面图</h3>
          <a-switch
            :checked="effectiveCoverEnabled"
            :disabled="coverRequired"
            @change="handleCoverToggle"
          />
        </div>
        <p class="muted">
          {{
            coverRequired
              ? t('media.publish.messages.coverRequired')
              : '默认关闭。需要时可在这里单独指定发布封面，并先写回文章。'
          }}
        </p>

        <div v-if="effectiveCoverEnabled" class="publish-modal__cover-body">
          <div class="publish-modal__cover-preview-wrap">
            <button
              type="button"
              class="publish-modal__cover-preview"
              @click="coverPickerOpen = true"
            >
              <template v-if="coverAssetUrl">
                <img :src="coverAssetUrl" alt="cover preview" />
              </template>
              <template v-else>
                <span class="publish-modal__cover-plus">+</span>
                <span>{{ t('media.publish.coverUpload') }}</span>
              </template>
            </button>

            <button
              v-if="coverAssetUrl"
              type="button"
              class="publish-modal__cover-remove"
              :aria-label="t('article.editor.coverRemove')"
              :title="t('article.editor.coverRemove')"
              @click.stop="handleRemoveCover"
            >
              <MinusCircleFilled />
            </button>
          </div>
        </div>
      </section>
    </div>
  </a-modal>

  <CoverPickerModal
    v-model:open="coverPickerOpen"
    :article-id="detailQuery.data.value?.id ?? null"
    :platform-ids="selectedPlatformIds"
    :current-url="coverAssetUrl"
    :current-file-name="coverFileName"
    :current-asset-id="coverImageAssetId"
    @confirmed="handleCoverPicked"
  />
</template>

<style scoped>
.publish-modal {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.publish-modal__hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 24px;
  padding: 24px;
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  background: #ffffff;
  box-shadow:
    0 1px 3px 0 rgba(0, 0, 0, 0.02),
    0 4px 8px -2px rgba(0, 0, 0, 0.02);
}

.publish-modal__eyebrow {
  display: inline-block;
  margin-bottom: 12px;
  padding: 4px 10px;
  font-size: 12px;
  font-weight: 600;
  color: #4f46e5;
  background: #e0e7ff;
  border-radius: 6px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.publish-modal__hero h3 {
  margin: 0;
  color: #111827;
  font-size: 18px;
  font-weight: 600;
  line-height: 1.5;
}

.publish-modal__hero p {
  margin: 8px 0 0;
  color: #6b7280;
  font-size: 14px;
  line-height: 1.6;
}

.publish-modal__hero-metrics {
  display: grid;
  grid-template-columns: repeat(3, minmax(96px, 1fr));
  gap: 12px;
}

.publish-modal__metric {
  padding: 16px;
  border-radius: 8px;
  background: #f9fafb;
  border: 1px solid #f3f4f6;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.publish-modal__metric strong {
  display: block;
  color: #111827;
  font-size: 24px;
  font-weight: 600;
  line-height: 1;
}

.publish-modal__metric span {
  display: block;
  margin-top: 6px;
  color: #6b7280;
  font-size: 13px;
  font-weight: 500;
}

.publish-modal__section {
  display: flex;
  flex-direction: column;
}

.publish-modal__compliance {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 16px 20px;
  border-radius: 12px;
  border: 1px solid transparent;
  transition: all 0.3s ease;
}

.publish-modal__compliance-icon {
  font-size: 24px;
  line-height: 1;
}

.publish-modal__compliance-content {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1;
}

.publish-modal__compliance-title {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  line-height: 24px;
}

.publish-modal__compliance-desc {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  opacity: 0.9;
}

.publish-modal__compliance--error {
  background: linear-gradient(145deg, #fef2f2, #fff5f5);
  border-color: #fee2e2;
  box-shadow: 0 4px 12px rgba(239, 68, 68, 0.06);
}
.publish-modal__compliance--error .publish-modal__compliance-icon {
  color: #ef4444;
}
.publish-modal__compliance--error .publish-modal__compliance-title {
  color: #991b1b;
}
.publish-modal__compliance--error .publish-modal__compliance-desc {
  color: #991b1b;
}

.publish-modal__compliance--warning {
  background: linear-gradient(145deg, #fffbeb, #fffde7);
  border-color: #fef3c7;
  box-shadow: 0 4px 12px rgba(245, 158, 11, 0.06);
}
.publish-modal__compliance--warning .publish-modal__compliance-icon {
  color: #f59e0b;
}
.publish-modal__compliance--warning .publish-modal__compliance-title {
  color: #92400e;
}
.publish-modal__compliance--warning .publish-modal__compliance-desc {
  color: #92400e;
}

.publish-modal__compliance--info {
  background: linear-gradient(145deg, #eff6ff, #f5f8ff);
  border-color: #dbeafe;
  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.06);
}
.publish-modal__compliance--info .publish-modal__compliance-icon {
  color: #3b82f6;
}
.publish-modal__compliance--info .publish-modal__compliance-title {
  color: #1e40af;
}
.publish-modal__compliance--info .publish-modal__compliance-desc {
  color: #1e40af;
}

.publish-modal__compliance--success {
  background: linear-gradient(145deg, #f0fdf4, #f5fdf7);
  border-color: #dcfce7;
  box-shadow: 0 4px 12px rgba(34, 197, 94, 0.06);
}
.publish-modal__compliance--success .publish-modal__compliance-icon {
  color: #22c55e;
}
.publish-modal__compliance--success .publish-modal__compliance-title {
  color: #166534;
}
.publish-modal__compliance--success .publish-modal__compliance-desc {
  color: #166534;
}

.publish-modal__section-header {
  margin-bottom: 12px;
}

.publish-modal__section-header--inline {
  display: flex;
  align-items: center;
  gap: 12px;
}

.publish-modal__section-header h3 {
  margin: 0;
  color: #1f2937;
  font-size: 16px;
  font-weight: 600;
}

.publish-modal__title-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 6px;
}

.publish-modal__section-header--inline h3 {
  margin: 0;
}

.required-star {
  color: #ef4444;
  margin-right: 4px;
}

.muted {
  margin: 0;
  color: #6b7280;
  font-size: 14px;
  line-height: 1.6;
}

.publish-modal__loading {
  padding: 12px 0;
}

.publish-modal__target-tabs {
  margin-top: 14px;
}

.publish-modal__target-tabs :deep(.ant-tabs-nav) {
  margin-bottom: 16px;
}

.publish-modal__target-tabs :deep(.ant-tabs-tab) {
  padding: 8px 0;
  font-weight: 600;
}

.publish-modal__enterprise-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
  padding: 10px 12px;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  background: #f9fafb;
}

.publish-modal__enterprise-toolbar span {
  color: #374151;
  font-size: 13px;
  font-weight: 600;
}

.publish-modal__account-lock {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  width: fit-content;
  max-width: 100%;
  padding: 10px 14px;
  border: 1px solid #ffedd5;
  border-radius: 8px;
  background: #fff7ed;
  color: #9a3412;
  font-size: 13px;
  line-height: 1.5;
}

.publish-modal__account-lock--inline {
  padding: 6px 12px;
  border-radius: 6px;
}

.publish-modal__account-lock--inline span {
  white-space: pre-wrap;
  line-height: 1.5;
}

.publish-modal__account-lock--error {
  background: #fef2f2;
  border-color: #fee2e2;
  color: #991b1b;
}

.publish-modal__account-lock--error .publish-modal__account-lock-icon {
  color: #ef4444;
}

.publish-modal__account-lock-icon {
  color: #ea580c;
  font-size: 15px;
}

.publish-modal__list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
  gap: 16px;
  max-height: 400px;
  overflow-y: auto;
  padding-right: 6px;
  padding-bottom: 4px;
}

/* Custom scrollbar for list */
.publish-modal__list::-webkit-scrollbar {
  width: 6px;
}
.publish-modal__list::-webkit-scrollbar-thumb {
  background: #d1d5db;
  border-radius: 999px;
}

.publish-modal__card {
  display: flex;
  flex-direction: column;
  gap: 14px;
  width: 100%;
  padding: 16px;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  background: #ffffff;
  text-align: left;
  cursor: pointer;
  transition: all 0.2s ease;
  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.02);
}

.publish-modal__card:hover:not(.publish-modal__card--disabled) {
  border-color: #d1d5db;
  box-shadow:
    0 4px 6px -1px rgba(0, 0, 0, 0.08),
    0 2px 4px -1px rgba(0, 0, 0, 0.04);
  transform: translateY(-2px);
}

.publish-modal__card--active {
  border-color: #3b82f6;
  background: #eff6ff;
  box-shadow: 0 0 0 1px #3b82f6;
}

.publish-modal__card--active:hover:not(.publish-modal__card--disabled) {
  box-shadow:
    0 4px 6px -1px rgba(59, 130, 246, 0.15),
    0 0 0 1px #3b82f6;
}

.publish-modal__card--disabled {
  cursor: not-allowed;
  opacity: 0.6;
  background: #f9fafb;
  box-shadow: none;
}

.publish-modal__card--enterprise {
  cursor: default;
  padding: 0;
  gap: 0;
  overflow: visible;
}

.publish-modal__card--enterprise:hover:not(.publish-modal__card--disabled):not(
    .publish-modal__card--active
  ) {
  transform: translateY(-2px);
  border-color: #d1d5db;
  box-shadow:
    0 4px 6px -1px rgba(0, 0, 0, 0.08),
    0 2px 4px -1px rgba(0, 0, 0, 0.04);
}

.publish-modal__card--enterprise.publish-modal__card--active:hover:not(
    .publish-modal__card--disabled
  ) {
  transform: none;
}

.publish-modal__enterprise-card-main {
  display: flex;
  flex-direction: column;
  gap: 14px;
  width: 100%;
  padding: 16px;
  border: 0;
  background: transparent;
  text-align: left;
  cursor: pointer;
}

.publish-modal__card--disabled .publish-modal__enterprise-card-main {
  cursor: not-allowed;
}

.publish-modal__enterprise-config {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0 16px 16px;
  padding: 12px 14px;
  border-radius: 8px;
  background: #f8fafc;
  border: 1px solid #e2e8f0;
  z-index: 2;
  transition: all 0.2s ease;
}

.publish-modal__card--active .publish-modal__enterprise-config {
  background: #ffffff;
  border-color: #bfdbfe;
  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.02);
}

.publish-modal__config-title {
  display: flex;
  align-items: center;
  gap: 6px;
  color: #475569;
  font-size: 12px;
  font-weight: 600;
}

.publish-modal__config-icon {
  color: #64748b;
  flex-shrink: 0;
}

.publish-modal__config-body {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
}

.publish-modal__enterprise-select {
  flex: 1;
  min-width: 0;
}

.publish-modal__sync-btn {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 32px;
  border-color: #d9d9d9;
  font-size: 13px;
  font-weight: 500;
  transition: all 0.2s ease;
}

.publish-modal__sync-btn:hover {
  color: #3b82f6;
  border-color: #3b82f6;
}

:global(.publish-modal__enterprise-select-dropdown) {
  z-index: 3200;
}

.publish-modal__site-warning {
  margin: -4px 16px 14px;
  color: #b45309;
  font-size: 12px;
  line-height: 1.5;
}

.publish-modal__card-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.publish-modal__card-identity {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

.publish-modal__card-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 12px;
  border-top: 1px dashed #e2e8f0;
  gap: 8px;
  transition: border-color 0.2s;
}

/* Custom premium status badges */
.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 500;
  line-height: 14px;
  background: #f1f5f9;
  color: #475569;
  border: 1px solid #e2e8f0;
  transition: all 0.2s ease;
}

.status-badge__dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #94a3b8;
  display: inline-block;
  flex-shrink: 0;
}

/* Success */
.status-badge--success {
  background: #f0fdf4;
  color: #166534;
  border-color: #bbf7d0;
}
.status-badge--success .status-badge__dot {
  background: #22c55e;
}

/* Error */
.status-badge--error {
  background: #fef2f2;
  color: #991b1b;
  border-color: #fee2e2;
}
.status-badge--error .status-badge__dot {
  background: #ef4444;
}

/* Warning */
.status-badge--warning {
  background: #fffbeb;
  color: #92400e;
  border-color: #fde68a;
}
.status-badge--warning .status-badge__dot {
  background: #f59e0b;
}

/* Neutral */
.status-badge--neutral {
  background: #f8fafc;
  color: #475569;
  border-color: #e2e8f0;
}
.status-badge--neutral .status-badge__dot {
  background: #94a3b8;
}

/* Processing */
.status-badge--processing {
  background: #eff6ff;
  color: #1e40af;
  border-color: #bfdbfe;
}
.status-badge--processing .status-badge__dot {
  background: #3b82f6;
  animation: badgePulse 2s infinite ease-in-out;
}

/* Published */
.status-badge--published {
  background: #f0fdf4;
  color: #166534;
  border-color: #bbf7d0;
}
.status-badge--published .status-badge__dot {
  background: #10b981;
}

/* Info */
.status-badge--info {
  background: #f0fdfa;
  color: #0f766e;
  border-color: #ccfbf1;
}

@keyframes badgePulse {
  0%,
  100% {
    opacity: 1;
    transform: scale(1);
  }
  50% {
    opacity: 0.4;
    transform: scale(0.8);
  }
}

.publish-modal__card--active .publish-modal__card-footer {
  border-top-color: #bfdbfe;
}

.publish-modal__check {
  width: 18px;
  height: 18px;
  border: 1px solid #d1d5db;
  border-radius: 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: #fff;
  flex-shrink: 0;
  transition: all 0.2s;
  margin-top: 2px;
}

.publish-modal__card:hover:not(.publish-modal__card--disabled) .publish-modal__check {
  border-color: #3b82f6;
}

.publish-modal__card--active .publish-modal__check {
  background: #3b82f6;
  border-color: #3b82f6;
}

.publish-modal__check-inner {
  width: 6px;
  height: 10px;
  border-right: 2px solid #fff;
  border-bottom: 2px solid #fff;
  transform: rotate(45deg) translate(-1px, -2px);
  opacity: 0;
  transition: opacity 0.2s;
}

.publish-modal__card--active .publish-modal__check-inner {
  opacity: 1;
}

.publish-modal__avatar {
  width: 40px;
  height: 40px;
  border-radius: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 15px;
  font-weight: 600;
  overflow: hidden;
  flex-shrink: 0;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05);
}

.publish-modal__avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.publish-modal__avatar--site {
  background: linear-gradient(135deg, #0ea5e9, #0f766e);
  box-shadow: 0 2px 6px rgba(15, 118, 110, 0.15);
  transition: all 0.3s ease;
}

.publish-modal__card:hover:not(.publish-modal__card--disabled) .publish-modal__avatar--site {
  transform: scale(1.05);
  box-shadow: 0 4px 10px rgba(15, 118, 110, 0.25);
}

.publish-modal__identity-copy {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.publish-modal__account-name-row {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.publish-modal__identity-copy strong {
  min-width: 0;
  color: #111827;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.4;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.publish-modal__identity-copy span {
  margin-top: 2px;
  color: #6b7280;
  font-size: 13px;
  line-height: 1.5;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.publish-modal__platform-line {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.publish-modal__platform-logo {
  width: 14px;
  height: 14px;
  border-radius: 3px;
  object-fit: contain;
  flex-shrink: 0;
  background: #fff;
}

.publish-modal__platform-logo--fallback {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff !important;
  font-size: 9px !important;
  font-weight: 600;
  line-height: 1 !important;
  margin-top: 0;
}

.publish-modal__platform-logo--cms {
  background: #0f766e;
  font-size: 8px !important;
  width: auto;
  padding: 0 4px;
  height: 14px;
  line-height: 14px !important;
  border-radius: 3px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  color: #fff !important;
}

.publish-modal__platform-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.publish-modal__state-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 72px;
  height: 24px;
  padding: 0 10px;
  border-radius: 6px;
  font-size: 12px;
  font-weight: 600;
  flex-shrink: 0;
}

.publish-modal__state-pill--compact {
  min-width: 0;
  height: 22px;
  padding: 0 9px;
  font-size: 12px;
}

.publish-modal__state-pill--immediate {
  background: #ecfdf5;
  color: #059669;
  border: 1px solid #a7f3d0;
}

.publish-modal__state-pill--queued {
  background: #fffbeb;
  color: #d97706;
  border: 1px solid #fde68a;
}

.publish-modal__state-pill--unavailable {
  background: #fef2f2;
  color: #dc2626;
  border: 1px solid #fecaca;
}

.publish-modal__state-pill--published {
  background: #dbeafe;
  color: #1d4ed8;
  border: 1px solid #bfdbfe;
}

.publish-modal__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.publish-modal__tags .ant-tag {
  margin: 0;
  border-radius: 4px;
}

.publish-modal__client-status-tag {
  display: inline-block;
  max-width: 118px;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: top;
  white-space: nowrap;
}

.publish-modal__cover-body {
  display: flex;
  margin-top: 12px;
}

.publish-modal__cover-preview-wrap {
  position: relative;
  width: 176px;
  flex: 0 0 176px;
}

.publish-modal__cover-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 124px;
  padding: 0;
  border: 1px dashed #d1d5db;
  border-radius: 8px;
  background: #f9fafb;
  color: #6b7280;
  font-size: 13px;
  gap: 8px;
  cursor: pointer;
  overflow: hidden;
  transition: all 0.2s ease;
}

.publish-modal__cover-preview:hover {
  border-color: #6366f1;
  color: #6366f1;
  background: #f5f3ff;
}

.publish-modal__cover-preview img {
  display: block;
  width: 100%;
  height: 124px;
  object-fit: cover;
}

.publish-modal__cover-plus {
  font-size: 20px;
  line-height: 1;
}

.publish-modal__cover-remove {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 999px;
  background: rgba(17, 24, 39, 0.7);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.2s;
}

.publish-modal__cover-remove:hover {
  background: rgba(17, 24, 39, 0.9);
}

@media (max-width: 900px) {
  .publish-modal__hero {
    grid-template-columns: minmax(0, 1fr);
  }

  .publish-modal__hero-metrics {
    min-width: 0;
  }
}
</style>
