<script setup lang="ts">
defineOptions({ name: 'AdminContents' });

import { ref, computed, watch, onActivated } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import type { TableInstance } from 'element-plus';
import { useAdminLocale } from '../composables/useAdminLocale';
import { usePermissions } from '../composables/usePermissions';
import { AdminPerm } from '../constants/permissions';
import api from '../api';
import AdminTableEmpty from '../components/AdminTableEmpty.vue';
import ContentImageField from '../components/ContentImageField.vue';
import ContentRichEditor from '../components/ContentRichEditor.vue';
import { stripHtml, sanitizeAnnouncementHtml, isHtmlEmpty } from '../utils/html';
import {
  normalizeStartTimeForApi,
  normalizeStartTimeForPicker,
} from './match-form';

const { t, localeTag } = useAdminLocale();
const { hasPermission } = usePermissions();
const canManageContent = computed(() => hasPermission(AdminPerm.content));

type StoredContentType = 'BANNER' | 'NOTICE' | 'TICKER';
type AdminTab = 'BANNER' | 'ANNOUNCEMENT' | 'INBOX_NOTIFY';
type ContentStatus = 'DRAFT' | 'ACTIVE' | 'INACTIVE';

interface TranslationForm {
  locale: string;
  title: string;
  body: string;
  imageUrl: string;
}

interface ContentItem {
  id: string;
  contentType: StoredContentType;
  sortOrder: number;
  status: ContentStatus;
  linkType: string | null;
  linkTarget: string | null;
  startTime: string | null;
  endTime: string | null;
  previewTitle: string;
  previewImageUrl: string | null;
  playerVisible: boolean;
  playerHiddenReason: string | null;
  translations: TranslationForm[];
}

const ADMIN_TABS: AdminTab[] = ['BANNER', 'ANNOUNCEMENT', 'INBOX_NOTIFY'];
const LOCALES = ['zh-CN', 'en-US', 'ms-MY'] as const;

const activeType = ref<AdminTab>('BANNER');
const filterStatus = ref<ContentStatus | ''>('');
const loading = ref(false);
const saving = ref(false);
const items = ref<ContentItem[]>([]);
const total = ref(0);
const page = ref(1);
const pageSize = ref(10);
const tableRef = ref<TableInstance>();
const selectedRows = ref<ContentItem[]>([]);

const hasSelection = computed(() => selectedRows.value.length > 0);

const bannerDialogVisible = ref(false);
const announcementDialogVisible = ref(false);
const bannerEditorRef = ref<InstanceType<typeof ContentRichEditor> | null>(null);
const editingId = ref<string | null>(null);
const editingContentType = ref<StoredContentType>('TICKER');
const activeLocale = ref<string>('zh-CN');
const notifyInbox = ref(false);

interface InboxNotifySettings {
  inboxEnabled: boolean;
  deposit: boolean;
  banner: boolean;
  announcement: boolean;
}

interface MessageBroadcastItem {
  id: string;
  title: string;
  body: string;
  translations?: Record<string, { title: string; body: string }>;
  targetType: 'ALL' | 'USER';
  targetUserId: string | null;
  targetUsername: string | null;
  recipientCount: number;
  createdById: string | null;
  createdByUsername: string | null;
  createdAt: string;
}

interface BroadcastTranslationForm {
  locale: string;
  title: string;
  body: string;
}

const inboxNotifySettings = ref<InboxNotifySettings>({
  inboxEnabled: true,
  deposit: true,
  banner: true,
  announcement: true,
});
const inboxNotifySaving = ref(false);

const broadcastLoading = ref(false);
const broadcastSending = ref(false);
const broadcastDialogVisible = ref(false);
const broadcastActiveLocale = ref<string>('zh-CN');
const broadcastEditorRef = ref<InstanceType<typeof ContentRichEditor> | null>(null);
const broadcastItems = ref<MessageBroadcastItem[]>([]);
const broadcastTotal = ref(0);
const broadcastPage = ref(1);
const broadcastPageSize = ref(10);
const broadcastDetailVisible = ref(false);
const broadcastDetailRow = ref<MessageBroadcastItem | null>(null);
const broadcastDetailLocale = ref<string>('zh-CN');

function emptyBroadcastTranslations(): BroadcastTranslationForm[] {
  return LOCALES.map((locale) => ({
    locale,
    title: '',
    body: '',
  }));
}

const broadcastForm = ref({
  targetType: 'ALL' as 'ALL' | 'USER',
  targetUsername: '',
  translations: emptyBroadcastTranslations(),
});

const broadcastActiveTranslation = computed(
  () =>
    broadcastForm.value.translations.find((tr) => tr.locale === broadcastActiveLocale.value) ??
    broadcastForm.value.translations[0],
);

const form = ref({
  sortOrder: 0,
  status: 'DRAFT' as ContentStatus,
  linkType: '' as '' | 'ROUTE' | 'URL',
  linkTarget: '',
  startTime: '' as string,
  endTime: '' as string,
  translations: emptyTranslations(),
});

function emptyTranslations(): TranslationForm[] {
  return LOCALES.map((locale) => ({
    locale,
    title: '',
    body: '',
    imageUrl: '',
  }));
}

function localeLabel(code: string) {
  const key = `content.locale.${code}`;
  const label = t(key);
  return label === key ? code : label;
}

function statusLabel(status: string) {
  const key = `content.status.${status}`;
  const label = t(key);
  return label === key ? status : label;
}

function statusTagType(status: string) {
  if (status === 'ACTIVE') return 'success';
  if (status === 'DRAFT') return 'info';
  return 'warning';
}

function hiddenTip(reason: string | null) {
  if (!reason) return '';
  const key = `content.hidden_reason.${reason}`;
  const label = t(key);
  return label === key ? reason : label;
}

function formatTime(v: string | null) {
  if (!v) return '—';
  return new Date(v).toLocaleString(localeTag.value, {
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
  });
}

function previewText(row: ContentItem) {
  const raw = row.previewTitle || row.translations.find((tr) => tr.body)?.body || '';
  return stripHtml(raw) || '—';
}

const isBannerTab = computed(() => activeType.value === 'BANNER');
const isInboxNotifyTab = computed(() => activeType.value === 'INBOX_NOTIFY');

const activeTranslation = computed(
  () =>
    form.value.translations.find((tr) => tr.locale === activeLocale.value) ??
    form.value.translations[0],
);

const bannerDialogTitle = computed(() =>
  editingId.value ? t('content.dialog.edit_banner') : t('content.dialog.create_banner'),
);

const announcementDialogTitle = computed(() =>
  editingId.value ? t('content.dialog.edit_notice') : t('content.dialog.create_notice'),
);

