<template>
    <div class="default-main">
        <el-alert type="info" :title="t('game.live.tip')" show-icon class="mb-12" />
        <el-alert :type="pushConnected ? 'success' : 'error'" :title="pushConnected ? t('game.live.push_connected') : t('game.live.push_disconnected')" show-icon class="mb-12" />
        <el-alert
            v-if="snapshot.runtime_enabled === false && !snapshot.maintenance_ui"
            type="warning"
            :title="t('game.live.runtime_draining_banner')"
            show-icon
            class="mb-12"
        />
        <el-alert
            v-if="snapshot.maintenance_ui"
            type="warning"
            :title="t('game.live.runtime_maintenance_banner')"
            show-icon
            class="mb-12"
        />

        <div class="live-top-toolbar">
            <div class="live-top-toolbar__row">
                <span class="live-top-toolbar__label">{{ t('game.live.runtime_switch') }}</span>
                <el-switch
                    :model-value="snapshot.runtime_enabled"
                    :loading="runtimeSwitchLoading"
                    :disabled="runtimeSwitchLoading || voidSubmitting"
                    @change="onRuntimeSwitch"
                />
                <span v-if="snapshot.maintenance_ui" class="live-top-toolbar__hint">{{ t('game.live.runtime_off_tip') }}</span>
            </div>
        </div>

        <el-card shadow="never" class="mb-12 live-control-card">
            <el-alert v-if="snapshot.is_payout_phase" type="warning" :title="t('game.live.payout_phase')" show-icon class="mb-12" />
            <div class="live-control-layout">
                <div class="live-control-main">
                    <el-descriptions :column="1" border size="small" class="live-desc">
                        <el-descriptions-item :label="t('game.live.current_record')">
                            <span class="period-no">{{ snapshot.record?.period_no || '—' }}</span>
                        </el-descriptions-item>
                        <el-descriptions-item :label="t('game.live.ai_default_number')">
                            <span class="num-em">{{ snapshot.ai_default_number ?? '—' }}</span>
                        </el-descriptions-item>
                        <el-descriptions-item v-if="snapshot.pending_draw_number != null" :label="t('game.live.pending_draw')">
                            <el-tag type="primary" effect="plain">{{ snapshot.pending_draw_number }}</el-tag>
                        </el-descriptions-item>
                    </el-descriptions>

                    <div class="countdown-block">
                        <div class="countdown-block__title">{{ t('game.live.countdown') }}</div>
                        <div v-if="snapshot.maintenance_ui" class="countdown-maintenance">
                            {{ t('game.live.countdown_maintenance') }}
                        </div>
                        <div v-else class="countdown-cards">
                            <div class="cd-card">
                                <span class="cd-card__label">{{ t('game.live.bet_countdown') }}</span>
                                <span class="cd-card__val">{{ countdownParts.bet }}s</span>
                            </div>
                            <div class="cd-card">
                                <span class="cd-card__label">{{ t('game.live.draw_countdown') }}</span>
                                <span class="cd-card__val">{{ countdownParts.draw }}s</span>
                            </div>
                            <div class="cd-card" :class="{ 'is-active': snapshot.is_payout_phase }">
                                <span class="cd-card__label">{{ t('game.live.payout_countdown') }}</span>
                                <span class="cd-card__val">{{ countdownParts.payout }}</span>
                            </div>
                        </div>
                    </div>

                    <div class="calc-result-bar">
                        <span class="calc-result-bar__item">
                            <span class="calc-result-bar__k">{{ t('game.live.calc_result_number') }}</span>
                            <span class="calc-result-bar__v">{{ calcResultNumber ?? '—' }}</span>
                        </span>
                        <span class="calc-result-bar__item">
                            <span class="calc-result-bar__k">{{ t('game.live.calc_estimated_loss') }}</span>
                            <span class="calc-result-bar__v mono">{{ calcEstimatedLoss }}</span>
                        </span>
                    </div>
                </div>

                <aside class="live-control-aside" :class="{ 'is-locked': asideOperationLocked }">
                    <div class="aside-title">{{ t('game.live.action_panel') }}</div>
                    <el-button
                        class="aside-void-btn"
                        type="danger"
                        plain
                        :disabled="asideOperationLocked || !canVoidPeriod || voidSubmitting || runtimeSwitchLoading"
                        @click="openVoidDialog"
                    >
                        {{ t('game.live.void_btn') }}
                    </el-button>
                    <div class="aside-field">
                        <span class="aside-field__label">{{ t('game.live.manual_draw_number') }}</span>
                        <el-input-number
                            v-model="manualNumber"
                            class="aside-field__input"
                            :min="1"
                            :max="snapshot.draw_number_max ?? 36"
                            :step="1"
                            :disabled="asideOperationLocked"
                            controls-position="right"
                        />
                    </div>
                    <div class="aside-btns">
                        <el-button
                            :loading="calcLoading"
                            :disabled="asideOperationLocked || !snapshot.can_calculate"
                            @click="onCalculate"
                        >
                            {{ t('game.live.btn_calc') }}
                        </el-button>
                        <el-button
                            type="primary"
                            :loading="drawLoading"
                            :disabled="asideOperationLocked || !snapshot.can_schedule_draw"
                            @click="onDraw"
                        >
                            {{ t('game.live.btn_draw') }}
                        </el-button>
                        <el-button :loading="loading" :disabled="asideOperationLocked" @click="loadSnapshot">{{ t('Refresh') }}</el-button>
                    </div>
                </aside>
            </div>
        </el-card>

        <el-row :gutter="12">
            <el-col :span="12">
                <el-card shadow="never">
                    <template #header>{{ t('game.live.candidate_title') }}</template>
                    <el-table :data="snapshot.candidate_numbers" height="420">
                        <el-table-column prop="number" :label="t('game.live.number')" width="100" />
                        <el-table-column prop="estimated_loss" :label="t('game.live.estimated_loss')" />
                    </el-table>
                </el-card>
            </el-col>
            <el-col :span="12">
                <el-card shadow="never">
                    <template #header>{{ t('game.live.bet_stream_title') }}</template>
                    <el-table :data="snapshot.bets" height="420">
                        <el-table-column prop="id" :label="t('game.live.bet_id')" width="90" />
                        <el-table-column prop="user_id" :label="t('game.live.user_id')" width="90" />
                        <el-table-column prop="pick_numbers" :label="t('game.live.pick_numbers')">
                            <template #default="scope">
                                {{ formatPicks(scope.row.pick_numbers) }}
                            </template>
                        </el-table-column>
                        <el-table-column prop="total_amount" :label="t('game.live.total_amount')" width="120" />
                        <el-table-column prop="streak_at_bet" :label="t('game.live.streak_at_bet')" width="90" />
                    </el-table>
                </el-card>
            </el-col>
        </el-row>

        <el-dialog
            v-model="voidDialogVisible"
            class="live-void-dialog"
            :title="t('game.live.void_dialog_title')"
            width="520px"
            align-center
            append-to-body
            destroy-on-close
            :close-on-click-modal="false"
            @closed="voidReason = ''"
        >
            <el-form class="live-void-form" label-position="top" @submit.prevent>
                <el-form-item :label="t('game.live.void_reason_label')">
                    <el-input
                        v-model="voidReason"
                        type="textarea"
                        :rows="5"
                        :autosize="{ minRows: 4, maxRows: 12 }"
                        :placeholder="t('game.live.void_reason_placeholder')"
                        maxlength="255"
                        show-word-limit
                    />
                </el-form-item>
            </el-form>
            <template #footer>
                <div class="live-void-footer">
                    <el-button @click="voidDialogVisible = false">{{ t('Cancel') }}</el-button>
                    <el-button type="primary" :loading="voidSubmitting" @click="submitVoidPeriod">{{ t('game.live.void_submit') }}</el-button>
                </div>
            </template>
        </el-dialog>
    </div>
