<script setup lang="ts">
import { ref, computed, watch } 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 {
  normalizeStartTimeForApi,
  normalizeStartTimeForPicker,
} from './match-form';

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

/* ── Image upload helpers ── */
const IMAGE_ACCEPT = 'image/png,image/jpeg,image/webp,image/gif,image/svg+xml';
const MAX_UPLOAD_SIZE = 5 * 1024 * 1024;

interface MediaFile {
  id: string;
  filename: string;
  category: string;
  mimeType: string;
  size: number;
  url: string;
  inUse: boolean;
  createdAt: string;
}

/** Per-locale uploading state */
const uploadingLocale = ref<string | null>(null);

/** Media picker state */
const mediaPickerVisible = ref(false);
const mediaPickerLocale = ref('');
const mediaFiles = ref<MediaFile[]>([]);
const mediaLoading = ref(false);

async function uploadBannerImage(locale: string, file: File) {
  if (file.size > MAX_UPLOAD_SIZE) {
    ElMessage.error(t('content.upload.size_error'));
    return;
  }
  uploadingLocale.value = locale;
  try {
    const fd = new FormData();
    fd.append('file', file);
    const { data } = await api.post('/admin/uploads?category=banners', fd, {
      headers: { 'Content-Type': 'multipart/form-data' },
    });
    const url = data.data?.url as string;
    if (url) {
      const tr = form.value.translations.find((item) => item.locale === locale);
      if (tr) tr.imageUrl = url;
      ElMessage.success(t('content.upload.success'));
    }
  } catch (err: any) {
    const msg = err?.response?.data?.message || t('content.upload.failed');
    ElMessage.error(String(msg));
  } finally {
    uploadingLocale.value = null;
  }
}

function onBannerFileChange(e: Event, locale: string) {
  const input = e.target as HTMLInputElement;
  if (input.files?.[0]) {
    void uploadBannerImage(locale, input.files[0]);
    input.value = '';
  }
}

function removeBannerImage(locale: string) {
  const tr = form.value.translations.find((item) => item.locale === locale);
  if (tr) tr.imageUrl = '';
}

async function openMediaPicker(locale: string) {
  mediaPickerLocale.value = locale;
  mediaPickerVisible.value = true;
  mediaLoading.value = true;
  try {
    const res = await api.get('/admin/files', { params: { category: 'banners', pageSize: 200 } });
    mediaFiles.value = res.data.data.items ?? [];
  } catch {
    mediaFiles.value = [];
  } finally {
    mediaLoading.value = false;
  }
}

function pickMediaFile(file: MediaFile) {
  const tr = form.value.translations.find((item) => item.locale === mediaPickerLocale.value);
  if (tr) tr.imageUrl = file.url;
  mediaPickerVisible.value = false;
}

type StoredContentType = 'BANNER' | 'NOTICE' | 'TICKER';
type AdminTab = 'BANNER' | 'ANNOUNCEMENT';
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'];
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 dialogVisible = ref(false);
const editingId = ref<string | null>(null);
const editingContentType = ref<StoredContentType>('NOTICE');

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',
  });
}

const isBanner = computed(() => activeType.value === 'BANNER');
const isAnnouncement = computed(() => activeType.value === 'ANNOUNCEMENT');
const dialogTitle = computed(() =>
  editingId.value ? t('content.dialog.edit') : t('content.dialog.create'),
);

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();
  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() {
  form.value = {
    sortOrder: 0,
    status: 'DRAFT',
    linkType: '',
    linkTarget: '',
    startTime: '',
    endTime: '',
    translations: emptyTranslations(),
  };
}

function openCreate() {
  editingId.value = null;
  resetForm();
  dialogVisible.value = true;
}

function openEdit(row: ContentItem) {
  editingId.value = row.id;
  editingContentType.value = row.contentType;
  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);
      return {
        locale,
        title: tr?.title ?? '',
        body: tr?.body ?? '',
        imageUrl: tr?.imageUrl ?? '',
      };
    }),
  };
  dialogVisible.value = true;
}