async function loadInboxNotifySettings() {
  try {
    const { data } = await api.get('/admin/contents/inbox-notify-settings');
    inboxNotifySettings.value = {
      inboxEnabled: data.data?.inboxEnabled !== false,
      deposit: Boolean(data.data?.deposit),
      banner: data.data?.banner !== false,
      announcement: data.data?.announcement !== false,
    };
  } catch (e: unknown) {
    const err = e as { response?: { data?: { error?: string } } };
    ElMessage.error(err.response?.data?.error ?? t('msg.load_failed'));
  }
}

async function saveInboxNotifySettings() {
  if (!canManageContent.value) return;
  inboxNotifySaving.value = true;
  try {
    const { data } = await api.put('/admin/contents/inbox-notify-settings', {
      inboxEnabled: inboxNotifySettings.value.inboxEnabled,
      deposit: inboxNotifySettings.value.deposit,
      banner: inboxNotifySettings.value.banner,
      announcement: inboxNotifySettings.value.announcement,
    });
    inboxNotifySettings.value = {
      inboxEnabled: data.data?.inboxEnabled !== false,
      deposit: Boolean(data.data?.deposit),
      banner: data.data?.banner !== false,
      announcement: data.data?.announcement !== false,
    };
    ElMessage.success(t('msg.saved'));
  } catch (e: unknown) {
    const err = e as { response?: { data?: { error?: string } } };
    ElMessage.error(err.response?.data?.error ?? t('msg.save_failed'));
    await loadInboxNotifySettings();
  } finally {
    inboxNotifySaving.value = false;
  }
}

async function loadBroadcasts() {
  broadcastLoading.value = true;
  try {
    const { data } = await api.get('/admin/player-message-broadcasts', {
      params: { page: broadcastPage.value, pageSize: broadcastPageSize.value },
    });
    broadcastItems.value = data.data?.items ?? [];
    broadcastTotal.value = data.data?.total ?? 0;
  } catch (e: unknown) {
    const err = e as { response?: { data?: { error?: string } } };
    ElMessage.error(err.response?.data?.error ?? t('msg.load_failed'));
  } finally {
    broadcastLoading.value = false;
  }
}

function resetBroadcastForm() {
  broadcastForm.value = {
    targetType: 'ALL',
    targetUsername: '',
    translations: emptyBroadcastTranslations(),
  };
  broadcastActiveLocale.value = 'zh-CN';
}

function hasBroadcastContent() {
  return broadcastForm.value.translations.some(
    (tr) => tr.title.trim() || tr.body.trim(),
  );
}

function openBroadcastDialog() {
  resetBroadcastForm();
  broadcastDialogVisible.value = true;
}

function closeBroadcastDialog() {
  if (broadcastSending.value) return;
  broadcastDialogVisible.value = false;
}

async function sendBroadcast() {
  if (!canManageContent.value) return;

  const editor = broadcastEditorRef.value;
  if (editor) {
    broadcastActiveTranslation.value.body = editor.getHtml();
    for (const tr of broadcastForm.value.translations) {
      tr.body = await editor.uploadPendingImages(tr.body);
    }
  }

  if (!hasBroadcastContent()) {
    ElMessage.warning(t('content.inbox_broadcast.form_invalid'));
    return;
  }
  if (broadcastForm.value.targetType === 'USER' && !broadcastForm.value.targetUsername.trim()) {
    ElMessage.warning(t('content.inbox_broadcast.target_user_required'));
    return;
  }
  broadcastSending.value = true;
  try {
    const { data } = await api.post('/admin/player-message-broadcasts', {
      translations: broadcastForm.value.translations.map((tr) => ({
        locale: tr.locale,
        title: tr.title.trim() || undefined,
        body: tr.body.trim() || undefined,
      })),
      targetType: broadcastForm.value.targetType,
      targetUsername:
        broadcastForm.value.targetType === 'USER'
          ? broadcastForm.value.targetUsername.trim()
          : undefined,
    });
    ElMessage.success(
      t('content.inbox_broadcast.send_success', {
        n: data.data?.recipientCount ?? 0,
      }),
    );
    resetBroadcastForm();
    broadcastPage.value = 1;
    broadcastDialogVisible.value = false;
    await loadBroadcasts();
  } catch (e: unknown) {
    const err = e as { response?: { data?: { error?: string } } };
    ElMessage.error(err.response?.data?.error ?? t('msg.save_failed'));
  } finally {
    broadcastSending.value = false;
  }
}

function broadcastTargetLabel(row: MessageBroadcastItem) {
  if (row.targetType === 'ALL') return t('content.inbox_broadcast.target_all');
  return row.targetUsername || `#${row.targetUserId ?? ''}`;
}

function formatBroadcastTime(value: string) {
  if (!value) return '—';
  return new Date(value).toLocaleString(localeTag.value);
}

function openBroadcastDetail(row: MessageBroadcastItem) {
  broadcastDetailRow.value = row;
  const tr = row.translations ?? {};
  const firstWithContent =
    LOCALES.find((locale) => {
      const item = tr[locale];
      return item && (item.title?.trim() || !isHtmlEmpty(item.body));
    }) ?? 'zh-CN';
  broadcastDetailLocale.value = firstWithContent;
  broadcastDetailVisible.value = true;
}

function broadcastDetailTranslation(locale: string) {
  const row = broadcastDetailRow.value;
  if (!row) return { title: '', body: '' };
  return row.translations?.[locale] ?? { title: '', body: '' };
}

async function deleteBroadcast(row: MessageBroadcastItem) {
  if (!canManageContent.value) return;
  try {
    await ElMessageBox.confirm(
      t('content.inbox_broadcast.delete_confirm', { title: row.title }),
      t('common.confirm'),
      { type: 'warning' },
    );
  } catch {
    return;
  }
  broadcastLoading.value = true;
  try {
    await api.delete(`/admin/player-message-broadcasts/${row.id}`);
    ElMessage.success(t('msg.saved'));
    if (broadcastItems.value.length === 1 && broadcastPage.value > 1) {
      broadcastPage.value -= 1;
    }
    await loadBroadcasts();
  } catch (e: unknown) {
    const err = e as { response?: { data?: { error?: string } } };
    ElMessage.error(err.response?.data?.error ?? t('msg.save_failed'));
  } finally {
    broadcastLoading.value = false;
  }
}

function onBroadcastPageChange(page: number) {
  broadcastPage.value = page;
  void loadBroadcasts();
}

function onBroadcastSizeChange(size: number) {
  broadcastPageSize.value = size;
  broadcastPage.value = 1;
  void loadBroadcasts();
}

async function load() {
  loading.value = true;
  try {
    const { data } = await api.get('/admin/contents', {
      params: {
        type: activeType.value,
        status: filterStatus.value || undefined,
        page: page.value,
        pageSize: pageSize.value,
      },
    });
    items.value = data.data.items ?? [];
    total.value = data.data.total ?? 0;
    selectedRows.value = [];
    tableRef.value?.clearSelection();
  } catch (e: unknown) {
    const err = e as { response?: { data?: { error?: string } } };
    ElMessage.error(err.response?.data?.error ?? t('msg.load_failed'));
  } finally {
    loading.value = false;
  }
}