</template>

<script setup lang="ts">
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import createAxios, { getPushScriptUrl } from '/@/utils/axios'

interface Snapshot {
    record: anyObj | null
    bets: anyObj[]
    candidate_numbers: anyObj[]
    ai_default_number: number | null
    pending_draw_number: number | null
    period_seconds?: number
    bet_seconds?: number
    pick_max_number_count?: number
    /** 开奖号码池上限（1–draw_number_max），与单注可选号码上限无关 */
    draw_number_max?: number
    remaining_seconds?: number
    bet_remaining_seconds?: number
    payout_remaining_seconds?: number
    is_payout_phase?: boolean
    can_calculate?: boolean
    can_draw?: boolean
    can_schedule_draw?: boolean
    server_time?: number
    /** 游戏运行开关：false 表示已关服（当局仍可进行至派彩结束） */
    runtime_enabled?: boolean
    /** 完整维护 UI：关服且当前无进行中/未结清对局（派彩已全部完成） */
    maintenance_ui?: boolean
}

const { t } = useI18n()
const loading = ref(false)
const pushConnected = ref(false)
const snapshot = reactive<Snapshot>({
    record: null,
    bets: [],
    candidate_numbers: [],
    ai_default_number: null,
    pending_draw_number: null,
    period_seconds: 30,
    bet_seconds: 20,
    pick_max_number_count: 10,
    draw_number_max: 36,
    remaining_seconds: 0,
    bet_remaining_seconds: 0,
    payout_remaining_seconds: 0,
    is_payout_phase: false,
    can_calculate: false,
    can_draw: false,
    can_schedule_draw: false,
    runtime_enabled: true,
    maintenance_ui: false,
})
const calcLoading = ref(false)
const drawLoading = ref(false)
const runtimeSwitchLoading = ref(false)
const voidDialogVisible = ref(false)
const voidReason = ref('')
const voidSubmitting = ref(false)
const manualNumber = ref<number | null>(1)
const calcResultNumber = ref<number | null>(null)
const calcEstimatedLoss = ref<string>('0.00')

