<script setup lang="ts">
/**
 * 系统公告管理
 *
 * 关键点：
 *  - 内容使用 wangeditor v4 富文本（依赖已装在 admin-snack/package.json）
 *  - 置顶 / 上下线 / 有效期 都由后端校验（生效时间必须早于失效时间）
 *  - 删除二次确认；状态切换走 row.status 直接 toggle，无需确认
 */
import { ref, reactive, onMounted, onBeforeUnmount, nextTick, shallowRef } from 'vue'
import {
  Plus,
  Refresh,
  Edit,
  Delete,
  View,
  Top,
  Bottom,
  Search
} from '@element-plus/icons-vue'
import {
  ElMessage,
  ElMessageBox,
  type FormInstance,
  type FormRules
} from 'element-plus'
import E from 'wangeditor'
import {
  getNoticePageApi,
  getNoticeDetailApi,
  createNoticeApi,
  updateNoticeApi,
  deleteNoticeApi,
  updateNoticeStatusApi,
  updateNoticeTopApi
} from '@/api/notice'
import type { NoticeItem, NoticeSavePayload } from '@/api/notice'
import { formatDate } from '@/utils/date'
import SearchBar from '@/components/common/SearchBar.vue'

// ============================================================
// 列表
// ============================================================
const loading = ref(false)
const list = ref<NoticeItem[]>([])
const total = ref(0)
const query = reactive({
  current: 1,
  size: 10,
  keyword: '',
  type: undefined as 0 | 1 | 2 | undefined,
  status: undefined as 0 | 1 | undefined
})

async function fetchData() {
  loading.value = true
  try {
    const res = await getNoticePageApi(query)
    list.value = res.records
    total.value = res.total
  } finally {
    loading.value = false
  }
}

function handleReset() {
  query.keyword = ''
  query.type = undefined
  query.status = undefined
  query.current = 1
  fetchData()
}

// ============================================================
// 启停 / 置顶 / 删除
// ============================================================
async function handleToggleStatus(row: any) {
  try {
    await updateNoticeStatusApi(row.id, row.status === 1 ? 0 : 1)
    ElMessage.success(row.status === 1 ? '已下线' : '已上线')
    fetchData()
  } catch {
    /* 拦截器已提示 */
  }
}

async function handleToggleTop(row: any) {
  try {
    await updateNoticeTopApi(row.id, row.isTop === 1 ? 0 : 1)
    ElMessage.success(row.isTop === 1 ? '已取消置顶' : '已置顶')
    fetchData()
  } catch {
    /* 拦截器已提示 */
  }
}

async function handleDelete(row: any) {
  await ElMessageBox.confirm(
    `确定要删除公告【${row.title}】吗？该操作不可恢复。`,
    '提示',
    { type: 'warning' }
  )
  try {
    await deleteNoticeApi(row.id)
    ElMessage.success('删除成功')
    fetchData()
  } catch {
    /* 拦截器已提示 */
  }
}

// ============================================================
// 新增 / 编辑弹窗（含 wangeditor）
// ============================================================
const dialogVisible = ref(false)
const dialogMode = ref<'create' | 'edit'>('create')
const dialogTitle = computed(() => (dialogMode.value === 'edit' ? '编辑公告' : '发布公告'))
const formRef = ref<FormInstance>()
const submitting = ref(false)

interface FormState {
  id?: number
  title: string
  type: 0 | 1 | 2
  isTop: 0 | 1
  status: 0 | 1
  content: string
  startTime: string
  endTime: string
}
const form = reactive<FormState>({
  title: '',
  type: 0,
  isTop: 0,
  status: 1,
  content: '',
  startTime: '',
  endTime: ''
})

const rules: FormRules<FormState> = {
  title: [
    { required: true, message: '请输入公告标题', trigger: 'blur' },
    { max: 200, message: '标题最长 200 字', trigger: 'blur' }
  ],
  type: [{ required: true, message: '请选择公告类型', trigger: 'change' }],
  content: [
    {
      validator: (_r, val, cb) => {
        // wangeditor 的内容是 HTML；空内容时 <p><br></p> 是合法空文档
        const stripped = (val || '').replace(/<[^>]+>/g, '').replace(/&nbsp;/g, '').trim()
        if (!stripped) return cb(new Error('请输入公告内容'))
        cb()
      },
      trigger: 'blur'
    }
  ]
}

// ---- wangeditor ----
const editorRef = ref<HTMLElement | null>(null)
// shallowRef 避免 Vue 把 wangeditor 实例深度代理（实例里挂大量 DOM 引用）
const editorInstance = shallowRef<any>(null)