function onPageChange(p: number) {
  page.value = p;
  load();
}

function onSizeChange(size: number) {
  pageSize.value = size;
  page.value = 1;
  load();
}

watch([activeType, filterStatus], () => {
  page.value = 1;
  selectedRows.value = [];
  tableRef.value?.clearSelection();
  if (activeType.value === 'INBOX_NOTIFY') {
    void loadInboxNotifySettings();
    void loadBroadcasts();
    return;
  }
  void load();
});

onActivated(() => {
  if (activeType.value === 'INBOX_NOTIFY') {
    void loadInboxNotifySettings();
    if (broadcastItems.value.length > 0) void loadBroadcasts();
    return;
  }
  if (items.value.length > 0) void load();
});

function onSelectionChange(rows: ContentItem[]) {
  selectedRows.value = rows;
}

async function runBatch(
  action: (row: ContentItem) => Promise<void>,
  confirmKey?: string,
) {
  const rows = [...selectedRows.value];
  if (!rows.length) return;

  if (confirmKey) {
    try {
      await ElMessageBox.confirm(t(confirmKey, { n: rows.length }), { type: 'warning' });
    } catch {
      return;
    }
  }

  saving.value = true;
  let ok = 0;
  let fail = 0;
  try {
    for (const row of rows) {
      try {
        await action(row);
        ok += 1;
      } catch {
        fail += 1;
      }
    }
    if (fail === 0) {
      ElMessage.success(t('content.batch.all_ok', { n: ok }));
    } else {
      ElMessage.warning(t('content.batch.partial', { ok, fail }));
    }
    await load();
  } finally {
    saving.value = false;
  }
}

function batchEnable() {
  void runBatch(
    (row) => api.patch(`/admin/contents/${row.id}/status`, { status: 'ACTIVE' }),
    'content.confirm_batch_enable',
  );
}

function batchDisable() {
  void runBatch(
    (row) => api.patch(`/admin/contents/${row.id}/status`, { status: 'INACTIVE' }),
    'content.confirm_batch_disable',
  );
}

function batchDelete() {
  void runBatch(
    (row) => api.delete(`/admin/contents/${row.id}`),
    'content.confirm_batch_delete',
  );
}

function resetForm() {
  activeLocale.value = 'zh-CN';
  notifyInbox.value = false;
  form.value = {
    sortOrder: 0,
    status: 'DRAFT',
    linkType: '',
    linkTarget: '',
    startTime: '',
    endTime: '',
    translations: emptyTranslations(),
  };
}

function loadRowIntoForm(row: ContentItem, plainBody = false) {
  const byLocale = new Map(row.translations.map((tr) => [tr.locale, tr]));
  form.value = {
    sortOrder: row.sortOrder,
    status: row.status,
    linkType: (row.linkType as '' | 'ROUTE' | 'URL') || '',
    linkTarget: row.linkTarget ?? '',
    startTime: normalizeStartTimeForPicker(row.startTime ?? undefined),
    endTime: normalizeStartTimeForPicker(row.endTime ?? undefined),
    translations: LOCALES.map((locale) => {
      const tr = byLocale.get(locale);
      const rawBody = tr?.body ?? '';
      return {
        locale,
        title: tr?.title ?? '',
        body: plainBody ? stripHtml(rawBody) : rawBody,
        imageUrl: tr?.imageUrl ?? '',
      };
    }),
  };
}

function openCreate() {
  editingId.value = null;
  resetForm();
  if (isBannerTab.value) {
    editingContentType.value = 'BANNER';
    bannerDialogVisible.value = true;
  } else {
    editingContentType.value = 'TICKER';
    announcementDialogVisible.value = true;
  }
}

function openEdit(row: ContentItem) {
  editingId.value = row.id;
  editingContentType.value = row.contentType;
  activeLocale.value = 'zh-CN';
  if (row.contentType === 'BANNER') {
    loadRowIntoForm(row, false);
    bannerDialogVisible.value = true;
  } else {
    loadRowIntoForm(row, true);
    announcementDialogVisible.value = true;
  }
}

function buildScheduleFields() {
  return {
    sortOrder: form.value.sortOrder,
    status: form.value.status,
    startTime: form.value.startTime ? normalizeStartTimeForApi(form.value.startTime) : null,
    endTime: form.value.endTime ? normalizeStartTimeForApi(form.value.endTime) : null,
  };
}

function buildBannerPayload() {
  const payload = {
    contentType: 'BANNER' as const,
    ...buildScheduleFields(),
    linkType: form.value.linkType ? form.value.linkType : null,
    linkTarget: form.value.linkType ? form.value.linkTarget.trim() : null,
    translations: form.value.translations.map((tr) => ({
      locale: tr.locale,
      title: tr.title.trim() || undefined,
      body: tr.body.trim() || undefined,
      imageUrl: tr.imageUrl.trim() || undefined,
    })),
  };
  if (!editingId.value) {
    return { ...payload, notifyInbox: notifyInbox.value };
  }
  return payload;
}

function buildAnnouncementPayload() {
  const contentType: StoredContentType = editingId.value ? editingContentType.value : 'TICKER';
  const payload = {
    contentType,
    ...buildScheduleFields(),
    linkType: null,
    linkTarget: null,
    translations: form.value.translations.map((tr) => ({
      locale: tr.locale,
      title: tr.title.trim() || undefined,
      body: tr.body.trim() || undefined,
    })),
  };
  if (!editingId.value) {
    return { ...payload, notifyInbox: notifyInbox.value };
  }
  return payload;
}

async function submitBannerForm() {
  saving.value = true;
  try {
    const editor = bannerEditorRef.value;
    if (editor) {
      activeTranslation.value.body = editor.getHtml();
      for (const tr of form.value.translations) {
        tr.body = await editor.uploadPendingImages(tr.body);
      }
    }

    const payload = buildBannerPayload();
    const isCreate = !editingId.value;
    if (editingId.value) {
      const { contentType: _type, notifyInbox: _notify, ...updateBody } = payload as ReturnType<typeof buildBannerPayload> & { notifyInbox?: boolean };
      await api.put(`/admin/contents/${editingId.value}`, updateBody);
      ElMessage.success(t('msg.saved'));
    } else {
      const { data } = await api.post('/admin/contents', payload);
      const notifiedCount = Number(data.data?.notifiedCount ?? 0);
      if (notifyInbox.value && notifiedCount > 0) {
        ElMessage.success(t('content.msg.notify_sent', { count: notifiedCount }));
      } else {
        ElMessage.success(t('msg.saved'));
      }
    }
    bannerDialogVisible.value = false;
    if (isCreate) page.value = 1;
    await load();
  } catch (e: unknown) {
    const err = e as { response?: { data?: { error?: string; message?: string | string[] } } };
    const msg = err.response?.data?.error
      ?? (Array.isArray(err.response?.data?.message)
        ? err.response?.data?.message.join(', ')
        : err.response?.data?.message)
      ?? (e instanceof Error ? e.message : t('msg.save_failed'));
    ElMessage.error(String(msg));
  } finally {
    saving.value = false;
  }
}