/** 服务端 Unix 秒 − 本地 Unix 秒，用于派彩倒计时与服务器对齐 */
const serverSkewSeconds = ref(0)
/** 每秒递增，驱动派彩剩余秒本地刷新 */
const clockTick = ref(0)
let clockTimer: number | null = null

let pushClient: any = null
let pushChannel: any = null
let pollTimer: number | null = null
let pushWatchdogTimer: number | null = null

function formatPicks(v: unknown): string {
    if (Array.isArray(v)) return JSON.stringify(v)
    if (typeof v === 'string') return v
    return '-'
}

const canVoidPeriod = computed(() => {
    const r = snapshot.record
    if (!r) {
        return false
    }
    const s = Number(r.status)
    return s === 0 || s === 1
})

/** 派彩结束后的完整维护态：操作区除顶部开关外全部锁定 */
const asideOperationLocked = computed(() => snapshot.maintenance_ui === true)

function mergeLiveSnapshot(data: anyObj): void {
    if (data.record !== undefined) {
        snapshot.record = data.record
    }
    snapshot.bets = data.bets || []
    snapshot.candidate_numbers = data.candidate_numbers || []
    snapshot.ai_default_number = data.ai_default_number ?? null
    snapshot.pending_draw_number = typeof data.pending_draw_number === 'number' ? data.pending_draw_number : null
    snapshot.period_seconds = data.period_seconds ?? 30
    snapshot.bet_seconds = data.bet_seconds ?? 20
    snapshot.pick_max_number_count = data.pick_max_number_count ?? 10
    snapshot.draw_number_max = data.draw_number_max ?? 36
    snapshot.remaining_seconds = data.remaining_seconds ?? 0
    snapshot.bet_remaining_seconds = data.bet_remaining_seconds ?? 0
    snapshot.payout_remaining_seconds = data.payout_remaining_seconds ?? 0
    snapshot.is_payout_phase = !!data.is_payout_phase
    snapshot.can_calculate = !!data.can_calculate
    snapshot.can_draw = !!data.can_draw
    snapshot.can_schedule_draw = !!(data.can_schedule_draw || data.can_draw)
    if (typeof data.runtime_enabled === 'boolean') {
        snapshot.runtime_enabled = data.runtime_enabled
    }
    if (typeof data.maintenance_ui === 'boolean') {
        snapshot.maintenance_ui = data.maintenance_ui
    }
    syncServerClock(data.server_time)
}


function syncServerClock(serverTime: unknown): void {
    if (typeof serverTime === 'number' && Number.isFinite(serverTime)) {
        serverSkewSeconds.value = serverTime - Math.floor(Date.now() / 1000)
        snapshot.server_time = serverTime
    }
}

function readPayoutUntilUnix(rec: anyObj | null): number | null {
    if (!rec) {
        return null
    }
    const v = rec.payout_until
    if (v === null || v === undefined || v === '') {
        return null
    }
    if (typeof v === 'number' && Number.isFinite(v)) {
        return v
    }
    if (typeof v === 'string' && /^\d+$/.test(v)) {
        return parseInt(v, 10)
    }
    return null
}

