<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
import { SButton, SButtonLoading, SCard, SConfigProvider, SInput, STag } from '@soybeanjs/ui';
import LotteryParticipantsDialog from './components/LotteryParticipantsDialog.vue';

type ChatItem = Record<string, any> & { _deleting?: boolean; _ruling?: boolean };
type BanItem = Record<string, any> & { _loading?: boolean };
type LogItem = { time?: string; level?: string; message?: string };
type GroupItem = { chat_id: number; name: string };
type ShopItem = Record<string, any> & { _loading?: boolean };
type LotteryItem = Record<string, any> & { _loading?: boolean };
type WhitelistItem = Record<string, any> & { _loading?: boolean };

const sseConnected = ref(false);
const chatLogs = ref<ChatItem[]>([]);
const bans = ref<BanItem[]>([]);
const logs = ref<LogItem[]>([]);
const groups = ref<GroupItem[]>([]);
const selectedChatId = ref<number | null>(null);
const shopItems = ref<ShopItem[]>([]);
const shopTransactions = ref<Record<string, any>[]>([]);
const lotteries = ref<LotteryItem[]>([]);
const whitelistItems = ref<WhitelistItem[]>([]);
const lotteryParticipants = ref<Record<string, any>[]>([]);
const lotteryParticipantsOpen = ref(false);
const lotteryParticipantsLoading = ref(false);
const lotteryParticipantsTitle = ref('');
const spamTestText = ref('');
const spamUseAi = ref(true);
const spamTesting = ref(false);
const spamTestResult = ref<Record<string, any> | null>(null);
const ruleForm = reactive({ type: 'keyword', pattern: '', score: 75 });
const ruleSaving = ref(false);
const generatedRuleHint = ref('');
const spamRules = reactive({ keywords: [] as Record<string, any>[], regex: [] as Record<string, any>[], adblock_domains: [] as Record<string, any>[], counts: { keywords: 0, regex: 0, adblock_domains: 0 } });
const rulesLoading = ref(false);
const ruleKeyword = ref('');
const whitelistLoading = ref(false);
const whitelistSaving = ref(false);
const whitelistForm = reactive({ user_id: '', username: '', first_name: '', reason: '' });
const shopForm = reactive({ item_key: '', name: '', cost: 0, stock: -1, desc: '', enabled: true });
const shopDialogOpen = ref(false);
const lotteryForm = reactive({ id: 0, title: '', prize: '', description: '', condition_type: 'all', condition_value: 0, draw_type: 'people', end_type: 'people', end_value: 3, max_participants: 3, prize_count: 1, remaining_count: 1, draw_at: '', status: 'active' });
const lotteryDialogOpen = ref(false);
const activePage = ref<'dashboard' | 'shop' | 'lottery' | 'rules' | 'whitelist'>('dashboard');
const activeTab = reactive({ shop: 'items', lottery: 'events' });
const toast = ref('');
const confirmState = reactive({ open: false, title: '', message: '', confirmText: '确认', danger: false, resolver: null as null | ((value: boolean) => void) });
const chatContextMenu = reactive({ open: false, x: 0, y: 0, item: null as ChatItem | null });
const authReady = ref(false);
const authenticated = ref(false);
const loginForm = reactive({ username: '', password: '' });
const loggingIn = ref(false);
const shopSaving = ref(false);
const lotterySaving = ref(false);
const logsRefreshing = ref(false);
const copyingLogs = ref(false);
const logWrap = ref(false);
const newMessage = ref('');
const replyTarget = ref<ChatItem | null>(null);
const sending = ref(false);
const restarting = ref(false);
const restartingBot = ref(false);
const stats = reactive({ today_bans: 0, total_bans: 0, active_bans: 0 });
const logScroll = ref<HTMLElement | null>(null);
let es: EventSource | null = null;
let statTimer: number | undefined;

const chatCount = computed(() => chatLogs.value.length);
const selectedQuery = computed(() => selectedChatId.value ? `?chat_id=${selectedChatId.value}` : '');

const conditionOptions = [
  { value: 'all', label: '所有人', desc: '无需门槛' },
  { value: 'checkin', label: '已签到', desc: '今日签到后可参与' },
  { value: 'points', label: '积分门槛', desc: '达到指定积分' }
];
const drawOptions = [
  { value: 'instant', label: '即开即中', desc: '参与即中奖，奖品抽完结束' },
  { value: 'people', label: '满人开奖', desc: '达到人数自动开奖' },
  { value: 'time', label: '定时开奖', desc: '到指定时间自动开奖' }
];
const statusOptions = [
  { value: 'draft', label: '草稿' },
  { value: 'active', label: '启用' }
];
const lotteryEditorTitle = computed(() => lotteryForm.id ? '编辑抽奖' : '新建抽奖');
const lotteryConditionSummary = computed(() => {
  if (lotteryForm.condition_type === 'checkin') return '今日已签到用户可参与';
  if (lotteryForm.condition_type === 'points') return `积分 ≥ ${Number(lotteryForm.condition_value || 0)} 可参与`;
  return '所有群成员均可参与';
});
const lotteryDrawSummary = computed(() => {
  if (lotteryForm.draw_type === 'instant') return `即开即中：共 ${Number(lotteryForm.prize_count || 1)} 份奖品`;
  if (lotteryForm.draw_type === 'time') return `定时开奖：${lotteryForm.draw_at ? formatTime(lotteryForm.draw_at) : '未设置时间'}`;
  return `满 ${Number(lotteryForm.end_value || lotteryForm.max_participants || 1)} 人自动开奖`;
});
function selectLotteryCondition(type: string) {
  lotteryForm.condition_type = type;
  if (type !== 'points') lotteryForm.condition_value = 0;
}
function selectLotteryDraw(type: string) {
  lotteryForm.draw_type = type;
  lotteryForm.end_type = type;
  if (type === 'people') {
    lotteryForm.draw_at = '';
    lotteryForm.end_value = Number(lotteryForm.end_value || lotteryForm.max_participants || 3);
    lotteryForm.max_participants = Number(lotteryForm.end_value || 3);
  } else if (type === 'instant') {
    lotteryForm.draw_at = '';
    lotteryForm.end_value = 0;
    lotteryForm.max_participants = 0;
    lotteryForm.prize_count = Math.max(1, Number(lotteryForm.prize_count || 1));
    lotteryForm.remaining_count = lotteryForm.prize_count;
  } else {
    lotteryForm.end_value = 0;
    lotteryForm.max_participants = 0;
    if (!lotteryForm.draw_at) setLotteryDrawAtMinutes(10);
  }
}
function setLotteryDrawAtMinutes(minutes: number) {
  const d = new Date(Date.now() + minutes * 60000);
  d.setSeconds(0, 0);
  lotteryForm.draw_at = toDatetimeLocal(d);
}
function toDatetimeLocal(date: Date) {
  const pad = (n: number) => String(n).padStart(2, '0');
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
}

const pageTitle = computed(() => ({
  dashboard: '群组安全总览', shop: '商城管理', lottery: '抽奖管理', rules: '规则管理', whitelist: '白名单管理'
}[activePage.value]));
const pageDesc = computed(() => ({
  dashboard: '实时审计聊天、封禁、日志和封禁记录', shop: '商品 CRUD 与兑换交易记录', lottery: '抽奖项目、开奖条件与参与记录', rules: '检测广告/灰产文本并维护规则库', whitelist: '管理只删不封的安全白名单'
}[activePage.value]));
function goPage(page: typeof activePage.value) {
  activePage.value = page;
  if (page === 'shop') { loadShopItems(); loadShopTransactions(); }
  if (page === 'lottery') loadLotteries();
  if (page === 'rules') loadSpamRules();
  if (page === 'whitelist') loadWhitelist();
}