async function submitAnnouncementForm() {
  saving.value = true;
  try {
    const payload = buildAnnouncementPayload();
    const isCreate = !editingId.value;
    if (editingId.value) {
      const { contentType: _type, notifyInbox: _notify, ...updateBody } = payload as ReturnType<
        typeof buildAnnouncementPayload
      > & { notifyInbox?: boolean };
      await api.put(`/admin/contents/${editingId.value}`, updateBody);
      ElMessage.success(t('msg.saved'));
    } else {
      const { data } = await api.post('/admin/contents', payload);
      const notifiedCount = Number(data.data?.notifiedCount ?? 0);
      if (notifyInbox.value && notifiedCount > 0) {
        ElMessage.success(t('content.msg.notify_sent', { count: notifiedCount }));
      } else {
        ElMessage.success(t('msg.saved'));
      }
    }
    announcementDialogVisible.value = false;
    if (isCreate) page.value = 1;
    await load();
  } catch (e: unknown) {
    const err = e as { response?: { data?: { error?: string; message?: string | string[] } } };
    const msg = err.response?.data?.error
      ?? (Array.isArray(err.response?.data?.message)
        ? err.response?.data?.message.join(', ')
        : err.response?.data?.message)
      ?? t('msg.save_failed');
    ElMessage.error(String(msg));
  } finally {
    saving.value = false;
  }
}

async function setStatus(row: ContentItem, status: ContentStatus) {
  saving.value = true;
  try {
    await api.patch(`/admin/contents/${row.id}/status`, { status });
    ElMessage.success(t('msg.saved'));
    await load();
  } catch (e: unknown) {
    const err = e as { response?: { data?: { error?: string } } };
    ElMessage.error(err.response?.data?.error ?? t('msg.save_failed'));
  } finally {
    saving.value = false;
  }
}

async function removeItem(row: ContentItem) {
  try {
    await ElMessageBox.confirm(
      t('content.confirm_delete', { title: previewText(row) }),
      { type: 'warning' },
    );
  } catch {
    return;
  }
  saving.value = true;
  try {
    await api.delete(`/admin/contents/${row.id}`);
    ElMessage.success(t('msg.saved'));
    await load();
  } catch (e: unknown) {
    const err = e as { response?: { data?: { error?: string } } };
    ElMessage.error(err.response?.data?.error ?? t('msg.save_failed'));
  } finally {
    saving.value = false;
  }
}

void load();
</script>