/** 派彩剩余秒：优先用 payout_until 与对时后的「服务器当前秒」计算，便于每秒递减 */
const payoutRemainingLive = computed(() => {
    clockTick.value
    if (!snapshot.is_payout_phase) {
        return null
    }
    const until = readPayoutUntilUnix(snapshot.record)
    if (until !== null) {
        const serverNow = Math.floor(Date.now() / 1000) + serverSkewSeconds.value
        const diff = until - serverNow
        return diff > 0 ? diff : 0
    }
    return snapshot.payout_remaining_seconds ?? 0
})

async function loadSnapshot() {
    loading.value = true
    try {
        const res = await createAxios({ url: '/admin/game.Live/snapshot', method: 'get', showCodeMessage: false })
        if (res.code === 1 && res.data) {
            mergeLiveSnapshot(res.data as anyObj)
            const dmax = res.data.draw_number_max ?? 36
            if (manualNumber.value === null || manualNumber.value < 1 || manualNumber.value > dmax) manualNumber.value = 1
        }
    } finally {
        loading.value = false
    }
}

async function onRuntimeSwitch(val: boolean | string | number): void {
    const on = val === true || val === 'true' || val === 1
    runtimeSwitchLoading.value = true
    try {
        const res = await createAxios({
            url: '/admin/game.Live/runtime',
            method: 'post',
            data: { enabled: on ? 1 : 0 },
            showSuccessMessage: true,
        })
        if (res.code === 1 && res.data) {
            mergeLiveSnapshot(res.data as anyObj)
        } else {
            await loadSnapshot()
        }
    } catch {
        await loadSnapshot()
    } finally {
        runtimeSwitchLoading.value = false
    }
}

function openVoidDialog(): void {
    voidReason.value = ''
    voidDialogVisible.value = true
}

async function submitVoidPeriod(): Promise<void> {
    const reason = voidReason.value.trim()
    if (reason.length < 2) {
        ElMessage.warning(t('game.live.void_reason_too_short'))
        return
    }
    if (!snapshot.record) {
        return
    }
    voidSubmitting.value = true
    try {
        const res = await createAxios({
            url: '/admin/game.Live/voidPeriod',
            method: 'post',
            data: {
                record_id: snapshot.record.id,
                void_reason: reason,
            },
            showSuccessMessage: true,
        })
        if (res.code === 1 && res.data) {
            mergeLiveSnapshot(res.data as anyObj)
        }
        voidDialogVisible.value = false
    } finally {
        voidSubmitting.value = false
    }
}

async function initPush() {
    const cfgRes = await createAxios({ url: '/admin/game.Live/pushConfig', method: 'get', showCodeMessage: false })
    if (cfgRes.code !== 1 || !cfgRes.data) {
        pushConnected.value = false
        return
    }
    const { url, app_key, channel, event } = cfgRes.data

    try {
        await loadPushJs()
    } catch {
        pushConnected.value = false
        startPolling()
        return
    }
    const PushCtor = (window as any).Push
    if (!PushCtor) {
        pushConnected.value = false
        startPolling()
        return
    }
    try {
        pushClient = new PushCtor({ url, app_key })
        pushChannel = pushClient.subscribe(channel)
        pushConnected.value = false
        pushChannel.on('pusher:subscription_succeeded', () => {
            pushConnected.value = true
        })
        startPushWatchdog()
        stopPolling()
        pushChannel.on(event, (payload: anyObj) => {
            pushConnected.value = true
            mergeLiveSnapshot(payload)
        })
    } catch {
        pushConnected.value = false
        startPolling()
    }
}

async function loadPushJs() {
    if ((window as any).Push) {
        return
    }
    await new Promise<void>((resolve, reject) => {
        const script = document.createElement('script')
        script.src = getPushScriptUrl()
        script.onload = () => resolve()
        script.onerror = () => reject(new Error('load push.js failed'))
        document.head.appendChild(script)
    })
}

async function onCalculate() {
    if (!snapshot.record) return
    calcLoading.value = true
    try {
        const res = await createAxios({
            url: '/admin/game.Live/calculate',
            method: 'post',
            data: {
                record_id: snapshot.record.id,
                manual_number: manualNumber.value,
            },
            showSuccessMessage: true,
        })
        if (res.code === 1 && res.data) {
            snapshot.candidate_numbers = res.data.candidate_numbers || []
            snapshot.ai_default_number = res.data.ai_default_number ?? null
            calcResultNumber.value = res.data.final_number ?? null
            calcEstimatedLoss.value = String(res.data.final_estimated_loss ?? '0.00')
        }
    } finally {
        calcLoading.value = false
    }
}