function isTransientBotRecord(item: ChatItem) {
  const text = String(item?.text || '').trim();
  return Number(item?.user_id || 0) === 0 && ['🔍 正在搜索知识库', '正在搜索知识库', '🔎 正在搜索知识库', '正在整理详细答案'].some(prefix => text.startsWith(prefix));
}
function visibleChatRows(rows: ChatItem[]) {
  return rows.filter(item => !isTransientBotRecord(item));
}

function formatTime(value?: string) {
  if (!value) return '-';
  const text = String(value);
  const m = text.match(/(\d{2}:\d{2}:\d{2})/);
  if (m) return m[1];
  return text.replace('T', ' ').slice(11, 19) || text;
}
function compactLog(msg?: string) {
  const raw = String(msg || '').trim();
  const text = logWrap.value ? raw : raw.replace(/\s+/g, ' ');
  return !logWrap.value && text.length > 260 ? `${text.slice(0, 260)}...` : text;
}
function chatStatusText(item: ChatItem) {
  if (item.deleted) return '已删';
  if (item.spam) return '垃圾';
  if (Number(item.user_id || 0) === 0) return 'Web';
  return '正常';
}
function chatStatusClass(item: ChatItem) {
  if (item.deleted) return 'deleted';
  if (item.spam) return 'spam';
  if (Number(item.user_id || 0) === 0) return 'web';
  return 'normal';
}
function chatItemClass(item: ChatItem) {
  return ['chat-item', chatStatusClass(item)];
}
function tagColor(item: ChatItem) {
  if (item.deleted) return 'secondary';
  if (item.spam) return 'destructive';
  if (Number(item.user_id || 0) === 0) return 'accent';
  return 'primary';
}

function showToast(message: string) {
  toast.value = message;
  window.setTimeout(() => { if (toast.value === message) toast.value = ''; }, 2600);
}
function askConfirm(title: string, message: string, options: { confirmText?: string; danger?: boolean } = {}) {
  confirmState.open = true;
  confirmState.title = title;
  confirmState.message = message;
  confirmState.confirmText = options.confirmText || '确认';
  confirmState.danger = Boolean(options.danger);
  return new Promise<boolean>((resolve) => { confirmState.resolver = resolve; });
}
function closeConfirm(value: boolean) {
  confirmState.open = false;
  confirmState.resolver?.(value);
  confirmState.resolver = null;
}

async function checkAuth() {
  try {
    const data = await fetchJson('/api/auth/status');
    authenticated.value = Boolean(data.authenticated);
  } catch {
    authenticated.value = false;
  } finally {
    authReady.value = true;
  }
}
async function login() {
  if (!loginForm.username || !loginForm.password) return showToast('请输入账号和密码');
  loggingIn.value = true;
  try {
    await fetchJson('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(loginForm) });
    authenticated.value = true;
    await bootstrapData();
  } catch (e: any) {
    showToast(e.message || '登录失败');
  } finally { loggingIn.value = false; }
}
async function logout() {
  await fetchJson('/api/auth/logout', { method: 'POST' }).catch(() => {});
  authenticated.value = false;
}

async function fetchJson(url: string, options?: RequestInit) {
  const res = await fetch(url, options);
  const data = await res.json().catch(() => ({}));
  if (!res.ok || data.ok === false) {
    const err: any = new Error(data.error || data.message || `HTTP ${res.status}`);
    err.data = data;
    throw err;
  }
  return data;
}
async function loadChatHistory() {
  try {
    const data = await fetchJson(`/api/chat${selectedQuery.value}`);
    chatLogs.value = visibleChatRows(data.map((m: ChatItem) => ({ ...m, _deleting: false })));
  } catch {}
}
async function loadBans() {
  try {
    const data = await fetchJson(`/api/bans${selectedQuery.value}`);
    bans.value = data.map((b: BanItem) => ({ ...b, _loading: false }));
  } catch {}
}

async function loadWhitelist() {
  if (whitelistLoading.value) return;
  whitelistLoading.value = true;
  try {
    if (!selectedChatId.value) { whitelistItems.value = []; return; }
    const data = await fetchJson(`/api/whitelist${selectedQuery.value}`);
    whitelistItems.value = data.map((w: WhitelistItem) => ({ ...w, _loading: false }));
  } catch (e: any) { showToast(`加载白名单失败：${e.message}`); }
  finally { whitelistLoading.value = false; }
}
async function saveWhitelist() {
  if (!selectedChatId.value) return showToast('请先选择群组');
  const userId = Number(whitelistForm.user_id || 0);
  if (!userId) return showToast('请填写用户 ID');
  whitelistSaving.value = true;
  try {
    await fetchJson('/api/whitelist', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        chat_id: selectedChatId.value,
        user_id: userId,
        username: whitelistForm.username,
        first_name: whitelistForm.first_name,
        reason: whitelistForm.reason || 'Web 手动添加白名单'
      })
    });
    whitelistForm.user_id = '';
    whitelistForm.username = '';
    whitelistForm.first_name = '';
    whitelistForm.reason = '';
    await loadWhitelist();
    showToast('已添加白名单');
  } catch (e: any) { showToast(`添加白名单失败：${e.message}`); }
  finally { whitelistSaving.value = false; }
}
async function deleteWhitelist(item: WhitelistItem) {
  if (!await askConfirm('移出白名单', `确认移出 @${item.username || item.first_name || item.user_id}？`, { confirmText: '移出', danger: true })) return;
  item._loading = true;
  try {
    await fetchJson(`/api/whitelist/${item.id}`, { method: 'DELETE' });
    await loadWhitelist();
    showToast('已移出白名单');
  } catch (e: any) { showToast(`移出失败：${e.message}`); }
  finally { item._loading = false; }
}
async function loadStats() {
  try {
    Object.assign(stats, await fetchJson(`/api/stats${selectedQuery.value}`));
  } catch {}
}
async function loadLogs() {
  try {
    logs.value = await fetchJson('/api/logs');
    scrollLogBottom();
  } catch {}
}
function scrollLogBottom() {
  nextTick(() => {
    if (logScroll.value) logScroll.value.scrollTop = logScroll.value.scrollHeight;
  });
}