<template>
  <div class="admin-list-page contents-page">
    <el-card class="filter-card" shadow="never">
      <el-tabs v-model="activeType" class="type-tabs">
        <el-tab-pane
          v-for="tp in ADMIN_TABS"
          :key="tp"
          :label="t(`content.type.${tp}`)"
          :name="tp"
        />
      </el-tabs>

      <template v-if="isInboxNotifyTab">
        <div class="inbox-notify-panel">
          <div class="inbox-notify-row">
            <div class="inbox-notify-row-text">
              <span class="inbox-notify-label">{{ t('content.inbox_notify.inbox_enabled') }}</span>
              <span class="inbox-notify-hint">{{ t('content.inbox_notify.inbox_enabled_hint') }}</span>
            </div>
            <el-switch
              v-model="inboxNotifySettings.inboxEnabled"
              :disabled="!canManageContent || inboxNotifySaving"
              @change="saveInboxNotifySettings"
            />
          </div>

          <div v-show="inboxNotifySettings.inboxEnabled" class="inbox-notify-row">
            <div class="inbox-notify-row-text">
              <span class="inbox-notify-label">{{ t('content.inbox_notify.deposit') }}</span>
              <span class="inbox-notify-hint">{{ t('content.inbox_notify.deposit_hint') }}</span>
            </div>
            <el-switch
              v-model="inboxNotifySettings.deposit"
              :disabled="!canManageContent || inboxNotifySaving"
              @change="saveInboxNotifySettings"
            />
          </div>

          <div v-show="inboxNotifySettings.inboxEnabled" class="inbox-notify-row">
            <div class="inbox-notify-row-text">
              <span class="inbox-notify-label">{{ t('content.inbox_notify.banner') }}</span>
              <span class="inbox-notify-hint">{{ t('content.inbox_notify.banner_hint') }}</span>
            </div>
            <el-switch
              v-model="inboxNotifySettings.banner"
              :disabled="!canManageContent || inboxNotifySaving"
              @change="saveInboxNotifySettings"
            />
          </div>

          <div v-show="inboxNotifySettings.inboxEnabled" class="inbox-notify-row">
            <div class="inbox-notify-row-text">
              <span class="inbox-notify-label">{{ t('content.inbox_notify.announcement') }}</span>
              <span class="inbox-notify-hint">{{ t('content.inbox_notify.announcement_hint') }}</span>
            </div>
            <el-switch
              v-model="inboxNotifySettings.announcement"
              :disabled="!canManageContent || inboxNotifySaving"
              @change="saveInboxNotifySettings"
            />
          </div>

          <p v-show="inboxNotifySettings.inboxEnabled" class="inbox-notify-notes-title">{{ t('content.inbox_notify.manual_title') }}</p>
          <ul v-show="inboxNotifySettings.inboxEnabled" class="inbox-notify-notes">
            <li>{{ t('content.inbox_notify.banner_note') }}</li>
            <li>{{ t('content.inbox_notify.announcement_note') }}</li>
          </ul>

          <div v-if="inboxNotifySettings.inboxEnabled" class="inbox-toolbar">
            <el-button
              v-if="canManageContent"
              type="primary"
              size="small"
              @click="openBroadcastDialog"
            >
              {{ t('content.inbox_broadcast.title') }}
            </el-button>
            <span class="inbox-toolbar-hint">{{ t('content.inbox_broadcast.hint') }}</span>
          </div>
        </div>
      </template>

      <el-form v-else inline class="filter-row">
        <el-form-item :label="t('common.status')">
          <el-select v-model="filterStatus" clearable style="width: 140px">
            <el-option :label="t('common.all')" value="" />
            <el-option
              v-for="st in ['DRAFT', 'ACTIVE', 'INACTIVE']"
              :key="st"
              :label="statusLabel(st)"
              :value="st"
            />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" size="small" @click="load">{{ t('common.search') }}</el-button>
          <el-button v-if="canManageContent" type="primary" plain size="small" @click="openCreate">
            {{ t('content.btn.create') }}
          </el-button>
          <template v-if="canManageContent">
            <span v-if="hasSelection" class="batch-hint">
              {{ t('content.batch.selected', { n: selectedRows.length }) }}
            </span>
            <el-button size="small" :disabled="!hasSelection || saving" @click="batchEnable">
              {{ t('content.batch.enable') }}
            </el-button>
            <el-button size="small" :disabled="!hasSelection || saving" @click="batchDisable">
              {{ t('content.batch.disable') }}
            </el-button>
            <el-button type="danger" plain size="small" :disabled="!hasSelection || saving" @click="batchDelete">
              {{ t('content.batch.delete') }}
            </el-button>
          </template>
        </el-form-item>
      </el-form>
    </el-card>

    <el-card
      v-if="isInboxNotifyTab && inboxNotifySettings.inboxEnabled"
      v-loading="broadcastLoading"
      class="data-card"
      shadow="never"
    >
      <div class="table-wrap">
        <el-table :data="broadcastItems" row-key="id" stripe size="small">
          <template #empty>
            <AdminTableEmpty />
          </template>
          <el-table-column
            type="index"
            :index="(i: number) => (broadcastPage - 1) * broadcastPageSize + i + 1"
            :label="t('common.seq')"
            width="70"
            align="center"
          />
          <el-table-column :label="t('content.inbox_broadcast.col_title')" min-width="160" prop="title" />
          <el-table-column :label="t('content.inbox_broadcast.col_target')" width="140">
            <template #default="{ row }">
              {{ broadcastTargetLabel(row) }}
            </template>
          </el-table-column>
          <el-table-column
            :label="t('content.inbox_broadcast.col_recipients')"
            width="90"
            align="center"
            prop="recipientCount"
          />
          <el-table-column :label="t('content.inbox_broadcast.col_sender')" width="120">
            <template #default="{ row }">
              {{ row.createdByUsername || '—' }}
            </template>
          </el-table-column>
          <el-table-column :label="t('content.inbox_broadcast.col_time')" width="170">
            <template #default="{ row }">
              {{ formatBroadcastTime(row.createdAt) }}
            </template>
          </el-table-column>
          <el-table-column :label="t('common.actions')" width="130" align="center" fixed="right">
            <template #default="{ row }">
              <el-button type="primary" link size="small" @click="openBroadcastDetail(row)">
                {{ t('common.detail') }}
              </el-button>
              <el-button
                v-if="canManageContent"
                type="danger"
                link
                size="small"
                @click="deleteBroadcast(row)"
              >
                {{ t('common.delete') }}
              </el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
      <div v-if="broadcastTotal > 0" class="pager-row">
        <el-pagination
          v-model:current-page="broadcastPage"
          v-model:page-size="broadcastPageSize"
          :total="broadcastTotal"
          :page-sizes="[10, 20, 50]"
          layout="total, sizes, prev, pager, next"
          background
          small
          @current-change="onBroadcastPageChange"
          @size-change="onBroadcastSizeChange"
        />
      </div>
    </el-card>

    <el-card v-if="!isInboxNotifyTab" v-loading="loading" class="data-card" shadow="never">
      <div class="table-wrap">
        <el-table
          ref="tableRef"
          :data="items"
          row-key="id"
          stripe
          size="small"
          @selection-change="onSelectionChange"
        >
          <template #empty>
            <AdminTableEmpty />
          </template>
          <el-table-column type="selection" width="44" :selectable="() => !saving" />
          <el-table-column type="index" :index="(i: number) => (page - 1) * pageSize + i + 1" :label="t('common.seq')" width="70" align="center" />
          <el-table-column prop="sortOrder" :label="t('content.col.sort')" width="64" align="center" />
          <el-table-column :label="t('content.col.preview')" width="88" align="center">
            <template #default="{ row }">
              <img v-if="row.previewImageUrl" :src="row.previewImageUrl" alt="" class="thumb" />
              <span v-else class="thumb-empty">—</span>
            </template>
          </el-table-column>
          <el-table-column :label="t('content.col.title')" min-width="180">
            <template #default="{ row }">
              <span class="preview-title">{{ previewText(row) }}</span>
              <p v-if="!row.playerVisible && row.playerHiddenReason" class="hidden-tip">
                {{ hiddenTip(row.playerHiddenReason) }}
              </p>
            </template>
          </el-table-column>
          <el-table-column :label="t('common.status')" width="96" align="center">
            <template #default="{ row }">
              <el-tag size="small" :type="statusTagType(row.status)" effect="dark">
                {{ statusLabel(row.status) }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column :label="t('content.col.player_visible')" width="88" align="center">
            <template #default="{ row }">
              <el-tag size="small" :type="row.playerVisible ? 'success' : 'warning'" effect="plain">
                {{ row.playerVisible ? t('common.yes') : t('common.no') }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column :label="t('content.col.schedule')" min-width="140">
            <template #default="{ row }">
              <span class="schedule-line">{{ formatTime(row.startTime) }}</span>
              <span class="schedule-sep">→</span>
              <span class="schedule-line">{{ formatTime(row.endTime) }}</span>
            </template>
          </el-table-column>
          <el-table-column :label="t('content.col.link')" min-width="120">
            <template #default="{ row }">
              <template v-if="row.linkType">
                {{ row.linkType }} · {{ row.linkTarget || '—' }}
              </template>
              <span v-else>—</span>
            </template>
          </el-table-column>
          <el-table-column :label="t('common.actions')" width="200" align="center" fixed="right">
            <template #default="{ row }">
              <el-button link type="primary" @click="openEdit(row)">
                {{ t('common.edit') }}
              </el-button>
              <el-button
                v-if="row.status !== 'ACTIVE'"
                link
                type="success"
                :disabled="saving"
                @click="setStatus(row, 'ACTIVE')"
              >
                {{ t('content.btn.enable') }}
              </el-button>
              <el-button v-else link type="warning" :disabled="saving" @click="setStatus(row, 'INACTIVE')">
                {{ t('content.btn.disable') }}
              </el-button>
              <el-button link type="danger" :disabled="saving" @click="removeItem(row)">
                {{ t('common.delete') }}
              </el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
      <div class="pager">
        <el-pagination
          v-model:current-page="page"
          v-model:page-size="pageSize"
          :total="total"
          :page-sizes="[10, 20, 50, 100]"
          layout="total, sizes, prev, pager, next"
          background
          @current-change="onPageChange"
          @size-change="onSizeChange"
        />
      </div>
    </el-card>

    <el-dialog
      v-model="bannerDialogVisible"
      :title="bannerDialogTitle"
      width="1000px"
      class="content-publish-dialog"
      destroy-on-close
    >
      <el-form label-width="84px" size="small" class="publish-form">
        <div class="publish-layout">
          <aside class="publish-meta">
            <section class="publish-section publish-section--compact">
              <div class="publish-section-head">{{ t('content.section.publish') }}</div>

              <el-row :gutter="8" class="publish-grid">
                <el-col :span="12">
                  <el-form-item :label="t('common.status')" label-position="top" class="publish-field">
                    <el-select v-model="form.status" style="width: 100%">
                      <el-option
                        v-for="st in ['DRAFT', 'ACTIVE', 'INACTIVE']"
                        :key="st"
                        :label="statusLabel(st)"
                        :value="st"
                      />
                    </el-select>
                  </el-form-item>
                </el-col>
                <el-col :span="12">
                  <el-form-item :label="t('content.col.sort')" label-position="top" class="publish-field">
                    <el-input-number
                      v-model="form.sortOrder"
                      :min="0"
                      :step="1"
                      controls-position="right"
                      style="width: 100%"
                    />
                  </el-form-item>
                </el-col>
              </el-row>

              <el-row :gutter="8" class="publish-grid">
                <el-col :span="12">
                  <el-form-item :label="t('content.field.start_time')" label-position="top" class="publish-field">
                    <el-date-picker
                      v-model="form.startTime"
                      type="datetime"
                      value-format="YYYY-MM-DDTHH:mm:ss"
                      clearable
                      style="width: 100%"
                    />
                  </el-form-item>
                </el-col>
                <el-col :span="12">
                  <el-form-item :label="t('content.field.end_time')" label-position="top" class="publish-field">
                    <el-date-picker
                      v-model="form.endTime"
                      type="datetime"
                      value-format="YYYY-MM-DDTHH:mm:ss"
                      clearable
                      style="width: 100%"
                    />
                  </el-form-item>
                </el-col>
              </el-row>

              <el-form-item :label="t('content.field.link_type')" label-position="top" class="publish-field">
                <el-select v-model="form.linkType" clearable style="width: 100%">
                  <el-option :label="t('content.link.none')" value="" />
                  <el-option label="ROUTE" value="ROUTE" />
                  <el-option label="URL" value="URL" />
                </el-select>
              </el-form-item>
              <el-form-item
                v-if="form.linkType"
                :label="t('content.field.link_target')"
                label-position="top"
                class="publish-field"
              >
                <el-input
                  v-model="form.linkTarget"
                  :placeholder="form.linkType === 'ROUTE' ? '/bet' : 'https://'"
                />
              </el-form-item>

              <el-form-item v-if="!editingId" label-position="top" class="publish-field notify-inbox-field">
                <template #label>{{ t('content.field.notify_inbox') }}</template>
                <div class="notify-inbox-block">
                  <el-switch v-model="notifyInbox" :disabled="form.status !== 'ACTIVE'" />
                  <p class="notify-inbox-hint">{{ t('content.field.notify_inbox_hint') }}</p>
                </div>
              </el-form-item>
            </section>

            <section class="publish-section publish-meta-fields">
              <h3 class="section-title">{{ t('content.section.content') }}</h3>
              <el-tabs v-model="activeLocale" class="locale-tabs">
                <el-tab-pane
                  v-for="tr in form.translations"
                  :key="tr.locale"
                  :label="localeLabel(tr.locale)"
                  :name="tr.locale"
                >
                  <el-form-item
                    :label="t('content.field.title')"
                    :required="form.status === 'ACTIVE'"
                  >
                    <el-input v-model="tr.title" :placeholder="t('content.field.title_ph')" />
                  </el-form-item>

                  <el-form-item
                    :label="t('content.field.cover_image')"
                    :required="form.status === 'ACTIVE'"
                  >
                    <ContentImageField
                      v-model="tr.imageUrl"
                      category="banners"
                      size-hint-key="content.upload.recommended_size"
                    />
                  </el-form-item>
                </el-tab-pane>
              </el-tabs>
            </section>
          </aside>

          <div class="publish-body">
            <div class="publish-body-head">
              <span class="publish-body-label">
                {{ t('content.field.body') }}
                <span v-if="form.status === 'ACTIVE'" class="required-mark">*</span>
              </span>
              <span class="locale-badge">{{ localeLabel(activeLocale) }}</span>
            </div>

            <ContentRichEditor
              ref="bannerEditorRef"
              v-model="activeTranslation.body"
              fill
              upload-category="banners"
              :placeholder="t('content.editor.placeholder')"
              class="publish-rich-editor"
            />
          </div>
        </div>
      </el-form>

      <template #footer>
        <el-button @click="bannerDialogVisible = false">{{ t('common.cancel') }}</el-button>
        <el-button type="primary" :loading="saving" @click="submitBannerForm">
          {{ t('common.save') }}
        </el-button>
      </template>
    </el-dialog>

    <el-dialog
      v-model="announcementDialogVisible"
      :title="announcementDialogTitle"
      width="640px"
      class="announcement-dialog"
      destroy-on-close
    >
      <p class="announcement-hint">{{ t('content.hint.announcement') }}</p>
      <el-form label-width="96px" size="small" class="announcement-form">
        <section class="announcement-publish">
          <div class="publish-section-head">{{ t('content.section.publish') }}</div>
          <el-row :gutter="8" class="announcement-publish-row">
            <el-col :xs="12" :sm="6">
              <el-form-item :label="t('common.status')" label-position="top" class="publish-field">
                <el-select v-model="form.status" style="width: 100%">
                  <el-option
                    v-for="st in ['DRAFT', 'ACTIVE', 'INACTIVE']"
                    :key="st"
                    :label="statusLabel(st)"
                    :value="st"
                  />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :xs="12" :sm="6">
              <el-form-item :label="t('content.col.sort')" label-position="top" class="publish-field">
                <el-input-number
                  v-model="form.sortOrder"
                  :min="0"
                  :step="1"
                  controls-position="right"
                  style="width: 100%"
                />
              </el-form-item>
            </el-col>
            <el-col :xs="12" :sm="6">
              <el-form-item :label="t('content.field.start_time')" label-position="top" class="publish-field">
                <el-date-picker
                  v-model="form.startTime"
                  type="datetime"
                  value-format="YYYY-MM-DDTHH:mm:ss"
                  clearable
                  style="width: 100%"
                />
              </el-form-item>
            </el-col>
            <el-col :xs="12" :sm="6">
              <el-form-item :label="t('content.field.end_time')" label-position="top" class="publish-field">
                <el-date-picker
                  v-model="form.endTime"
                  type="datetime"
                  value-format="YYYY-MM-DDTHH:mm:ss"
                  clearable
                  style="width: 100%"
                />
              </el-form-item>
            </el-col>
          </el-row>

          <el-form-item v-if="!editingId" label-position="top" class="publish-field notify-inbox-field">
            <template #label>{{ t('content.field.notify_inbox') }}</template>
            <div class="notify-inbox-block">
              <el-switch v-model="notifyInbox" :disabled="form.status !== 'ACTIVE'" />
              <p class="notify-inbox-hint">{{ t('content.field.notify_inbox_hint') }}</p>
            </div>
          </el-form-item>
        </section>

        <section class="announcement-content">
          <h3 class="section-title">{{ t('content.section.content') }}</h3>
          <el-tabs v-model="activeLocale" class="locale-tabs">
            <el-tab-pane
              v-for="tr in form.translations"
              :key="tr.locale"
              :label="localeLabel(tr.locale)"
              :name="tr.locale"
            >
              <el-form-item :label="t('content.field.title')">
                <el-input v-model="tr.title" :placeholder="t('content.field.ticker_title_ph')" />
              </el-form-item>
              <el-form-item
                :label="t('content.field.announce_text')"
                :required="form.status === 'ACTIVE'"
              >
                <el-input
                  v-model="tr.body"
                  type="textarea"
                  :rows="4"
                  :placeholder="t('content.field.ticker_body_ph')"
                />
                <p class="field-hint compact-hint">{{ t('content.field.ticker_hint') }}</p>
              </el-form-item>
            </el-tab-pane>
          </el-tabs>
        </section>
      </el-form>

      <template #footer>
        <el-button @click="announcementDialogVisible = false">{{ t('common.cancel') }}</el-button>
        <el-button type="primary" :loading="saving" @click="submitAnnouncementForm">
          {{ t('common.save') }}
        </el-button>
      </template>
    </el-dialog>

    <el-dialog
      v-model="broadcastDetailVisible"
      :title="t('content.inbox_broadcast.view_title')"
      width="min(760px, 96vw)"
      destroy-on-close
      append-to-body
      class="inbox-broadcast-detail-dialog"
    >
      <template v-if="broadcastDetailRow">
        <dl class="broadcast-detail-meta">
          <div class="meta-row">
            <dt>{{ t('content.inbox_broadcast.col_target') }}</dt>
            <dd>{{ broadcastTargetLabel(broadcastDetailRow) }}</dd>
          </div>
          <div class="meta-row">
            <dt>{{ t('content.inbox_broadcast.col_recipients') }}</dt>
            <dd>{{ broadcastDetailRow.recipientCount }}</dd>
          </div>
          <div class="meta-row">
            <dt>{{ t('content.inbox_broadcast.col_sender') }}</dt>
            <dd>{{ broadcastDetailRow.createdByUsername || '—' }}</dd>
          </div>
          <div class="meta-row">
            <dt>{{ t('content.inbox_broadcast.col_time') }}</dt>
            <dd>{{ formatBroadcastTime(broadcastDetailRow.createdAt) }}</dd>
          </div>
        </dl>

        <el-tabs v-model="broadcastDetailLocale" class="locale-tabs">
          <el-tab-pane
            v-for="locale in LOCALES"
            :key="locale"
            :label="localeLabel(locale)"
            :name="locale"
          >
            <div class="broadcast-detail-block">
              <div class="broadcast-detail-label">{{ t('content.inbox_broadcast.field_title') }}</div>
              <div class="broadcast-detail-title">
                {{ broadcastDetailTranslation(locale).title || '—' }}
              </div>
            </div>
            <div class="broadcast-detail-block">
              <div class="broadcast-detail-label">{{ t('content.inbox_broadcast.field_body') }}</div>
              <div
                v-if="!isHtmlEmpty(broadcastDetailTranslation(locale).body)"
                class="broadcast-detail-body rich-html"
                v-html="sanitizeAnnouncementHtml(broadcastDetailTranslation(locale).body)"
              />
              <div v-else class="broadcast-detail-empty">—</div>
            </div>
          </el-tab-pane>
        </el-tabs>
      </template>
      <template #footer>
        <el-button @click="broadcastDetailVisible = false">{{ t('common.close') }}</el-button>
      </template>
    </el-dialog>

    <el-dialog
      v-model="broadcastDialogVisible"
      :title="t('content.inbox_broadcast.title')"
      width="min(920px, 96vw)"
      destroy-on-close
      :close-on-click-modal="!broadcastSending"
      class="inbox-broadcast-dialog content-publish-dialog"
      @close="closeBroadcastDialog"
    >
      <el-form label-position="top" class="inbox-broadcast-form" @submit.prevent>
        <el-form-item :label="t('content.inbox_broadcast.field_target')">
          <el-radio-group v-model="broadcastForm.targetType" :disabled="broadcastSending">
            <el-radio value="ALL">{{ t('content.inbox_broadcast.target_all') }}</el-radio>
            <el-radio value="USER">{{ t('content.inbox_broadcast.target_user') }}</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item
          v-if="broadcastForm.targetType === 'USER'"
          :label="t('content.inbox_broadcast.field_username')"
        >
          <el-input
            v-model="broadcastForm.targetUsername"
            :placeholder="t('content.inbox_broadcast.username_placeholder')"
            :disabled="broadcastSending"
          />
        </el-form-item>

        <section class="broadcast-content-section">
          <div class="broadcast-content-head">
            <h3 class="section-title">{{ t('content.section.content') }}</h3>
            <p class="field-hint">{{ t('content.inbox_broadcast.locale_fallback_hint') }}</p>
          </div>
          <el-tabs v-model="broadcastActiveLocale" class="locale-tabs">
            <el-tab-pane
              v-for="tr in broadcastForm.translations"
              :key="tr.locale"
              :label="localeLabel(tr.locale)"
              :name="tr.locale"
            >
              <el-form-item :label="t('content.inbox_broadcast.field_title')">
                <el-input
                  v-model="tr.title"
                  maxlength="256"
                  show-word-limit
                  :placeholder="t('content.field.title_ph')"
                  :disabled="broadcastSending"
                />
              </el-form-item>
            </el-tab-pane>
          </el-tabs>

          <div class="broadcast-editor-head">
            <span class="publish-body-label">{{ t('content.inbox_broadcast.field_body') }}</span>
            <span class="locale-badge">{{ localeLabel(broadcastActiveLocale) }}</span>
          </div>
          <ContentRichEditor
            ref="broadcastEditorRef"
            v-model="broadcastActiveTranslation.body"
            fill
            upload-category="contents"
            :placeholder="t('content.editor.placeholder')"
            :disabled="broadcastSending"
            class="broadcast-rich-editor"
          />
        </section>
      </el-form>
      <template #footer>
        <el-button :disabled="broadcastSending" @click="closeBroadcastDialog">
          {{ t('common.cancel') }}
        </el-button>
        <el-button type="primary" :loading="broadcastSending" @click="sendBroadcast">
          {{ t('content.inbox_broadcast.send') }}
        </el-button>
      </template>
    </el-dialog>
  </div>
</template>

<style scoped>
.contents-page .type-tabs :deep(.el-tabs__header) {
  margin-bottom: 12px;
}

.field-hint,
.batch-hint,
.schedule-line,
.schedule-sep,
.thumb-empty {
  color: var(--text-muted);
}

.field-hint {
  margin: 4px 0 0;
  font-size: 11px;
  line-height: 1.35;
}

.field-hint.inline-hint,
.field-hint.compact-hint {
  margin-top: 0;
}

.field-hint.inline-hint {
  margin-left: 8px;
}

.filter-row {
  margin-top: 4px;
}

.batch-hint {
  font-size: 12px;
  margin: 0 4px;
  white-space: nowrap;
}

.thumb {
  width: 56px;
  height: 32px;
  object-fit: cover;
  border-radius: 4px;
  background: #f4f0e8;
}

.preview-title {
  font-size: 13px;
  color: var(--text);
  font-weight: 700;
}

.hidden-tip {
  margin: 4px 0 0;
  font-size: 11px;
  color: var(--warning-text);
}

.schedule-line {
  font-size: 11px;
}

.schedule-sep {
  margin: 0 4px;
  font-size: 11px;
}

.content-publish-dialog :deep(.el-dialog__body) {
  padding-top: 10px;
  padding-bottom: 12px;
}

.content-publish-dialog :deep(.el-dialog__footer) {
  padding-top: 10px;
}

.publish-layout {
  display: flex;
  gap: 14px;
  align-items: stretch;
  min-height: 480px;
}

.publish-meta {
  flex: 0 0 380px;
  max-width: 400px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.publish-meta-fields {
  flex: 1;
  min-height: 0;
}

.publish-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  min-height: 480px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #fbfaf7;
}

.publish-body-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 8px;
}

.publish-body-label {
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
}

.required-mark {
  color: var(--el-color-danger);
  margin-left: 2px;
}

.locale-badge {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-muted);
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: #fff;
  white-space: nowrap;
}