async function onDraw() {
    if (!snapshot.record) return
    drawLoading.value = true
    try {
        await createAxios({
            url: '/admin/game.Live/draw',
            method: 'post',
            data: {
                record_id: snapshot.record.id,
                manual_number: manualNumber.value,
            },
            showSuccessMessage: true,
        })
        await loadSnapshot()
    } finally {
        drawLoading.value = false
    }
}

const countdownParts = computed(() => {
    const bet = snapshot.bet_remaining_seconds ?? 0
    const draw = snapshot.remaining_seconds ?? 0
    let payout = t('game.live.payout_na')
    if (snapshot.is_payout_phase && payoutRemainingLive.value !== null) {
        payout = `${payoutRemainingLive.value}s`
    }
    return { bet, draw, payout }
})

onMounted(async () => {
    clockTimer = window.setInterval(() => {
        clockTick.value++
    }, 1000)
    await loadSnapshot()
    try {
        await initPush()
    } catch {
        pushConnected.value = false
        startPolling()
    }
})

onUnmounted(() => {
    try {
        if (pushClient && typeof pushClient.disconnect === 'function') {
            pushClient.disconnect()
        }
    } catch {
        // ignore
    }
    stopPolling()
    stopPushWatchdog()
    if (clockTimer !== null) {
        window.clearInterval(clockTimer)
        clockTimer = null
    }
})

function startPolling() {
    if (pollTimer !== null) {
        return
    }
    pollTimer = window.setInterval(() => {
        void loadSnapshot()
    }, 2000)
}

function stopPolling() {
    if (pollTimer !== null) {
        window.clearInterval(pollTimer)
        pollTimer = null
    }
}

function startPushWatchdog() {
    if (pushWatchdogTimer !== null) {
        return
    }
    pushWatchdogTimer = window.setInterval(() => {
        if (!pushClient || !pushClient.connection) {
            pushConnected.value = false
            return
        }
        const state = String(pushClient.connection.state || '')
        // push.js：onOpen 后长期为 connecting，必须收到 pusher:connection_established 才变 connected。
        // 把 connecting 当「已连接」会误报绿条（Nginx 只完成握手、帧未透传时 DevTools 消息列表为空）。
        if (state === 'disconnected') {
            pushConnected.value = false
        }
    }, 1000)
}

function stopPushWatchdog() {
    if (pushWatchdogTimer !== null) {
        window.clearInterval(pushWatchdogTimer)
        pushWatchdogTimer = null
    }
}
</script>

<style scoped lang="scss">
.mb-12 {
    margin-bottom: 12px;
}

.live-top-toolbar {
    margin-bottom: 16px;
    padding: 12px 16px;
    border-radius: 8px;
    border: 1px solid var(--el-border-color-lighter);
    background: var(--el-bg-color-overlay);
}

.live-top-toolbar__row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px 20px;
}

.live-top-toolbar__label {
    font-size: 14px;
    font-weight: 600;
    color: var(--el-text-color-primary);
}

.live-top-toolbar__hint {
    font-size: 13px;
    color: var(--el-text-color-secondary);
    flex: 1 1 220px;
    line-height: 1.45;
}

.live-control-card {
    :deep(.el-card__body) {
        padding-top: 8px;
    }
}

.live-control-layout {
    display: flex;
    flex-wrap: wrap;
    gap: 20px 24px;
    align-items: flex-start;
}

.live-control-main {
    flex: 1 1 320px;
    min-width: 0;
}

.live-desc {
    margin-bottom: 16px;
    :deep(.el-descriptions__label) {
        width: 120px;
        font-weight: 500;
    }
}

.period-no {
    display: inline-block;
    max-width: 100%;
    word-break: break-all;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    font-size: 13px;
    line-height: 1.5;
    color: var(--el-text-color-primary);
}

.num-em {
    font-size: 16px;
    font-weight: 600;
    color: var(--el-color-primary);
}

.countdown-block {
    margin-bottom: 14px;
}

.countdown-block__title {
    font-size: 13px;
    color: var(--el-text-color-secondary);
    margin-bottom: 8px;
}

.countdown-maintenance {
    border: 1px solid var(--el-border-color-lighter);
    border-radius: 8px;
    padding: 16px 12px;
    text-align: center;
    font-size: 16px;
    font-weight: 600;
    color: var(--el-color-warning-dark-2);
    background: var(--el-color-warning-light-9);
}