async function loadGroups() {
  try {
    groups.value = await fetchJson('/api/groups');
    if (!selectedChatId.value && groups.value.length) selectedChatId.value = groups.value[0].chat_id;
  } catch {}
}
async function loadShopItems() {
  try { shopItems.value = (await fetchJson(`/api/shop-items${selectedQuery.value}`)).map((x: ShopItem) => ({ ...x, _loading: false })); } catch {}
}
async function loadShopTransactions() {
  try { shopTransactions.value = await fetchJson(`/api/shop-transactions${selectedQuery.value}`); } catch {}
}
async function saveShopItem() {
  if (!shopForm.item_key || !shopForm.name) return showToast('请填写商品编号和名称');
  if (shopSaving.value) return;
  shopSaving.value = true;
  try {
    await fetchJson('/api/shop-items', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ...shopForm, chat_id: selectedChatId.value || 0 })
    });
    Object.assign(shopForm, { item_key: '', name: '', cost: 0, stock: -1, desc: '', enabled: true });
    shopDialogOpen.value = false;
    await loadShopItems();
  } catch (e: any) { showToast(`保存商品失败：${e.message}`); }
  finally { shopSaving.value = false; }
}
function newShopItem() { Object.assign(shopForm, { item_key: '', name: '', cost: 0, stock: -1, desc: '', enabled: true }); shopDialogOpen.value = true; }
async function editShopItem(item: ShopItem) { Object.assign(shopForm, { item_key: item.item_key || '', name: item.name || '', cost: item.cost || 0, stock: item.stock ?? -1, desc: item.desc || '', enabled: item.enabled !== false }); shopDialogOpen.value = true; }
async function deleteShopItem(item: ShopItem) {
  if (item._loading) return;
  if (!await askConfirm('删除商品', `确认删除商品「${item.name}」？交易记录会保留。`, { confirmText: '删除', danger: true })) return;
  item._loading = true;
  try {
    await fetchJson(`/api/shop-items/${item.item_key}${selectedQuery.value}`, { method: 'DELETE' });
    await loadShopItems();
  } catch (e: any) { showToast(`删除商品失败：${e.message}`); }
  finally { item._loading = false; }
}
async function loadLotteries() {
  try { lotteries.value = (await fetchJson(`/api/lotteries${selectedQuery.value}`)).map((x: LotteryItem) => ({ ...x, _loading: false })); } catch {}
}
function resetLotteryForm() { Object.assign(lotteryForm, { id: 0, title: '', prize: '', description: '', condition_type: 'all', condition_value: 0, draw_type: 'people', end_type: 'people', end_value: 3, max_participants: 3, prize_count: 1, remaining_count: 1, draw_at: '', status: 'active' }); }
function newLottery() { resetLotteryForm(); lotteryParticipants.value = []; lotteryDialogOpen.value = true; }
function editLottery(item: LotteryItem) {
  if (Number(item.participants_count || 0) > 0) return showToast('已有用户参与，不能编辑，仅可开奖或关闭');
  const keys = ['id', 'title', 'prize', 'description', 'condition_type', 'condition_value', 'draw_type', 'end_type', 'end_value', 'max_participants', 'prize_count', 'remaining_count', 'draw_at', 'status'];
  const picked: Record<string, any> = {};
  keys.forEach((key) => { picked[key] = item[key]; });
  Object.assign(lotteryForm, picked, { id: item.id || 0 });
  lotteryDialogOpen.value = true;
}
async function saveLottery() {
  if (!lotteryForm.title || !lotteryForm.prize) return showToast('请填写抽奖标题和奖品');
  if (lotteryForm.condition_type === 'points' && Number(lotteryForm.condition_value || 0) <= 0) return showToast('请填写积分门槛');
  if (lotteryForm.draw_type === 'people' && Number(lotteryForm.end_value || 0) <= 0) return showToast('请填写开奖人数');
  if (lotteryForm.draw_type === 'instant' && Number(lotteryForm.prize_count || 0) <= 0) return showToast('请填写奖品数量');
  if (lotteryForm.draw_type === 'time' && !lotteryForm.draw_at) return showToast('请选择开奖时间');
  if (lotterySaving.value) return;
  lotterySaving.value = true;
  try {
    const payload = { ...lotteryForm, chat_id: selectedChatId.value || 0 };
    payload.end_type = payload.draw_type;
    if (payload.draw_type === 'people') {
      payload.end_value = Number(payload.end_value || payload.max_participants || 1);
      payload.max_participants = payload.end_value;
      payload.draw_at = '';
      payload.prize_count = 1;
      payload.remaining_count = 1;
    } else if (payload.draw_type === 'instant') {
      payload.end_value = 0;
      payload.max_participants = 0;
      payload.draw_at = '';
      payload.prize_count = Math.max(1, Number(payload.prize_count || 1));
      payload.remaining_count = payload.id ? Number(payload.remaining_count ?? payload.prize_count) : payload.prize_count;
    } else {
      payload.end_value = 0;
      payload.max_participants = 0;
      payload.prize_count = 1;
      payload.remaining_count = 1;
    }
    const url = lotteryForm.id ? `/api/lotteries/${lotteryForm.id}` : '/api/lotteries';
    await fetchJson(url, { method: lotteryForm.id ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
    showToast(lotteryForm.id ? '抽奖已更新' : '抽奖已创建');
    resetLotteryForm();
    lotteryDialogOpen.value = false;
    await loadLotteries();
  } catch (e: any) { showToast(`保存抽奖失败：${e.message}`); }
  finally { lotterySaving.value = false; }
}
async function deleteLottery(item: LotteryItem) {
  if (item._loading) return;
  if (!await askConfirm('删除抽奖', `确认删除抽奖「${item.title || item.prize}」？参与记录会一并移除。`, { confirmText: '删除', danger: true })) return;
  item._loading = true;
  try { await fetchJson(`/api/lotteries/${item.id}`, { method: 'DELETE' }); await loadLotteries(); }
  catch (e: any) { showToast(`删除抽奖失败：${e.message}`); }
  finally { item._loading = false; }
}
async function loadLotteryParticipants(item: LotteryItem) {
  lotteryParticipants.value = await fetchJson(`/api/lotteries/${item.id}/participants`);
}
async function showParticipants(item: LotteryItem) {
  if (lotteryParticipantsLoading.value) return;
  lotteryParticipantsTitle.value = item.title || item.prize || `抽奖 #${item.id}`;
  lotteryParticipantsOpen.value = true;
  lotteryParticipantsLoading.value = true;
  try {
    await loadLotteryParticipants(item);
  } catch (e: any) {
    showToast(`加载参与记录失败：${e.message}`);
  } finally {
    lotteryParticipantsLoading.value = false;
  }
}
function closeParticipantsDialog() {
  lotteryParticipantsOpen.value = false;
  lotteryParticipants.value = [];
}
async function cancelLottery(item: LotteryItem) {
  if (item._loading) return;
  if (!await askConfirm('取消抽奖', '确认取消这个抽奖？已参与用户不会自动补偿。', { confirmText: '取消抽奖', danger: true })) return;
  item._loading = true;
  try { await fetchJson(`/api/lotteries/${item.id}/cancel`, { method: 'POST' }); await loadLotteries(); }
  catch (e: any) { showToast(`取消抽奖失败：${e.message}`); }
  finally { item._loading = false; }
}
async function drawLottery(item: LotteryItem) {
  if (item._loading) return;
  if (!await askConfirm('立即开奖', '确认现在开奖？开奖后状态会变更。', { confirmText: '立即开奖' })) return;
  item._loading = true;
  try { await fetchJson(`/api/lotteries/${item.id}/draw`, { method: 'POST' }); await loadLotteries(); }
  catch (e: any) { showToast(`开奖失败：${e.message}`); }
  finally { item._loading = false; }
}

async function testSpamText() {
  if (!spamTestText.value.trim()) return showToast('请输入要检测的文本');
  if (spamTesting.value) return;
  spamTesting.value = true;
  try {
    spamTestResult.value = await fetchJson('/api/spam-test', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ text: spamTestText.value, use_ai: spamUseAi.value })
    });
  } catch (e: any) { showToast(`检测失败：${e.message}`); }
  finally { spamTesting.value = false; }
}
async function loadSpamRules() {
  if (rulesLoading.value) return;
  rulesLoading.value = true;
  try {
    const result = await fetchJson('/api/spam-rules');
    spamRules.keywords = result.keywords || [];
    spamRules.regex = result.regex || [];
    spamRules.counts = result.counts || { keywords: 0, regex: 0 };
  } catch (e: any) { showToast(`加载规则库失败：${e.message}`); }
  finally { rulesLoading.value = false; }
}
const filteredKeywordRules = computed(() => {
  const key = ruleKeyword.value.trim().toLowerCase();
  const rows = [...spamRules.keywords].reverse();
  return key ? rows.filter(r => String(r.pattern || '').toLowerCase().includes(key)).slice(0, 300) : rows.slice(0, 120);
});
const filteredRegexRules = computed(() => {
  const key = ruleKeyword.value.trim().toLowerCase();
  const rows = [...spamRules.regex].reverse();
  return key ? rows.filter(r => String(r.pattern || '').toLowerCase().includes(key)).slice(0, 300) : rows.slice(0, 120);
});
const filteredAdblockRules = computed(() => {
  const key = ruleKeyword.value.trim().toLowerCase();
  const rows = [...spamRules.adblock_domains];
  return key ? rows.filter(r => String(r.pattern || '').toLowerCase().includes(key)).slice(0, 300) : rows.slice(0, 120);
});