.publish-rich-editor {
  flex: 1;
  min-height: 0;
}

.publish-form :deep(.el-form-item) {
  margin-bottom: 8px;
}

.publish-form :deep(.el-form-item__label) {
  padding-right: 8px;
  line-height: 28px;
}

.publish-section {
  margin-bottom: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #fbfaf7;
}

.publish-section--compact {
  padding: 10px 12px 8px;
}

.publish-section:last-child {
  margin-bottom: 0;
}

.section-title {
  margin: 0 0 8px;
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
}

.publish-section-head {
  margin: 0 0 8px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-muted);
}

.publish-section--compact :deep(.publish-field.el-form-item) {
  margin-bottom: 12px;
}

.publish-section--compact :deep(.publish-field.el-form-item:last-child) {
  margin-bottom: 0;
}

.publish-section--compact :deep(.publish-field .el-form-item__label) {
  padding: 0 0 4px;
  line-height: 1.35;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
}

.publish-section--compact :deep(.publish-field .el-form-item__content) {
  line-height: normal;
}

.publish-grid {
  margin-bottom: 12px;
}

.publish-grid :deep(.publish-field.el-form-item) {
  margin-bottom: 0;
}

.notify-inbox-block {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  width: 100%;
}

.notify-inbox-hint {
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-muted);
  word-break: break-word;
}