function destroyEditor() {
  if (editorInstance.value) {
    try {
      editorInstance.value.destroy()
    } catch {
      /* ignore */
    }
    editorInstance.value = null
  }
}

function buildEditor() {
  if (!editorRef.value) return null
  const editor = new E(editorRef.value)
  // 菜单配置
  editor.config.menus = [
    'head',
    'bold',
    'fontSize',
    'fontName',
    'italic',
    'underline',
    'strikeThrough',
    'foreColor',
    'backColor',
    'link',
    'list',
    'justify',
    'quote',
    'emoticon',
    'image',
    'table',
    'code',
    'splitLine',
    'undo',
    'redo'
  ]
  editor.config.placeholder = '请输入公告内容…'
  editor.config.uploadImgMaxSize = 2 * 1024 * 1024
  // 上传图片走已有的 /api/admin/file 上传接口
  editor.config.uploadImgServer = '/api/admin/file/upload'
  editor.config.uploadFileName = 'file'
  // 上传图片结果解析：后端返回 Result<{ url: string }>
  editor.config.uploadImgHooks = {
    customInsert: (insertImgFn: (url: string) => void, result: any) => {
      const data = result?.data
      const url = typeof data === 'string' ? data : data?.url
      if (url) insertImgFn(url)
    }
  }
  // 内容变化时回写到 form.content
  editor.config.onchange = (newHtml: string) => {
    form.content = newHtml
  }
  return editor
}

async function openCreate() {
  dialogMode.value = 'create'
  await nextTick()
  formRef.value?.resetFields()
  Object.assign(form, {
    id: undefined,
    title: '',
    type: 0,
    isTop: 0,
    status: 1,
    content: '',
    startTime: '',
    endTime: ''
  })
  dialogVisible.value = true
  // 等对话框 DOM 完成、editorRef 实际挂载后再创建编辑器
  await nextTick()
  if (!editorInstance.value) {
    const editor = buildEditor()
    if (editor) {
      editor.create()
      editor.txt.html('')
      editorInstance.value = editor
    }
  } else {
    editorInstance.value.txt.html('')
  }
}

async function openEdit(row: any) {
  dialogMode.value = 'edit'
  await nextTick()
  formRef.value?.resetFields()
  let detail: NoticeItem | null = null
  try {
    detail = await getNoticeDetailApi(row.id)
  } catch {
    detail = row
  }
  Object.assign(form, {
    id: detail!.id,
    title: detail!.title,
    type: (detail!.type ?? 0) as 0 | 1 | 2,
    isTop: (detail!.isTop ?? 0) as 0 | 1,
    status: (detail!.status ?? 1) as 0 | 1,
    content: detail!.content ?? '',
    startTime: detail!.startTime ?? '',
    endTime: detail!.endTime ?? ''
  })
  dialogVisible.value = true
  await nextTick()
  if (!editorInstance.value) {
    const editor = buildEditor()
    if (editor) {
      editor.create()
      editorInstance.value = editor
    }
  }
  // 把后端 HTML 写进编辑器
  if (editorInstance.value) {
    editorInstance.value.txt.html(detail!.content ?? '')
  }
}

function closeDialog() {
  dialogVisible.value = false
  // 编辑器实例保留以便下次打开复用，避免重复创建导致内存泄漏
}

async function handleSubmit() {
  if (!formRef.value) return
  // 同步编辑器最新内容（onchange 一般已同步，这里兜底）
  if (editorInstance.value) {
    form.content = editorInstance.value.txt.html()
  }
  await formRef.value.validate(async (valid) => {
    if (!valid) return
    submitting.value = true
    try {
      const payload: NoticeSavePayload = {
        id: form.id,
        title: form.title,
        content: form.content,
        type: form.type,
        isTop: form.isTop,
        status: form.status,
        startTime: form.startTime || undefined,
        endTime: form.endTime || undefined
      }
      if (dialogMode.value === 'create') {
        await createNoticeApi(payload)
        ElMessage.success('发布成功')
      } else {
        await updateNoticeApi(payload)
        ElMessage.success('更新成功')
      }
      dialogVisible.value = false
      await fetchData()
    } catch {
      /* 拦截器已提示 */
    } finally {
      submitting.value = false
    }
  })
}

// ============================================================
// 详情预览（只读）
// ============================================================
const previewVisible = ref(false)
const previewItem = ref<NoticeItem | null>(null)
function openPreview(row: any) {
  previewItem.value = row
  previewVisible.value = true
}

onMounted(fetchData)
onBeforeUnmount(destroyEditor)