function buildPayload() {
  const contentType: StoredContentType = editingId.value
    ? editingContentType.value
    : isBanner.value
      ? 'BANNER'
      : 'NOTICE';

  return {
    contentType,
    sortOrder: form.value.sortOrder,
    status: form.value.status,
    linkType: isBanner.value && form.value.linkType ? form.value.linkType : null,
    linkTarget:
      isBanner.value && form.value.linkType ? form.value.linkTarget.trim() : null,
    startTime: form.value.startTime ? normalizeStartTimeForApi(form.value.startTime) : null,
    endTime: form.value.endTime ? normalizeStartTimeForApi(form.value.endTime) : 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,
    })),
  };
}

async function submitForm() {
  if (uploadingLocale.value) {
    ElMessage.warning(t('content.upload.uploading'));
    return;
  }
  saving.value = true;
  try {
    const payload = buildPayload();
    const isCreate = !editingId.value;
    if (editingId.value) {
      const { contentType: _type, ...updateBody } = payload;
      await api.put(`/admin/contents/${editingId.value}`, updateBody);
    } else {
      await api.post('/admin/contents', payload);
    }
    ElMessage.success(t('msg.saved'));
    dialogVisible.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: row.previewTitle || row.id }),
      { 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>
      <p v-if="isAnnouncement" class="type-hint">{{ t('content.hint.announcement') }}</p>
      <el-form 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-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 prop="sortOrder" :label="t('content.col.sort')" width="64" align="center" />
          <el-table-column v-if="isBanner" :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="160">
            <template #default="{ row }">
              <span class="preview-title">{{ row.previewTitle || '—' }}</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 v-if="isBanner" :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="dialogVisible" :title="dialogTitle" width="640px" destroy-on-close>
      <el-form label-width="96px" size="small">
        <el-form-item :label="t('content.col.sort')">
          <el-input-number v-model="form.sortOrder" :min="0" :step="1" />
        </el-form-item>
        <el-form-item :label="t('common.status')">
          <el-select v-model="form.status" style="width: 160px">
            <el-option
              v-for="st in ['DRAFT', 'ACTIVE', 'INACTIVE']"
              :key="st"
              :label="statusLabel(st)"
              :value="st"
            />
          </el-select>
        </el-form-item>
        <template v-if="isBanner">
          <el-form-item :label="t('content.field.link_type')">
            <el-select v-model="form.linkType" clearable style="width: 160px">
              <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')">
            <el-input
              v-model="form.linkTarget"
              :placeholder="form.linkType === 'ROUTE' ? '/football' : 'https://'"
            />
          </el-form-item>
        </template>
        <el-form-item :label="t('content.field.start_time')">
          <el-date-picker
            v-model="form.startTime"
            type="datetime"
            value-format="YYYY-MM-DDTHH:mm:ss"
            clearable
            style="width: 100%"
          />
        </el-form-item>
        <el-form-item :label="t('content.field.end_time')">
          <el-date-picker
            v-model="form.endTime"
            type="datetime"
            value-format="YYYY-MM-DDTHH:mm:ss"
            clearable
            style="width: 100%"
          />
        </el-form-item>

        <div v-for="tr in form.translations" :key="tr.locale" class="locale-block">
          <div class="locale-head">{{ localeLabel(tr.locale) }}</div>
          <el-form-item :label="t('content.field.title')">
            <el-input v-model="tr.title" :placeholder="t('content.field.title_ph')" />
          </el-form-item>
          <el-form-item
            v-if="isBanner"
            :label="t('content.field.image_url')"
            :required="form.status === 'ACTIVE'"
          >
            <div class="banner-upload-field">
              <p class="banner-size-hint">{{ t('content.upload.recommended_size') }}</p>
              <!-- Image preview -->
              <div v-if="tr.imageUrl" class="banner-preview">
                <img :src="tr.imageUrl" alt="" class="banner-preview-img" />
                <button type="button" class="banner-preview-remove" :title="t('content.upload.remove')" @click="removeBannerImage(tr.locale)">&times;</button>
              </div>
              <!-- Upload actions -->
              <div class="banner-upload-actions">
                <label
                  class="banner-upload-btn"
                  :class="{ 'is-uploading': uploadingLocale === tr.locale }"
                >
                  <input
                    type="file"
                    :accept="IMAGE_ACCEPT"
                    style="display: none"
                    :disabled="uploadingLocale === tr.locale"
                    @change="onBannerFileChange($event, tr.locale)"
                  />
                  {{ uploadingLocale === tr.locale ? t('content.upload.uploading') : t('content.upload.upload_btn') }}
                </label>
                <button type="button" class="banner-pick-btn" @click="openMediaPicker(tr.locale)">
                  {{ t('content.upload.pick_media') }}
                </button>
              </div>
              <!-- Manual URL fallback -->
              <el-input
                v-model="tr.imageUrl"
                :placeholder="t('content.upload.url_placeholder')"
                size="small"
                class="banner-url-input"
              />
            </div>
          </el-form-item>
          <el-form-item
            :label="isAnnouncement ? t('content.field.announce_text') : t('content.field.body')"
            :required="isAnnouncement && form.status === 'ACTIVE'"
          >
            <el-input v-model="tr.body" type="textarea" :rows="isAnnouncement ? 2 : 3" />
          </el-form-item>
        </div>
      </el-form>
      <template #footer>
        <el-button @click="dialogVisible = false">{{ t('common.cancel') }}</el-button>
        <el-button type="primary" :loading="saving" :disabled="!!uploadingLocale" @click="submitForm">
          {{ t('common.save') }}
        </el-button>
      </template>
    </el-dialog>

    <!-- Media picker dialog -->
    <el-dialog v-model="mediaPickerVisible" :title="t('content.upload.pick_media_title')" width="680px" destroy-on-close append-to-body>
      <div v-if="mediaLoading" class="media-picker-loading">{{ t('common.loading') }}</div>
      <div v-else-if="mediaFiles.length === 0" class="media-picker-empty">{{ t('content.upload.no_media') }}</div>
      <div v-else class="media-picker-grid">
        <div
          v-for="file in mediaFiles"
          :key="file.id"
          class="media-picker-card"
          @click="pickMediaFile(file)"
        >
          <div class="media-picker-thumb">
            <img v-if="file.mimeType !== 'image/svg+xml'" :src="file.url" :alt="file.filename" loading="lazy" />
            <div v-else class="media-picker-svg">SVG</div>
          </div>
          <div class="media-picker-name" :title="file.filename">{{ file.filename }}</div>
        </div>
      </div>
    </el-dialog>
  </div>
</template>

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

.type-hint {
  margin: 0 0 8px;
  font-size: 12px;
  color: #666;
  line-height: 1.5;
}

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

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

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

.thumb-empty {
  color: #555;
  font-size: 12px;
}

.preview-title {
  font-size: 13px;
  color: #ccc;
}

.hidden-tip {
  margin: 4px 0 0;
  font-size: 11px;
  color: #c9a227;
}

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

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

.locale-block {
  margin-top: 12px;
  padding: 10px 12px;
  border: 1px solid #252525;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.02);
}

.locale-head {
  font-size: 12px;
  font-weight: 700;
  color: #888;
  margin-bottom: 8px;
}

/* ── Banner image upload widget ── */
.banner-upload-field {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
}

.banner-size-hint {
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
  color: #777;
}

.banner-preview {
  position: relative;
  width: 100%;
  max-width: 320px;
  aspect-ratio: 43 / 18;
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid #252525;
  background: #111;
}

.banner-preview-img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

.banner-preview-remove {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.7);
  color: #fff;
  border: 1px solid #333;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s;
}