.notify-inbox-field {
  margin-top: 4px;
  padding-top: 10px;
  border-top: 1px dashed var(--border);
}

.publish-grid--secondary {
  margin-top: 2px;
}

.announcement-dialog :deep(.el-dialog__body) {
  padding-top: 8px;
}

.announcement-hint {
  margin: 0 0 12px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-muted);
}

.announcement-publish {
  margin-bottom: 12px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #fbfaf7;
}

.announcement-content {
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #fbfaf7;
}

.announcement-form :deep(.el-form-item) {
  margin-bottom: 10px;
}

.announcement-publish-row {
  margin-top: 4px;
}

@media (max-width: 900px) {
  .publish-layout {
    flex-direction: column;
    min-height: 0;
  }

  .publish-meta {
    flex: none;
    max-width: none;
    width: 100%;
  }

  .publish-body {
    min-height: 420px;
  }
}

.locale-tabs :deep(.el-tabs__header) {
  margin-bottom: 6px;
}

.locale-tabs :deep(.el-tabs__content) {
  padding-top: 0;
}

@media (max-width: 700px) {
  .filter-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
  }
}

.inbox-notify-panel {
  padding: 4px 0 12px;
  max-width: 420px;
}

.inbox-notify-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}

.inbox-notify-row-text {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.inbox-notify-label {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}

.inbox-notify-hint {
  font-size: 12px;
  color: var(--text-muted);
  line-height: 1.4;
}

.inbox-notify-notes-title {
  margin: 14px 0 6px;
  font-size: 12px;
  color: var(--text-muted);
}

.inbox-notify-notes {
  margin: 0 0 16px;
  padding-left: 18px;
  font-size: 13px;
  color: var(--text);
  line-height: 1.8;
}

.inbox-notify-notes li {
  margin: 0;
}

.inbox-toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 4px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}