.countdown-cards {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 10px;
}

.cd-card {
    border: 1px solid var(--el-border-color-lighter);
    border-radius: 8px;
    padding: 10px 12px;
    background: var(--el-fill-color-blank);
    display: flex;
    flex-direction: column;
    gap: 4px;
    align-items: center;
    text-align: center;
    min-width: 0;
}

/* 移动端保持同一排，压缩间距与字号以省纵向空间 */
@media (max-width: 768px) {
    .countdown-block {
        margin-bottom: 10px;
    }

    .countdown-block__title {
        margin-bottom: 6px;
    }

    .countdown-cards {
        gap: 6px;
    }

    .cd-card {
        padding: 6px 4px;
        border-radius: 6px;
        gap: 2px;
        min-width: 0;
    }

    .cd-card__label {
        font-size: 11px;
        line-height: 1.2;
        padding: 0 2px;
    }

    .cd-card__val {
        font-size: 15px;
    }
}

.cd-card.is-active {
    border-color: var(--el-color-warning-light-5);
    background: var(--el-color-warning-light-9);
}

.cd-card__label {
    font-size: 12px;
    color: var(--el-text-color-secondary);
    line-height: 1.3;
}

.cd-card__val {
    font-size: 18px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--el-text-color-primary);
}

.calc-result-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 16px 24px;
    padding: 10px 14px;
    border-radius: 8px;
    background: var(--el-fill-color-light);
    border: 1px solid var(--el-border-color-lighter);
}

.calc-result-bar__item {
    display: inline-flex;
    align-items: baseline;
    gap: 8px;
}

.calc-result-bar__k {
    font-size: 13px;
    color: var(--el-text-color-secondary);
}

.calc-result-bar__v {
    font-size: 14px;
    font-weight: 500;
}

.calc-result-bar__v.mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}

.live-control-aside {
    flex: 0 0 240px;
    padding-left: 20px;
    border-left: 1px solid var(--el-border-color-lighter);
    display: flex;
    flex-direction: column;
    gap: 12px;

    &.is-locked {
        opacity: 0.9;
        filter: grayscale(0.08);
    }
}

@media (max-width: 992px) {
    .live-control-aside {
        flex: 1 1 100%;
        padding-left: 0;
        border-left: none;
        padding-top: 16px;
        border-top: 1px solid var(--el-border-color-lighter);
    }
}

.aside-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--el-text-color-regular);
}

.aside-void-btn {
    width: 100%;
    margin-bottom: 12px;
}

.aside-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.aside-field__label {
    font-size: 12px;
    color: var(--el-text-color-secondary);
}

.aside-field__input {
    width: 100%;
}

.aside-btns {
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    gap: 8px;
    align-items: stretch;
}

.aside-btns .el-button {
    flex: 1;
    min-width: 0;
    margin-left: 0;
    padding-left: 8px;
    padding-right: 8px;
}

.aside-btns .el-button :deep(span) {
    white-space: normal;
    line-height: 1.25;
    text-align: center;
}
</style>

<style lang="scss">
/* Dialog teleport 到 body，独立块以便样式命中 */
.live-void-dialog.el-dialog {
    display: flex;
    flex-direction: column;
    max-height: min(92vh, 720px);
    margin: auto;
}

.live-void-dialog .el-dialog__header {
    flex-shrink: 0;
    padding: 16px 16px 10px;
}

.live-void-dialog .el-dialog__body {
    flex: 1;
    min-height: 0;
    overflow-x: hidden;
    overflow-y: auto;
    padding: 8px 16px 12px;
    -webkit-overflow-scrolling: touch;
}

.live-void-dialog .el-dialog__footer {
    flex-shrink: 0;
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
}

.live-void-footer {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 10px;
    width: 100%;
}

.live-void-form .el-form-item {
    margin-bottom: 0;
}

@media (max-width: 768px) {
    .live-void-dialog.el-dialog {
        width: calc(100vw - 24px) !important;
        max-width: 520px;
        max-height: 90vh;
    }

    .live-void-dialog .el-dialog__title {
        font-size: 16px;
        line-height: 1.4;
        word-break: break-word;
    }

    .live-void-footer {
        justify-content: stretch;
    }

    .live-void-footer .el-button {
        flex: 1;
        min-width: 0;
    }
}
</style>