.banner-preview-remove:hover {
  background: rgba(224, 85, 85, 0.85);
}

.banner-upload-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.banner-upload-btn {
  display: inline-flex;
  align-items: center;
  padding: 6px 14px;
  border-radius: 6px;
  font-size: 12px;
  font-family: inherit;
  cursor: pointer;
  border: 1px solid rgba(212, 175, 55, 0.5);
  background: rgba(212, 175, 55, 0.1);
  color: var(--gold-text);
  font-weight: 600;
  transition: all 0.15s;
}

.banner-upload-btn:hover {
  background: rgba(212, 175, 55, 0.2);
}

.banner-upload-btn.is-uploading {
  opacity: 0.6;
  cursor: wait;
}

.banner-pick-btn {
  display: inline-flex;
  align-items: center;
  padding: 6px 14px;
  border-radius: 6px;
  font-size: 12px;
  font-family: inherit;
  cursor: pointer;
  border: 1px solid #2a2a2a;
  background: transparent;
  color: #888;
  transition: all 0.15s;
}

.banner-pick-btn:hover {
  border-color: #444;
  color: #ccc;
}

.banner-url-input {
  max-width: 320px;
}

.banner-url-input :deep(.el-input__wrapper) {
  font-size: 12px;
}

/* ── Media picker ── */
.media-picker-loading,
.media-picker-empty {
  text-align: center;
  padding: 40px 0;
  color: #555;
  font-size: 14px;
}