async function saveSpamRule() {
  if (!ruleForm.pattern.trim()) return showToast('请输入规则内容');
  if (ruleSaving.value) return;
  ruleSaving.value = true;
  try {
    const result = await fetchJson('/api/spam-rules', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(ruleForm)
    });
    generatedRuleHint.value = '';
    showToast(`规则已写入，关键词 ${result.rule_counts?.keywords || '-'} / 正则 ${result.rule_counts?.regex || '-'}`);
    ruleForm.pattern = '';
    await loadSpamRules();
    await testSpamText();
  } catch (e: any) { showToast(`写入规则失败：${e.message}`); }
  finally { ruleSaving.value = false; }
}

async function addSpamTextToRules() {
  if (!spamTestText.value.trim()) return showToast('请输入要入库的聊天内容');
  if (ruleSaving.value) return;
  ruleSaving.value = true;
  try {
    const result = await fetchJson('/api/spam-rules/from-text', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ text: spamTestText.value, use_ai: spamUseAi.value })
    });
    const generated = result.generated_rule || {};
    ruleForm.type = result.type || generated.type || 'keyword';
    ruleForm.pattern = result.pattern || generated.pattern || '';
    ruleForm.score = Number(result.score || generated.score || 90);
    generatedRuleHint.value = `${generated.ai_used === false ? '兜底生成' : 'AI生成'}：${ruleForm.type === 'regex' ? '正则' : '关键词'} / ${ruleForm.score} / ${ruleForm.pattern}${generated.reason ? `（${generated.reason}）` : ''}`;
    showToast(`已${generated.ai_used === false ? '兜底' : 'AI'}生成并写入规则：${ruleForm.pattern}`);
    await loadSpamRules();
    await testSpamText();
  } catch (e: any) {
    const generated = e?.data?.generated_rule;
    if (generated?.pattern) {
      ruleForm.type = generated.type || 'keyword';
      ruleForm.pattern = generated.pattern;
      ruleForm.score = Number(generated.score || 90);
      generatedRuleHint.value = `已生成但未写入：${ruleForm.pattern}${generated.reason ? `（${generated.reason}）` : ''}`;
    }
    showToast(`内容入库失败：${e.message}`);
  }
  finally { ruleSaving.value = false; }
}

function reloadByGroup() { loadChatHistory(); loadBans(); loadStats(); loadShopItems(); loadShopTransactions(); loadLotteries(); loadWhitelist(); }

function connectSSE() {
  if (es) es.close();
  es = new EventSource('/api/stream');
  es.addEventListener('connected', () => { sseConnected.value = true; });
  es.addEventListener('chat', (e) => {
    const data = JSON.parse((e as MessageEvent).data);
    if (isTransientBotRecord(data)) return;
    chatLogs.value.unshift(data);
    if (chatLogs.value.length > 120) chatLogs.value.pop();
  });
  es.addEventListener('spam', (e) => {
    const data = JSON.parse((e as MessageEvent).data);
    const item = chatLogs.value.find(x => x.msg_id === data.msg_id);
    if (item) Object.assign(item, data);
  });
  es.addEventListener('chat_delete', (e) => {
    const data = JSON.parse((e as MessageEvent).data);
    const item = chatLogs.value.find(x => x.id === data.id);
    if (item) Object.assign(item, { deleted: true, manually_deleted: true, deleted_at: data.deleted_at });
    loadStats();
  });
  es.addEventListener('ban', (e) => {
    const data = JSON.parse((e as MessageEvent).data);
    bans.value.unshift({ ...data, _loading: false });
    if (bans.value.length > 80) bans.value.pop();
    loadStats();
  });
  es.addEventListener('unban', (e) => {
    const data = JSON.parse((e as MessageEvent).data);
    const ban = bans.value.find(b => b.user_id === data.user_id);
    if (ban) ban.auto_unban = false;
    loadStats();
  });
  es.addEventListener('log', (e) => {
    logs.value.push(JSON.parse((e as MessageEvent).data));
    if (logs.value.length > 300) logs.value.shift();
    scrollLogBottom();
  });
  es.onerror = () => {
    sseConnected.value = false;
    window.setTimeout(connectSSE, 3000);
  };
}

async function sendMessage() {
  const text = newMessage.value.trim();
  if (!text || sending.value) return;
  sending.value = true;
  try {
    await fetchJson('/api/send', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ text, chat_id: selectedChatId.value, reply_to_message_id: replyTarget.value?.msg_id || null })
    });
    newMessage.value = '';
    replyTarget.value = null;
  } catch (e: any) {
    showToast(`发送失败：${e.message}`);
  } finally {
    sending.value = false;
  }
}


function openChatContextMenu(event: MouseEvent, item: ChatItem) {
  chatContextMenu.item = item;
  chatContextMenu.open = true;
  const menuWidth = 188;
  const menuHeight = item.deleted ? 184 : 224;
  chatContextMenu.x = Math.min(event.clientX, window.innerWidth - menuWidth - 8);
  chatContextMenu.y = Math.min(event.clientY, window.innerHeight - menuHeight - 8);
}
function closeChatContextMenu() {
  chatContextMenu.open = false;
  chatContextMenu.item = null;
}
async function contextAddToRules() {
  const item = chatContextMenu.item;
  closeChatContextMenu();
  if (item) await addChatMessageToRules(item);
}
async function contextAddWhitelist() {
  const item = chatContextMenu.item;
  closeChatContextMenu();
  if (!item?.user_id) return showToast('当前消息缺少用户 ID，无法加入白名单');
  if (!await askConfirm('加入白名单', `确认将 @${item.username || item.first_name || item.user_id} 加入白名单？
白名单用户命中垃圾规则时只删除消息，不封禁账号。`, { confirmText: '加入白名单' })) return;
  try {
    await fetchJson(`/api/messages/${item.id}/whitelist`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ reason: 'Web 右键加入白名单' })
    });
    showToast('已加入白名单：后续只删消息，不封禁账号');
  } catch (e: any) {
    showToast(`加入白名单失败：${e.message}`);
  }
}
async function contextDeleteMessage(ban = false) {
  const item = chatContextMenu.item;
  closeChatContextMenu();
  if (item) await deleteChatMessage(item, ban);
}
async function pickBanDuration() {
  const text = window.prompt('选择封禁时间：0=永久，60=1小时，360=6小时，1440=1天，10080=7天', '0');
  if (text === null) return null;
  const value = Number(String(text).trim());
  if (!Number.isFinite(value) || value < 0) {
    showToast('封禁时间无效，请输入 0 或正整数分钟');
    return null;
  }
  return Math.floor(value);
}
function banDurationText(minutes: number) {
  if (!minutes) return '永久';
  if (minutes % 1440 === 0) return `${minutes / 1440} 天`;
  if (minutes % 60 === 0) return `${minutes / 60} 小时`;
  return `${minutes} 分钟`;
}
async function contextDeleteAndBanMessage() {
  const item = chatContextMenu.item;
  closeChatContextMenu();
  if (!item) return;
  const duration = await pickBanDuration();
  if (duration === null) return;
  if (item.deleted) await banChatMessage(item, duration);
  else await deleteChatMessage(item, true, duration);
}
async function contextBanMessage() {
  const item = chatContextMenu.item;
  closeChatContextMenu();
  if (!item) return;
  const duration = await pickBanDuration();
  if (duration === null) return;
  await banChatMessage(item, duration);
}
async function contextCopyText() {
  const item = chatContextMenu.item;
  const text = String(item?.text || '').trim();
  closeChatContextMenu();
  if (!text) return showToast('当前消息没有可复制的文本');
  try {
    await navigator.clipboard.writeText(text);
    showToast('消息文本已复制');
  } catch {
    showToast('复制失败，浏览器未允许剪贴板权限');
  }
}
function contextReplyMessage() {
  const item = chatContextMenu.item;
  closeChatContextMenu();
  if (!item?.msg_id) return showToast('当前消息无法回复');
  replyTarget.value = item;
  nextTick(() => document.querySelector<HTMLInputElement>('.floating-chat-send input')?.focus());
}
function cancelReply() { replyTarget.value = null; }