const TYPE_TAG_TYPE: Record<0 | 1 | 2, '' | 'danger' | 'warning'> = {
  0: '',
  1: 'danger',
  2: 'warning'
}
const TYPE_LABELS = { 0: '普通', 1: '重要', 2: '活动' } as const
</script>

<template>
  <div class="page-container">
    <div class="page-header">
      <div>
        <h2>系统公告</h2>
        <p class="page-desc">发布系统通知、营销公告，支持置顶、定时上下线</p>
      </div>
      <el-button type="primary" :icon="Plus" @click="openCreate">发布公告</el-button>
    </div>

    <el-card shadow="never" class="table-card">
      <SearchBar show-divider>
        <el-input
          v-model="query.keyword"
          placeholder="标题关键字"
          :prefix-icon="Search"
          clearable
          style="width: 200px"
          @keyup.enter="fetchData"
        />
        <el-select v-model="query.type" placeholder="类型" clearable style="width: 120px">
          <el-option label="普通" :value="0" />
          <el-option label="重要" :value="1" />
          <el-option label="活动" :value="2" />
        </el-select>
        <el-select v-model="query.status" placeholder="状态" clearable style="width: 120px">
          <el-option label="已上线" :value="1" />
          <el-option label="已下线" :value="0" />
        </el-select>
        <el-button type="primary" :icon="Search" @click="fetchData">搜索</el-button>
        <el-button :icon="Refresh" @click="handleReset">重置</el-button>
      </SearchBar>

      <el-table v-loading="loading" :data="list" stripe border>
        <el-table-column type="index" label="#" width="60" align="center" />
        <el-table-column label="公告标题" min-width="280">
          <template #default="{ row }">
            <div class="title-cell">
              <el-tag v-if="row.isTop === 1" type="danger" size="small" effect="dark">
                <el-icon><Top /></el-icon> 置顶
              </el-tag>
              <el-tag v-else-if="row.type === 1" type="danger" size="small">重要</el-tag>
              <el-tag v-else-if="row.type === 2" type="warning" size="small">活动</el-tag>
              <span class="title-text">{{ row.title }}</span>
            </div>
          </template>
        </el-table-column>
        <el-table-column label="类型" width="80" align="center">
          <template #default="{ row }">
            <el-tag :type="TYPE_TAG_TYPE[row.type as 0|1|2] as any" size="small">
              {{ TYPE_LABELS[row.type as 0|1|2] }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="浏览量" width="100" align="center">
          <template #default="{ row }">{{ row.viewCount ?? 0 }}</template>
        </el-table-column>
        <el-table-column label="有效期" width="240">
          <template #default="{ row }">
            <div v-if="row.startTime || row.endTime" class="time-cell">
              {{ row.startTime ? formatDate(row.startTime, 'YYYY-MM-DD HH:mm') : '不限' }}
              <span class="dash">~</span>
              {{ row.endTime ? formatDate(row.endTime, 'YYYY-MM-DD HH:mm') : '不限' }}
            </div>
            <div v-else class="muted">不限</div>
          </template>
        </el-table-column>
        <el-table-column label="状态" width="100" align="center">
          <template #default="{ row }">
            <span :class="['status-dot', row.status === 1 ? 'success' : 'info']">
              {{ row.status === 1 ? '已上线' : '已下线' }}
            </span>
          </template>
        </el-table-column>
        <el-table-column label="创建时间" width="170">
          <template #default="{ row }">{{ formatDate(row.createTime) }}</template>
        </el-table-column>
        <el-table-column label="操作" width="360" align="center" fixed="right">
          <template #default="{ row }">
            <el-button link type="primary" size="small" :icon="View" @click="openPreview(row)">
              预览
            </el-button>
            <el-button link type="primary" size="small" :icon="Edit" @click="openEdit(row)">
              编辑
            </el-button>
            <el-button
              link
              :type="row.isTop === 1 ? 'info' : 'warning'"
              size="small"
              :icon="row.isTop === 1 ? Bottom : Top"
              @click="handleToggleTop(row)"
            >
              {{ row.isTop === 1 ? '取消置顶' : '置顶' }}
            </el-button>
            <el-button
              link
              :type="row.status === 1 ? 'warning' : 'success'"
              size="small"
              @click="handleToggleStatus(row)"
            >
              {{ row.status === 1 ? '下线' : '上线' }}
            </el-button>
            <el-button link type="danger" size="small" :icon="Delete" @click="handleDelete(row)">
              删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>

      <div class="pagination-wrapper">
        <el-pagination
          v-model:current-page="query.current"
          v-model:page-size="query.size"
          :total="total"
          :page-sizes="[10, 20, 50]"
          layout="total, sizes, prev, pager, next, jumper"
          background
          @current-change="fetchData"
          @size-change="fetchData"
        />
      </div>
    </el-card>

    <!-- 发布 / 编辑弹窗 -->
    <el-dialog
      v-model="dialogVisible"
      :title="dialogTitle"
      width="820px"
      :close-on-click-modal="false"
      destroy-on-close
      @close="closeDialog"
    >
      <el-form ref="formRef" :model="form" :rules="rules" label-width="90px">
        <el-form-item label="公告标题" prop="title">
          <el-input
            v-model="form.title"
            placeholder="请输入公告标题（最长 200 字）"
            maxlength="200"
            show-word-limit
          />
        </el-form-item>
        <el-form-item label="公告类型" prop="type">
          <el-radio-group v-model="form.type">
            <el-radio :value="0">普通</el-radio>
            <el-radio :value="1">重要</el-radio>
            <el-radio :value="2">活动</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="显示设置">
          <el-space>
            <el-switch
              v-model="form.isTop"
              :active-value="1"
              :inactive-value="0"
              active-text="置顶"
              inactive-text="不置顶"
            />
            <el-switch
              v-model="form.status"
              :active-value="1"
              :inactive-value="0"
              active-text="上线"
              inactive-text="下线"
            />
          </el-space>
        </el-form-item>
        <el-form-item label="有效期">
          <el-date-picker
            v-model="form.startTime"
            type="datetime"
            placeholder="生效时间（不填 = 立即生效）"
            value-format="YYYY-MM-DD HH:mm:ss"
            style="width: 45%"
          />
          <span style="margin: 0 8px">至</span>
          <el-date-picker
            v-model="form.endTime"
            type="datetime"
            placeholder="失效时间（不填 = 长期有效）"
            value-format="YYYY-MM-DD HH:mm:ss"
            style="width: 45%"
          />
        </el-form-item>
        <el-form-item label="公告内容" prop="content">
          <div ref="editorRef" class="editor-container" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" :loading="submitting" @click="handleSubmit">发布</el-button>
      </template>
    </el-dialog>

    <!-- 预览弹窗 -->
    <el-dialog
      v-model="previewVisible"
      :title="`公告预览 — ${previewItem?.title ?? ''}`"
      width="720px"
      destroy-on-close
    >
      <template v-if="previewItem">
        <div class="preview-meta">
          <el-tag v-if="previewItem.isTop === 1" type="danger" size="small" effect="dark">
            <el-icon><Top /></el-icon> 置顶
          </el-tag>
          <el-tag :type="TYPE_TAG_TYPE[previewItem.type as 0|1|2] as any" size="small">
            {{ TYPE_LABELS[previewItem.type as 0|1|2] }}
          </el-tag>
          <span class="muted">
            {{ previewItem.createTime ? formatDate(previewItem.createTime) : '' }}
          </span>
          <span class="muted">浏览 {{ previewItem.viewCount ?? 0 }}</span>
        </div>
        <h2 class="preview-title">{{ previewItem.title }}</h2>
        <div class="preview-content" v-html="previewItem.content" />
      </template>
    </el-dialog>
  </div>
</template>

<style lang="scss" scoped>
.title-cell {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.title-text {
  font-weight: 500;
}
.time-cell {
  font-size: 12px;
  color: $color-text-secondary;
  .dash {
    margin: 0 4px;
  }
}
.status-dot {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 10px;
  font-size: 12px;
  &.success {
    background: rgba(16, 185, 129, 0.1);
    color: $color-success;
  }
  &.info {
    background: rgba(156, 163, 175, 0.15);
    color: $color-text-secondary;
  }
}
.muted {
  color: $color-text-secondary;
  font-size: 12px;
}
.pagination-wrapper {
  display: flex;
  justify-content: flex-end;
  padding: 16px 20px;
}

.editor-container {
  width: 100%;
}

.preview-meta {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
}
.preview-title {
  font-size: 20px;
  font-weight: 600;
  margin: 0 0 16px;
  color: $color-text-primary;
}
.preview-content {
  font-size: 14px;
  line-height: 1.7;
  color: $color-text-primary;

  :deep(img) {
    max-width: 100%;
  }
  :deep(table) {
    border-collapse: collapse;
    width: 100%;
    th, td {
      border: 1px solid $color-border-light;
      padding: 6px 10px;
    }
  }
  :deep(pre), :deep(code) {
    background: #f6f8fa;
    border-radius: 4px;
    padding: 2px 6px;
  }
}
</style>