.media-picker-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 12px;
  max-height: 420px;
  overflow-y: auto;
}

.media-picker-card {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid #1e1e1e;
  border-radius: 8px;
  overflow: hidden;
  cursor: pointer;
  transition: border-color 0.15s, box-shadow 0.15s;
}

.media-picker-card:hover {
  border-color: rgba(212, 175, 55, 0.5);
  box-shadow: 0 2px 12px rgba(212, 175, 55, 0.15);
}

.media-picker-thumb {
  height: 80px;
  background: #111;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.media-picker-thumb img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

.media-picker-svg {
  font-size: 14px;
  font-weight: 700;
  color: #666;
  letter-spacing: 0.1em;
}

.media-picker-name {
  padding: 6px 8px;
  font-size: 11px;
  color: #888;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.contents-page {
  color: var(--text);
}

.type-hint,
.batch-hint,
.banner-size-hint,
.schedule-line,
.schedule-sep,
.media-picker-loading,
.media-picker-empty,
.media-picker-name,
.thumb-empty,
.locale-head {
  color: var(--text-muted);
}

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

.hidden-tip {
  color: var(--warning-text);
}

.thumb,
.banner-preview,
.media-picker-thumb {
  background: #f4f0e8;
}

.banner-preview,
.locale-block {
  border-color: var(--border);
  background: #fbfaf7;
}

.banner-preview-remove {
  border-color: rgba(255, 255, 255, 0.36);
  background: rgba(31, 35, 32, 0.78);
}

.banner-preview-remove:hover {
  background: rgba(159, 47, 45, 0.92);
}

.banner-upload-btn,
.banner-pick-btn {
  border-radius: 7px;
  font-weight: 700;
}

.banner-upload-btn {
  border-color: var(--primary);
  background: var(--primary);
  color: #ffffff;
}

.banner-upload-btn:hover {
  background: var(--primary-light);
}

.banner-pick-btn {
  border-color: var(--border);
  background: #ffffff;
  color: var(--text);
}

.banner-pick-btn:hover {
  border-color: #d5cfc3;
  background: var(--accent-hover);
  color: var(--text);
}

.media-picker-card {
  border-color: var(--border);
  background: #ffffff;
}

.media-picker-card:hover {
  border-color: #d5cfc3;
  box-shadow: 0 8px 22px rgba(56, 49, 37, 0.08);
}

.media-picker-svg {
  color: var(--text-muted);
}

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

  .banner-preview,
  .banner-url-input {
    max-width: 100%;
  }
}
</style>