async function banChatMessage(item: ChatItem, durationMinutes = 0) {
  if (!item.user_id) return showToast('当前记录没有用户 ID，无法封禁');
  if (!await askConfirm('封禁用户', `确认封禁 @${item.username || item.first_name || item.user_id}？时长：${banDurationText(durationMinutes)}`, { confirmText: '封禁', danger: true })) return;
  item._deleting = true;
  try {
    await fetchJson(`/api/messages/${item.id}/ban`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ duration_minutes: durationMinutes }) });
    await loadBans();
    await loadStats();
  } catch (e: any) { showToast(`封禁失败：${e.message}`); }
  finally { item._deleting = false; }
}

async function addChatMessageToRules(item: ChatItem) {
  const text = String(item.text || '').trim();
  if (!text) return showToast('当前消息没有可写入的文本');
  if (text.length > 500) return showToast('当前消息超过 500 字，请到规则测试页提取关键词或正则');
  if (!await askConfirm('写入拦截库', '确认将当前聊天内容作为关键词写入拦截规则库？', { confirmText: '写入', danger: false })) return;
  item._ruling = true;
  try {
    const result = await fetchJson('/api/spam-rules', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ type: 'keyword', pattern: text, score: 90 })
    });
    showToast(`已写入拦截库，关键词 ${result.rule_counts?.keywords || '-'} / 正则 ${result.rule_counts?.regex || '-'}`);
  } catch (e: any) {
    showToast(`写入失败：${e.message}`);
  } finally {
    item._ruling = false;
  }
}

async function deleteChatMessage(item: ChatItem, ban = false, durationMinutes = 0) {
  if (item.manually_deleted || item.deleted) return showToast('该消息已删除');
  const action = ban ? '删除并封禁' : '删除';
  const message = ban ? `确认删除这条消息并封禁用户？时长：${banDurationText(durationMinutes)}` : '确认删除这条消息？';
  if (!await askConfirm(action, message, { confirmText: action, danger: true })) return;
  item._deleting = true;
  try {
    await fetchJson(`/api/messages/${item.id}/delete`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ban_user: ban, duration_minutes: durationMinutes })
    });
    Object.assign(item, { deleted: true, manually_deleted: true });
    await loadBans();
    await loadStats();
  } catch (e: any) {
    showToast(`${action}失败：${e.message}`);
  } finally {
    item._deleting = false;
  }
}
async function unban(item: BanItem) {
  if (!await askConfirm('解除封禁', `确认解封 ${item.username || item.user_id}？`, { confirmText: '解封' })) return;
  item._loading = true;
  try {
    await fetchJson(`/api/unban/${item.user_id}`, { method: 'POST' });
    item.auto_unban = false;
    await loadStats();
  } catch (e: any) {
    showToast(`解封失败：${e.message}`);
  } finally {
    item._loading = false;
  }
}
async function restartBot() {
  if (!await askConfirm('重启 Bot', '确认只重启 Telegram Bot？Web 管理页不会重启。', { confirmText: '重启 Bot' })) return;
  restartingBot.value = true;
  try {
    await fetchJson('/api/restart-bot', { method: 'POST' });
    window.setTimeout(() => { restartingBot.value = false; loadLogs(); }, 5000);
  } catch (e: any) {
    restartingBot.value = false;
    showToast(`重启 Bot 失败：${e.message}`);
  }
}
async function restartService() {
  if (!await askConfirm('重启服务', '确认重启 TG Spam Guard 服务？重启期间 Web 和 Bot 会短暂不可用。', { confirmText: '重启服务', danger: true })) return;
  restarting.value = true;
  try {
    await fetchJson('/api/restart', { method: 'POST' });
  } catch {}
  window.setTimeout(() => location.reload(), 8000);
}
async function refreshLogs() {
  if (logsRefreshing.value) return;
  logsRefreshing.value = true;
  try { await loadLogs(); } finally { logsRefreshing.value = false; }
}
async function copyLogs() {
  if (copyingLogs.value) return;
  copyingLogs.value = true;
  try {
    const text = logs.value.map(l => `${formatTime(l.time)} ${l.level || ''} ${compactLog(l.message)}`).join('\n');
    await navigator.clipboard?.writeText(text);
    showToast('日志已复制');
  } finally { copyingLogs.value = false; }
}

async function bootstrapData() {
  await loadGroups().then(reloadByGroup);
  loadChatHistory();
  loadBans();
  loadStats();
  loadShopItems();
  loadLotteries();
  loadLogs();
  loadWhitelist();
  connectSSE();
  if (!statTimer) statTimer = window.setInterval(loadStats, 30000);
}
onMounted(async () => {
  window.addEventListener('click', closeChatContextMenu);
  window.addEventListener('scroll', closeChatContextMenu, true);
  await checkAuth();
  if (authenticated.value) await bootstrapData();
});
onBeforeUnmount(() => {
  window.removeEventListener('click', closeChatContextMenu);
  window.removeEventListener('scroll', closeChatContextMenu, true);
  if (es) es.close();
  if (statTimer) window.clearInterval(statTimer);
});
</script>