.inbox-toolbar-hint {
  font-size: 12px;
  color: var(--text-muted);
  line-height: 1.4;
}

.inbox-broadcast-form :deep(.el-form-item) {
  margin-bottom: 14px;
}

.broadcast-content-section {
  margin-top: 4px;
}

.broadcast-content-head {
  margin-bottom: 8px;
}

.broadcast-content-head .section-title {
  margin: 0 0 4px;
}

.broadcast-editor-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 12px 0 8px;
}

.broadcast-rich-editor {
  min-height: 280px;
}

.inbox-broadcast-dialog :deep(.el-dialog__body) {
  padding-top: 8px;
}

.broadcast-detail-meta {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px 16px;
  margin: 0 0 16px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #fbfaf7;
}

.meta-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.meta-row dt {
  margin: 0;
  font-size: 11px;
  color: var(--text-muted);
}

.meta-row dd {
  margin: 0;
  font-size: 13px;
  color: var(--text);
  word-break: break-word;
}

.broadcast-detail-block + .broadcast-detail-block {
  margin-top: 14px;
}

.broadcast-detail-label {
  margin-bottom: 6px;
  font-size: 12px;
  color: var(--text-muted);
}

.broadcast-detail-title {
  font-size: 15px;
  font-weight: 600;
  line-height: 1.45;
  word-break: break-word;
}

.broadcast-detail-body,
.broadcast-detail-empty {
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #fff;
  min-height: 80px;
}

.broadcast-detail-empty {
  color: var(--text-muted);
}

.rich-html :deep(img) {
  max-width: 100%;
  height: auto;
  border-radius: 4px;
}

.rich-html :deep(p) {
  margin: 0 0 0.75em;
}

.rich-html :deep(p:last-child) {
  margin-bottom: 0;
}
</style>