<template>
  <SConfigProvider :theme="{ theme: { primary: 'indigo' }, size: 'sm' }">
    <div v-if="!authReady" class="login-page"><div class="login-card">正在检查登录状态...</div></div>
    <div v-else-if="!authenticated" class="login-page"><form class="login-card" @submit.prevent="login"><img class="brand-icon" src="/icon.svg" alt="TG Spam Guard" /><h1>TG Spam Guard</h1><p>请输入管理账号密码</p><SInput v-model="loginForm.username" placeholder="账号" /><SInput v-model="loginForm.password" type="password" placeholder="密码" /><SButton type="submit" color="primary" :loading="loggingIn" :disabled="loggingIn">登录</SButton></form></div>
    <main v-else class="admin-shell">
      <aside class="sidebar">
        <div class="side-brand">
          <img class="brand-icon" src="/icon.svg" alt="TG Spam Guard" />
          <div>
            <h1>TG Spam Guard</h1>
            <p>群组安全后台</p>
          </div>
        </div>
        <div class="side-section">
          <span class="side-label">当前群组</span>
          <select v-model="selectedChatId" class="form-select side-select" @change="reloadByGroup"><option :value="null">全部群组</option><option v-for="g in groups" :key="g.chat_id" :value="g.chat_id">{{ g.label || g.name }}</option></select>
        </div>
        <nav class="side-nav">
          <button :class="{active: activePage === 'dashboard'}" @click="goPage('dashboard')"><span>总览</span><em>Dashboard</em></button>
          <button :class="{active: activePage === 'shop'}" @click="goPage('shop')"><span>商城管理</span><em>Shop</em></button>
          <button :class="{active: activePage === 'lottery'}" @click="goPage('lottery')"><span>抽奖管理</span><em>Lottery</em></button>
          <button :class="{active: activePage === 'rules'}" @click="goPage('rules')"><span>规则管理</span><em>Rules</em></button>
          <button :class="{active: activePage === 'whitelist'}" @click="goPage('whitelist')"><span>白名单管理</span><em>Whitelist</em></button>
        </nav>
        <div class="side-footer">
          <span class="conn" :class="sseConnected ? 'on' : 'off'">{{ sseConnected ? '实时连接' : '连接中' }}</span>
          <small>Bot 管理控制台</small>
        </div>
      </aside>

      <section class="admin-main">
        <header class="admin-header" id="dashboard">
          <div>
            <span class="eyebrow">CONTROL CENTER</span>
            <h2>{{ pageTitle }}</h2>
            <p>{{ pageDesc }}</p>
          </div>
          <div class="top-actions compact-actions">
            <SButton size="xs" variant="outline" @click="logout">退出</SButton>
            <SButton size="xs" variant="outline" color="primary" :loading="restartingBot" :disabled="restartingBot" @click="restartBot">重启 Bot</SButton>
            <SButton size="xs" variant="soft" color="warning" :loading="restarting" :disabled="restarting" @click="restartService">重启服务</SButton>
          </div>
        </header>

      <section v-show="activePage === 'dashboard'" class="stats-grid compact-stats">
        <SCard class="stat-card blue"><div class="stat-row"><span class="stat-icon">今</span><div><span>今日封禁</span><strong>{{ stats.today_bans || 0 }}</strong></div></div></SCard>
        <SCard class="stat-card red"><div class="stat-row"><span class="stat-icon">总</span><div><span>累计封禁</span><strong>{{ stats.total_bans || 0 }}</strong></div></div></SCard>
        <SCard class="stat-card orange"><div class="stat-row"><span class="stat-icon">封</span><div><span>当前封禁</span><strong>{{ stats.active_bans || 0 }}</strong></div></div></SCard>
        <SCard class="stat-card green"><div class="stat-row"><span class="stat-icon">聊</span><div><span>聊天记录</span><strong>{{ chatCount }}</strong></div></div></SCard>
      </section>

      <section v-show="activePage === 'dashboard'" class="main-grid">
        <SCard id="chat" class="panel chat-panel" title="聊天记录" :description="`${chatCount} 条`">
          <div class="scroll-area chat-scroll">
            <div v-if="!chatLogs.length" class="empty">等待消息...</div>
            <article v-for="(item, i) in chatLogs" :key="item.id || i" :class="[chatItemClass(item), { deleting: item._deleting || item._ruling }]" @contextmenu.prevent.stop="openChatContextMenu($event, item)">
              <div class="chat-head">
                <div class="chat-user-line">
                  <STag size="xs" :color="tagColor(item)" variant="soft">{{ chatStatusText(item) }}</STag>
                  <span class="chat-user">@{{ item.username || item.first_name || 'unknown' }}</span>
                  <span class="chat-uid">{{ item.user_id }}</span>
                </div>
                <div class="chat-meta-line">
                  <span class="chat-id">#{{ item.msg_id || '-' }}</span>
                  <span class="chat-time">{{ formatTime(item.time) }}</span>
                </div>
              </div>
              <div class="chat-text" :title="item.text || '（空消息）'">{{ item.text || '（空消息）' }}</div>
              <div class="chat-foot">
                <STag v-if="item.spam" class="reason-tag" size="xs" color="destructive" variant="soft" :title="item.reason || item.spam_reason">{{ item.reason || item.spam_reason }}</STag>
                <span v-else />
                <div class="chat-actions">
                  <STag v-if="item.deleted" size="xs" color="secondary" variant="soft">已删 · {{ formatTime(item.deleted_at) }}</STag>
                  <STag v-else size="xs" color="secondary" variant="soft">右键操作</STag>
                </div>
              </div>
              <div v-if="item._deleting || item._ruling" class="chat-loading-mask">
                <span class="mini-spinner"></span>
                <strong>{{ item._ruling ? '正在写入拦截库...' : '正在处理当前消息...' }}</strong>
              </div>
            </article>
          </div>
        </SCard>

        <SCard class="panel log-panel" title="📜 实时日志" :description="`${logs.length} 条`">
          <template #extra>
            <div class="panel-extra">
              <SButtonLoading size="xs" variant="outline" :loading="copyingLogs" :disabled="copyingLogs" @click="copyLogs">复制</SButtonLoading>
              <SButton size="xs" variant="outline" @click="logWrap = !logWrap">{{ logWrap ? '单行' : '换行' }}</SButton>
              <SButtonLoading size="xs" variant="outline" :loading="logsRefreshing" :disabled="logsRefreshing" @click="refreshLogs">刷新</SButtonLoading>
            </div>
          </template>
          <div ref="logScroll" class="log-window" :class="{ wrap: logWrap }">
            <div v-if="!logs.length" class="empty dark">等待日志...</div>
            <div v-for="(item, i) in logs" :key="i" class="log-line" :class="item.level" :title="compactLog(item.message)">
              <span class="log-time">{{ formatTime(item.time) }}</span>
              <span class="log-level">{{ item.level }}</span>
              <span class="log-msg">{{ compactLog(item.message) }}</span>
            </div>
          </div>
        </SCard>

        <SCard class="panel ban-panel overview-ban-panel" title="🚫 封禁记录" :description="`${bans.length} 条`">
          <template #extra><SButton size="xs" variant="outline" @click="loadBans">刷新</SButton></template>
          <div class="ban-list overview-ban-list v-overflow">
            <div v-if="!bans.length" class="empty">暂无封禁记录</div>
            <div v-for="(item, i) in bans" :key="item.id || i" class="ban-item">
              <div class="ban-main">
                <b>@{{ item.username || item.first_name || item.user_id }}</b>
                <span>{{ item.reason || '-' }}</span>
              </div>
              <div class="ban-meta">
                <span>封禁 {{ formatTime(item.time || item.created_at) }}</span>
                <span v-if="item.chat_id" class="muted">群 {{ item.chat_id }}</span>
                <span v-if="item.unban_at" class="muted">解封 {{ formatTime(item.unban_at) }}</span>
                <SButton v-if="item.auto_unban" size="xs" variant="outline" color="success" :loading="item._loading" :disabled="item._loading" @click="unban(item)">解封</SButton>
                <STag v-else size="xs" color="secondary" variant="soft">已处理</STag>
              </div>
            </div>
          </div>
        </SCard>
      </section>

      <div
        v-if="chatContextMenu.open"
        class="chat-context-menu"
        :style="{ left: `${chatContextMenu.x}px`, top: `${chatContextMenu.y}px` }"
        @click.stop
        @contextmenu.prevent
      >
        <button class="danger" :disabled="!!chatContextMenu.item?.deleted" @click="contextDeleteMessage(false)">🗑️ 删除</button>
        <button class="warning" :disabled="!chatContextMenu.item?.user_id" @click="contextDeleteAndBanMessage">⛔ 删除并封禁</button>
        <button class="warning" :disabled="!chatContextMenu.item?.user_id" @click="contextBanMessage">🚫 封印用户</button>
        <button :disabled="!chatContextMenu.item?.user_id" @click="contextAddWhitelist">🛡️ 加入白名单</button>
        <button @click="contextAddToRules">📥 写入拦截库</button>
        <button @click="contextCopyText">📋 复制内容</button>
        <button @click="contextReplyMessage">↩️ 回复</button>
      </div>


      <section v-show="activePage === 'whitelist'" class="tab-page whitelist-page">
        <SCard class="panel page-panel whitelist-panel" title="🛡️ 白名单管理" :description="`${whitelistItems.length} 个用户 · 仅当前群组生效，命中垃圾时只删消息不封禁`">
          <div class="whitelist-toolbar">
            <div class="scope-chip"><span>当前群组</span><strong>{{ selectedChatId || '-' }}</strong></div>
            <label><span>用户 ID</span><input v-model="whitelistForm.user_id" class="form-input" placeholder="必填，如 5418812283" /></label>
            <label><span>用户名</span><input v-model="whitelistForm.username" class="form-input" placeholder="可选，不带 @" /></label>
            <label><span>昵称</span><input v-model="whitelistForm.first_name" class="form-input" placeholder="可选" /></label>
            <label class="reason-field"><span>原因</span><input v-model="whitelistForm.reason" class="form-input" placeholder="可选，如 熟人/误杀保护" /></label>
            <SButton color="primary" :loading="whitelistSaving" :disabled="whitelistSaving" @click="saveWhitelist">添加</SButton>
            <SButton variant="outline" :loading="whitelistLoading" :disabled="whitelistLoading" @click="loadWhitelist">刷新</SButton>
          </div>
          <div class="data-table whitelist-table">
            <div class="table-head"><span>用户</span><span>范围</span><span>原因</span><span>更新时间</span><span>操作</span></div>
            <div v-if="!whitelistItems.length" class="empty whitelist-empty">暂无白名单用户</div>
            <div v-for="item in whitelistItems" :key="item.id" class="table-row">
              <div><b>@{{ item.username || item.first_name || item.user_id }}</b><small>{{ item.user_id }}</small></div>
              <span>{{ item.chat_id }}</span>
              <em :title="item.reason">{{ item.reason || '-' }}</em>
              <small>{{ formatTime(item.updated_at || item.created_at) }}</small>
              <SButton size="xs" variant="outline" color="destructive" :loading="item._loading" :disabled="item._loading" @click="deleteWhitelist(item)">移出</SButton>
            </div>
          </div>
        </SCard>
      </section>

      <section v-show="activePage === 'rules'" class="tab-page rule-manage-page">
        <SCard class="panel page-panel rule-detect-card compact-detect-card" title="🧪 规则检测" description="粘贴聊天内容，快速判断广告 / 垃圾 / 灰产，并可写入规则库">
          <div class="rule-detect-compact rule-detect-row">
            <label class="rule-text-field">
              <span>聊天内容</span>
              <textarea v-model="spamTestText" class="form-input rule-textarea compact" placeholder="粘贴可疑聊天内容，例如：点击 https://xxx 领取福利"></textarea>
            </label>
            <div class="rule-result-card compact" :class="{ danger: spamTestResult?.final_is_spam, ok: spamTestResult && spamTestResult.final_is_spam === false }">
              <div v-if="!spamTestResult" class="compact-empty">暂无检测结果</div>
              <template v-else>
                <div class="compact-result-top">
                  <STag :color="spamTestResult.final_is_spam ? 'destructive' : spamTestResult.final_is_spam === null ? 'warning' : 'success'" variant="soft">
                    {{ spamTestResult.final_is_spam ? '广告/垃圾/灰产' : spamTestResult.final_is_spam === null ? '需 AI 判断' : '正常' }}
                  </STag>
                  <strong>评分 {{ spamTestResult.score }}</strong>
                </div>
                <div class="compact-reason" :title="spamTestResult.reason">{{ spamTestResult.reason }}</div>
                <div v-if="spamTestResult.ai_reason" class="compact-reason muted" :title="spamTestResult.ai_reason">AI：{{ spamTestResult.ai_reason }}</div>
                <div class="compact-counts">关键词 {{ spamTestResult.rule_counts?.keywords }} / 正则 {{ spamTestResult.rule_counts?.regex }} / 域名 {{ spamTestResult.rule_counts?.adblock_domains || spamRules.counts.adblock_domains || 0 }}</div>
              </template>
            </div>
            <div class="rule-action-bar">
              <label class="switch-line compact"><input v-model="spamUseAi" type="checkbox" /> AI 兜底</label>
              <SButtonLoading color="primary" :loading="spamTesting" :disabled="spamTesting" @click="testSpamText">检测</SButtonLoading>
              <SButtonLoading variant="outline" color="warning" :loading="ruleSaving" :disabled="ruleSaving || !spamTestText.trim()" @click="addSpamTextToRules">AI入库</SButtonLoading>
            </div>
          </div>
          <div v-if="generatedRuleHint" class="generated-rule-hint">{{ generatedRuleHint }}</div>
          <div class="rule-write-grid inline-rule-writer compact-rule-writer">
            <label>类型<select v-model="ruleForm.type" class="form-select"><option value="keyword">关键词</option><option value="regex">正则</option></select></label>
            <label>分数<SInput v-model="ruleForm.score" type="number" placeholder="75" /></label>
            <label class="rule-pattern-field">规则条目<SInput v-model="ruleForm.pattern" :placeholder="ruleForm.type === 'keyword' ? '如 黑U' : '如 (?:黑\s*[uU]).{0,20}(?:联系|项目)'" /></label>
            <SButtonLoading color="primary" :loading="ruleSaving" :disabled="ruleSaving" @click="saveSpamRule">写入</SButtonLoading>
          </div>
        </SCard>

        <SCard class="panel page-panel rule-library-card" title="📚 规则库" :description="`关键词 ${spamRules.counts.keywords} 条 / 正则 ${spamRules.counts.regex} 条 / Adblock 域名 ${spamRules.counts.adblock_domains || 0} 条`">
          <template #extra><SButtonLoading size="xs" variant="outline" :loading="rulesLoading" :disabled="rulesLoading" @click="loadSpamRules">刷新</SButtonLoading></template>
          <div class="rule-library-toolbar"><SInput v-model="ruleKeyword" placeholder="搜索规则条目" /></div>
          <div class="rule-library-grid">
            <div><h4>关键词规则</h4><div class="rule-list"><div v-for="r in filteredKeywordRules" :key="`kw-${r.line}-${r.pattern}`" class="rule-row"><span :title="r.pattern">{{ r.pattern }}</span><em>{{ r.score || '-' }}</em></div><div v-if="!filteredKeywordRules.length" class="empty">暂无关键词规则</div></div></div>
            <div><h4>正则规则</h4><div class="rule-list"><div v-for="r in filteredRegexRules" :key="`re-${r.line}-${r.pattern}`" class="rule-row"><span :title="r.pattern">{{ r.pattern }}</span><em>{{ r.score || '-' }}</em></div><div v-if="!filteredRegexRules.length" class="empty">暂无正则规则</div></div></div>
          </div>
        </SCard>
      </section>

      <section v-show="activePage === 'shop'" class="tab-page manager-layout single-panel-page">
        <SCard class="panel data-panel" title="🛒 商城管理" :description="`${shopItems.length} 个商品`">
          <template #extra><SButton size="xs" color="primary" @click="newShopItem">新增商品</SButton></template>
          <div class="tabbar"><button :class="{active: activeTab.shop === 'items'}" @click="activeTab.shop='items'">商品列表</button><button :class="{active: activeTab.shop === 'tx'}" @click="activeTab.shop='tx'; loadShopTransactions()">交易记录</button></div>
          <div v-if="activeTab.shop === 'items'" class="data-table shop-table"><div class="table-head"><span>编号/商品</span><span>价格</span><span>库存</span><span>状态</span><span>操作</span></div><div v-for="item in shopItems" :key="item.item_key" class="table-row"><span><b>{{ item.item_key }}</b><em>{{ item.name }}</em><small>{{ item.desc || '无说明' }}</small></span><span>{{ item.cost }} 分</span><span>{{ Number(item.stock ?? -1) < 0 ? '不限' : item.stock }}</span><span><STag size="xs" :color="item.enabled ? 'success' : 'secondary'" variant="soft">{{ item.enabled ? '上架' : '下架' }}</STag></span><span class="row-actions"><SButton size="xs" variant="outline" @click="editShopItem(item)">编辑</SButton><SButton size="xs" variant="soft" color="destructive" :loading="item._loading" :disabled="item._loading" @click="deleteShopItem(item)">删除</SButton></span></div></div>
          <div v-else class="data-table tx-table"><div class="table-head"><span>用户</span><span>商品</span><span>积分</span><span>时间</span></div><div v-for="tx in shopTransactions" :key="tx.id" class="table-row"><span>@{{ tx.username || tx.user_id }}</span><span>{{ tx.item_name }}</span><span>{{ tx.cost }} 分</span><span>{{ formatTime(tx.created_at) }}</span></div></div>
        </SCard>
      </section>

      <section v-show="activePage === 'lottery'" class="tab-page manager-layout single-panel-page lottery-page">
        <SCard class="panel data-panel" title="🎁 抽奖项目" :description="`${lotteries.length} 个活动`">
          <template #extra><SButton size="xs" color="primary" @click="newLottery">新增抽奖</SButton></template>
          <div class="table-list lottery-list">
            <div v-for="item in lotteries" :key="item.id" class="data-row lottery-row">
              <div class="data-main">
                <div class="row-title">
                  <strong>{{ item.title || item.prize }}</strong>
                  <STag size="xs" :color="item.status === 'active' ? 'success' : item.status === 'drawn' ? 'primary' : item.status === 'cancelled' ? 'destructive' : 'secondary'" variant="soft">{{ item.status }}</STag>
                </div>
                <div class="row-desc">奖品：{{ item.prize }} · 条件：{{ item.condition_type }}{{ item.condition_value ? ':' + item.condition_value : '' }}</div>
                <div class="row-desc">开奖：{{ item.draw_type === 'time' ? formatTime(item.draw_at) : `满 ${item.max_participants || item.end_value || '-'} 人` }} · 参与：{{ item.participants_count || 0 }} 人</div>
              </div>
              <div class="row-actions">
                <SButton size="xs" variant="outline" :disabled="Number(item.participants_count || 0) > 0" @click="editLottery(item)">编辑</SButton>
                <SButton size="xs" variant="outline" @click="showParticipants(item)">查看参与</SButton>
                <SButton size="xs" variant="outline" color="primary" :loading="item._loading" :disabled="item._loading || item.status !== 'active'" @click="drawLottery(item)">开奖</SButton>
                <SButton size="xs" variant="outline" color="warning" :loading="item._loading" :disabled="item._loading || item.status !== 'active'" @click="cancelLottery(item)">关闭</SButton>
                <SButton size="xs" variant="link" color="destructive" :loading="item._loading" :disabled="item._loading" @click="deleteLottery(item)">删除</SButton>
              </div>
            </div>
            <div v-if="!lotteries.length" class="empty">暂无抽奖项目</div>
          </div>
        </SCard>
      </section>

      <form v-if="activePage === 'dashboard'" class="send-box floating-chat-send" @submit.prevent="sendMessage">
        <div v-if="replyTarget" class="reply-chip">
          <span>回复 @{{ replyTarget.username || replyTarget.first_name || replyTarget.user_id }}：{{ replyTarget.text || '（空消息）' }}</span>
          <button type="button" @click="cancelReply">×</button>
        </div>
        <SInput v-model="newMessage" :placeholder="replyTarget ? '输入回复内容' : '发送消息到 TG 群'" />
        <SButton type="submit" color="primary" :loading="sending" :disabled="sending || !newMessage.trim()">发送</SButton>
      </form>

      <div v-if="shopDialogOpen" class="confirm-mask" @click.self="shopDialogOpen=false">
        <div class="confirm-card editor-dialog"><h3>{{ shopForm.item_key ? '编辑商品' : '新增商品' }}</h3><p>库存填 -1 表示不限库存，0 表示售罄。</p><label>商品编号<SInput v-model="shopForm.item_key" placeholder="如 1 / vip_card" /></label><label>商品名称<SInput v-model="shopForm.name" placeholder="补签卡" /></label><label>积分价格<SInput v-model="shopForm.cost" type="number" placeholder="50" /></label><label>库存<SInput v-model="shopForm.stock" type="number" placeholder="-1 不限库存" /></label><label>说明<SInput v-model="shopForm.desc" placeholder="展示在 /shop 内" /></label><label class="switch-line"><input v-model="shopForm.enabled" type="checkbox" /> 上架销售</label><div class="confirm-actions"><SButton size="xs" variant="outline" :disabled="shopSaving" @click="shopDialogOpen=false">取消</SButton><SButtonLoading size="xs" color="primary" :loading="shopSaving" :disabled="shopSaving" @click="saveShopItem">保存</SButtonLoading></div></div>
      </div>
      <div v-if="lotteryDialogOpen" class="confirm-mask" @click.self="lotteryDialogOpen=false">
        <div class="confirm-card editor-dialog lottery-dialog">
          <div class="dialog-head">
            <div><h3>{{ lotteryEditorTitle }}</h3><p>{{ lotteryConditionSummary }} · {{ lotteryDrawSummary }}</p></div>
            <button class="dialog-close" type="button" @click="lotteryDialogOpen=false">×</button>
          </div>
          <div class="form-section"><h4>基础信息</h4><div class="form-grid two"><label>抽奖标题<SInput v-model="lotteryForm.title" placeholder="如 月卡福利抽奖" /></label><label>奖品内容<SInput v-model="lotteryForm.prize" placeholder="月卡一张 / 邀请码" /></label></div><label>说明<SInput v-model="lotteryForm.description" placeholder="展示给参与用户，可留空" /></label></div>
          <div class="form-section"><h4>参与门槛</h4><div class="segmented option-grid"><button v-for="option in conditionOptions" :key="option.value" type="button" :class="{active: lotteryForm.condition_type === option.value}" @click="selectLotteryCondition(option.value)"><b>{{ option.label }}</b><small>{{ option.desc }}</small></button></div><label v-if="lotteryForm.condition_type === 'points'">积分门槛<SInput v-model="lotteryForm.condition_value" type="number" placeholder="100" /></label></div>
          <div class="form-section"><h4>开奖方式</h4><div class="segmented option-grid"><button v-for="option in drawOptions" :key="option.value" type="button" :class="{active: lotteryForm.draw_type === option.value}" @click="selectLotteryDraw(option.value)"><b>{{ option.label }}</b><small>{{ option.desc }}</small></button></div><label v-if="lotteryForm.draw_type === 'people'">开奖人数<SInput v-model="lotteryForm.end_value" type="number" placeholder="3" /></label><label v-else-if="lotteryForm.draw_type === 'instant'">奖品数量<SInput v-model="lotteryForm.prize_count" type="number" placeholder="1" /></label><div v-else class="datetime-block"><label>开奖时间<input v-model="lotteryForm.draw_at" class="form-input" type="datetime-local" /></label><div class="quick-time"><SButton size="xs" variant="outline" @click="setLotteryDrawAtMinutes(10)">10 分钟</SButton><SButton size="xs" variant="outline" @click="setLotteryDrawAtMinutes(30)">30 分钟</SButton><SButton size="xs" variant="outline" @click="setLotteryDrawAtMinutes(60)">1 小时</SButton></div></div></div>
          <div class="form-section"><h4>状态</h4><div class="segmented small"><button v-for="option in statusOptions" :key="option.value" type="button" :class="{active: lotteryForm.status === option.value}" @click="lotteryForm.status = option.value"><b>{{ option.label }}</b></button></div></div>
          <div class="confirm-actions sticky-actions"><SButton size="xs" variant="outline" :disabled="lotterySaving" @click="lotteryDialogOpen=false">取消</SButton><SButtonLoading size="xs" color="primary" :loading="lotterySaving" :disabled="lotterySaving" @click="saveLottery">保存</SButtonLoading></div>
        </div>
      </div>

      <LotteryParticipantsDialog
        :open="lotteryParticipantsOpen"
        :title="lotteryParticipantsTitle"
        :loading="lotteryParticipantsLoading"
        :participants="lotteryParticipants"
        :format-time="formatTime"
        @close="closeParticipantsDialog"
      />

      <div v-if="toast" class="toast">{{ toast }}</div>
      <div v-if="confirmState.open" class="confirm-mask" @click.self="closeConfirm(false)">
        <div class="confirm-card">
          <h3>{{ confirmState.title }}</h3>
          <p>{{ confirmState.message }}</p>
          <div class="confirm-actions">
            <SButton size="xs" variant="outline" @click="closeConfirm(false)">关闭</SButton>
            <SButton size="xs" :color="confirmState.danger ? 'destructive' : 'primary'" @click="closeConfirm(true)">{{ confirmState.confirmText }}</SButton>
          </div>
        </div>
      </div>
      </section>
    </main>
  </SConfigProvider>
</template>
