<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import {
  getCourseList,
  createCourse,
  updateCourse,
  deleteCourse,
  cancelCourse,
  getCourseTypes,
  fetchCourseStatuses,
  type GroupCourse,
  type CourseStatusItem,
} from '@/api/groupCourse'
import { getCoachList, type CoachInfo } from '@/api/coach'
import { verifyPassword } from '@/api/auth'
import AppLayout from '@/components/AppLayout.vue'
import { useToast } from '@/composables/useToast'
import { useAuthStore } from '@/stores/auth'
import { uploadCourseImage, getProxyUrl } from '@/utils/ossUpload'

const toast = useToast()
const authStore = useAuthStore()

// --- 数据 ---
const allCourses = ref<GroupCourse[]>([])
const typeMap = ref<Record<number, string>>({})
const loading = ref(false)
const coaches = ref<CoachInfo[]>([])

// --- 分类筛选 ---
const filterStatus = ref('all')
const keyword = ref('')

/** 默认状态映射（API 返回前或失败时的兜底，与后端 CourseStatus 枚举严格一致）*/
const DEFAULT_STATUS_LIST: CourseStatusItem[] = [
  { value: 0, dbValue: '0', label: '正常', cssClass: 'green' },
  { value: 1, dbValue: '1', label: '已取消', cssClass: 'orange' },
  { value: 2, dbValue: '2', label: '已结束', cssClass: 'gray' },
  { value: 3, dbValue: '3', label: '进行中', cssClass: 'blue' },
  { value: 4, dbValue: '4', label: '超时', cssClass: 'red' },
]

const statusList = ref<CourseStatusItem[]>([...DEFAULT_STATUS_LIST])

const filterTabs = computed(() => {
  const tabs: { key: string; label: string }[] = [{ key: 'all', label: '全部' }]
  for (const s of statusList.value) {
    tabs.push({ key: s.dbValue, label: s.label })
  }
  tabs.push({ key: 'deleted', label: '已删除' })
  return tabs
})

// --- 排序 ---
type SortMode = 'id_asc' | 'id_desc' | 'updated_desc' | 'start_asc'
const sortMode = ref<SortMode>('id_asc')

const sortOptions: { value: SortMode; label: string }[] = [
  { value: 'id_asc', label: 'ID 从小到大' },
  { value: 'id_desc', label: 'ID 从大到小' },
  { value: 'updated_desc', label: '最近修改' },
  { value: 'start_asc', label: '即将开始' },
]

// --- 分页 ---
const currentPage = ref(0)
const pageSize = ref(10)

// --- 新建/编辑弹窗 ---
const showModal = ref(false)
const isEdit = ref(false)
const editId = ref<number | null>(null)
const saveLoading = ref(false)

// --- 取消模式 ---
const cancelMode = ref(false)
const selectedCancelIds = ref<Set<number>>(new Set())
const selectAll = ref(false)
const cancelLoading = ref(false)

// --- 密码验证弹窗 ---
const showPasswordModal = ref(false)
const passwordAction = ref<'cancel' | 'delete'>('cancel')
const pendingDeleteId = ref<number | null>(null)
const adminPassword = ref('')
const passwordError = ref('')
const verifying = ref(false)

const form = ref({
  courseName: '',
  coachId: undefined as number | undefined,
  courseType: undefined as number | undefined,
  startTime: '',
  endTime: '',
  maxMembers: 20,
  location: '',
  description: '',
  pointCardAmount: 1,
  storedValueAmount: 0,
})

// --- 日期/时间拆分字段 ---
const startDate = ref('')
const startTimeStr = ref('')
const endTimeStr = ref('')

function parseDateFromISO(iso: string): string {
  if (!iso) return ''
  return iso.substring(0, 10)
}

function parseTimeFromISO(iso: string): string {
  if (!iso) return ''
  return iso.substring(11, 16)
}

function combineDateTime(date: string, time: string): string {
  if (!date || !time) return ''
  return `${date}T${time}:00`
}

function updateStartDateTime() {
  form.value.startTime = combineDateTime(startDate.value, startTimeStr.value)
}

function updateEndDateTime() {
  form.value.endTime = combineDateTime(startDate.value, endTimeStr.value)
}

function onStartDateChange() {
  updateStartDateTime()
  updateEndDateTime()
}

function onStartTimeChange() { updateStartDateTime() }
function onEndTimeChange() { updateEndDateTime() }

// --- 图片上传 ---
const coverImageFile = ref<File | null>(null)
const coverImagePreview = ref<string>('')
const coverImageUrl = ref<string>('')
const uploading = ref(false)

const courseStatusMap = computed(() => {
  const map: Record<string, string> = {}
  for (const s of statusList.value) {
    map[s.dbValue] = s.label
  }
  return map
})

const courseStatusClassMap = computed(() => {
  const map: Record<string, string> = {}
  for (const s of statusList.value) {
    map[s.dbValue] = s.cssClass
  }
  return map
})

function getStatusLabel(status: number | null, deletedAt?: string | null): string {
  if (deletedAt != null && deletedAt !== '') return '已删除'
  return courseStatusMap.value[String(status ?? '')] || '未知'
}

function getStatusClass(status: number | null, deletedAt?: string | null): string {
  if (deletedAt != null && deletedAt !== '') return 'gray'
  return courseStatusClassMap.value[String(status ?? '')] || ''
}

// --- 过滤 + 分页 ---
const filteredCourses = computed(() => {
  let list = allCourses.value

  // 分类筛选
  if (filterStatus.value === 'deleted') {
    list = list.filter((c) => c.deletedAt != null && c.deletedAt !== '')
  } else if (filterStatus.value !== 'all') {
    list = list.filter(
      (c) =>
        (c.deletedAt == null || c.deletedAt === '') &&
        Number(c.status) === Number(filterStatus.value),
    )
  }

  // 关键词搜索
  if (keyword.value.trim()) {
    const kw = keyword.value.trim().toLowerCase()
    list = list.filter(
      (c) =>
        c.courseName.toLowerCase().includes(kw) ||
        String(c.id).includes(kw) ||
        (c.location && c.location.toLowerCase().includes(kw)) ||
        (typeMap.value[c.courseType]?.toLowerCase().includes(kw)),
    )
  }

  // 排序
  if (sortMode.value === 'id_asc') {
    list.sort((a, b) => a.id - b.id)
  } else if (sortMode.value === 'id_desc') {
    list.sort((a, b) => b.id - a.id)
  } else if (sortMode.value === 'updated_desc') {
    list.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
  } else if (sortMode.value === 'start_asc') {
    list.sort((a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime())
  }

  return list
})

const totalElements = computed(() => filteredCourses.value.length)

const totalPages = computed(() => Math.max(1, Math.ceil(totalElements.value / pageSize.value)))

const pagedCourses = computed(() => {
  const start = currentPage.value * pageSize.value
  return filteredCourses.value.slice(start, start + pageSize.value)
})

// 取消模式下可选择的行
const cancellableCount = computed(() =>
  pagedCourses.value.filter((c) => c.status != null && Number(c.status) === 0 && (c.deletedAt == null || c.deletedAt === '')).length,
)

const hasCancelSelection = computed(() => selectedCancelIds.value.size > 0)

function resetForm() {
  form.value = {
    courseName: '',
    coachId: undefined,
    courseType: undefined,
    startTime: '',
    endTime: '',
    maxMembers: 20,
    location: '',
    description: '',
    pointCardAmount: 1,
    storedValueAmount: 0,
  }
  startDate.value = ''
  startTimeStr.value = ''
  endTimeStr.value = ''
  coverImageFile.value = null
  coverImagePreview.value = ''
  coverImageUrl.value = ''
}

function handleCoverImageChange(e: Event) {
  const input = e.target as HTMLInputElement
  if (input.files && input.files[0]) {
    const file = input.files[0]
    coverImageFile.value = file
    coverImageUrl.value = ''
    const reader = new FileReader()
    reader.onload = (ev) => {
      coverImagePreview.value = ev.target?.result as string
    }
    reader.readAsDataURL(file)
  }
}

function displayCoverUrl(): string {
  const src = coverImagePreview.value
  if (!src) return ''
  // base64 data URL 直接使用，OSS URL 走代理
  if (src.startsWith('data:')) return src
  if (src.startsWith('http://') || src.startsWith('https://')) return getProxyUrl(src)
  return src
}

async function fetchCourses() {
  loading.value = true
  try {
    const list = await getCourseList(true)
    allCourses.value = list
  } catch {
    allCourses.value = []
  } finally {
    loading.value = false
  }
}

async function loadCourseTypes() {
  try {
    const types: any[] = await getCourseTypes()
    const map: Record<number, string> = {}
    for (const t of types) {
      map[t.id] = t.typeName
    }
    typeMap.value = map
  } catch {
    // ignore
  }
}

async function loadCoaches() {
  try {
    coaches.value = await getCoachList()
  } catch {
    // ignore
  }
}

function changeFilter(key: FilterKey) {
  filterStatus.value = key
  currentPage.value = 0
  selectedCancelIds.value = new Set()
  selectAll.value = false
}

function handleSearch() {
  currentPage.value = 0
}

function handleSortChange() {
  currentPage.value = 0
}

function handleCreate() {
  isEdit.value = false
  editId.value = null
  resetForm()
  showModal.value = true
}

function handleEdit(course: GroupCourse) {
  isEdit.value = true
  editId.value = course.id
  form.value = {
    courseName: course.courseName,
    coachId: course.coachId,
    courseType: course.courseType,
    startTime: course.startTime,
    endTime: course.endTime,
    maxMembers: course.maxMembers,
    location: course.location,
    description: course.description,
    pointCardAmount: course.pointCardAmount || 1,
    storedValueAmount: course.storedValueAmount || 0,
  }
  startDate.value = parseDateFromISO(course.startTime)
  startTimeStr.value = parseTimeFromISO(course.startTime)
  endTimeStr.value = parseTimeFromISO(course.endTime)
  coverImageFile.value = null
  coverImagePreview.value = course.coverImage || ''
  coverImageUrl.value = course.coverImage || ''
  editingCourse.value = course
  showModal.value = true
}

const editingCourse = ref<GroupCourse | null>(null)

async function handleSave() {
  if (!form.value.startTime) {
    toast.error('请选择开始时间')
    return
  }
  if (!form.value.endTime) {
    toast.error('请选择结束时间')
    return
  }
  if (new Date(form.value.endTime) <= new Date(form.value.startTime)) {
    toast.error('结束时间不得早于或等于开始时间')
    return
  }
  saveLoading.value = true
  try {
    // 先上传图片到OSS
    let coverImage = coverImageUrl.value
    if (coverImageFile.value) {
      uploading.value = true
      try {
        coverImage = await uploadCourseImage(coverImageFile.value)
      } catch (e: any) {
        toast.error('图片上传失败: ' + (e?.message || '未知错误'))
        return
      } finally {
        uploading.value = false
      }
    }

    if (isEdit.value && editId.value) {
      await updateCourse(editId.value, { ...form.value, coverImage })
      toast.success('团课更新成功')
    } else {
      await createCourse({ ...form.value, coverImage })
      toast.success('团课创建成功')
    }
    showModal.value = false
    fetchCourses()
  } catch (e: any) {
    toast.error(e?.response?.data?.message || '操作失败')
  } finally {
    saveLoading.value = false
  }
}

// --- 取消模式 ---
function enterCancelMode() {
  cancelMode.value = true
  selectedCancelIds.value = new Set()
  selectAll.value = false
}

function exitCancelMode() {
  cancelMode.value = false
  selectedCancelIds.value = new Set()
  selectAll.value = false
}

function toggleCancelSelect(id: number) {
  const course = pagedCourses.value.find((c) => c.id === id)
  if (!course) return
  const s = course.status != null ? Number(course.status) : -1
  const deleted = course.deletedAt != null && course.deletedAt !== ''
  if (s !== 0 || deleted) return
  const next = new Set(selectedCancelIds.value)
  if (next.has(id)) {
    next.delete(id)
  } else {
    next.add(id)
  }
  selectedCancelIds.value = next
  selectAll.value = next.size === cancellableCount.value && cancellableCount.value > 0
}

function toggleSelectAll() {
  if (selectAll.value) {
    selectedCancelIds.value = new Set()
    selectAll.value = false
  } else {
    selectedCancelIds.value = new Set(
      pagedCourses.value
        .filter((c) => c.status != null && Number(c.status) === 0 && (c.deletedAt == null || c.deletedAt === ''))
        .map((c) => c.id),
    )
    selectAll.value = true
  }
}

function openCancelPasswordDialog() {
  if (!hasCancelSelection.value) {
    toast.error('请先选择要取消的团课')
    return
  }
  passwordAction.value = 'cancel'
  adminPassword.value = ''
  passwordError.value = ''
  showPasswordModal.value = true
}

function handleDeleteClick(id: number) {
  const course = allCourses.value.find((c) => c.id === id)
  if (!course) return
  const deleted = course.deletedAt != null && course.deletedAt !== ''
  const s = course.status != null ? Number(course.status) : -1
  if (!deleted && s !== 1 && s !== 2) {
    toast.error('只能删除已取消或已结束的团课')
    return
  }
  pendingDeleteId.value = id
  passwordAction.value = 'delete'
  adminPassword.value = ''
  passwordError.value = ''
  showPasswordModal.value = true
}

async function handleVerifyPassword() {
  if (!adminPassword.value) {
    passwordError.value = '请输入管理员密码'
    return
  }
  verifying.value = true
  passwordError.value = ''
  try {
    await verifyPassword({
      username: authStore.username,
      password: adminPassword.value,
    })
    showPasswordModal.value = false
    if (passwordAction.value === 'cancel') {
      await executeBatchCancel()
    } else if (passwordAction.value === 'delete' && pendingDeleteId.value != null) {
      await executeDelete(pendingDeleteId.value)
    }
  } catch {
    passwordError.value = '密码验证失败，请重新输入'
  } finally {
    verifying.value = false
  }
}

async function executeBatchCancel() {
  cancelLoading.value = true
  const ids = Array.from(selectedCancelIds.value)
  let successCount = 0
  let failCount = 0
  let firstError = ''

  for (const id of ids) {
    try {
      await cancelCourse(id)
      successCount++
    } catch (e: any) {
      failCount++
      if (!firstError) {
        firstError =
          e?.response?.data?.message ||
          e?.message ||
          `课程 id=${id} 取消失败`
      }
    }
  }

  exitCancelMode()

  if (failCount === 0) {
    toast.success(`已成功取消 ${successCount} 个团课`)
  } else {
    toast.error(firstError || `成功 ${successCount} 个，失败 ${failCount} 个`)
  }

  cancelLoading.value = false
  fetchCourses()
}

async function executeDelete(id: number) {
  try {
    await deleteCourse(id)
    toast.success('团课删除成功')
    pendingDeleteId.value = null
    fetchCourses()
  } catch (e: any) {
    toast.error(e?.response?.data?.message || '删除失败')
  }
}

function formatTime(t: string) {
  if (!t) return '--'
  return new Date(t).toLocaleString('zh-CN')
}

function formatTimeShort(t: string | null | undefined): string {
  if (!t) return '--'
  const d = new Date(t)
  const pad = (n: number) => String(n).padStart(2, '0')
  return `${d.getMonth() + 1}/${d.getDate()} ${pad(d.getHours())}:${pad(d.getMinutes())}`
}

function isActualTime(c: GroupCourse): boolean {
  return !!(c.actualStartTime || c.actualEndTime)
}

function isAutoEnded(c: GroupCourse): boolean {
  // 自动结课：课程已结束，且实际结束时间与理论结束时间相同（由调度器自动设置）
  return !!(c.status === 2 && c.actualEndTime && c.endTime && c.actualEndTime === c.endTime)
}

function changePage(page: number) {
  if (page >= 0 && page < totalPages.value) {
    currentPage.value = page
  }
}

/**
 * 加载课程状态映射（从后端统一获取，避免硬编码）
 */
async function loadStatuses() {
  try {
    const res = await fetchCourseStatuses()
    if (res.success && Array.isArray(res.data)) {
      statusList.value = res.data
    }
  } catch (e) {
    console.warn('加载课程状态映射失败，使用默认映射', e)
  }
}

onMounted(() => {
  loadStatuses()
  loadCourseTypes()
  loadCoaches()
  fetchCourses()
})
</script>

<template>
  <AppLayout>
    <!-- 工具栏 -->
    <div class="toolbar">
      <div class="search-row">
        <input
          v-model="keyword"
          type="text"
          placeholder="搜索课程名称、类型或地点..."
          class="search-input"
          @keyup.enter="handleSearch"
        />
        <button class="btn btn-outline" @click="handleSearch">
          <i class="fas fa-search"></i> 搜索
        </button>
        <button v-if="!cancelMode && authStore.hasPermission('business:groupCourse:create')" class="btn btn-primary" @click="handleCreate">
          <i class="fas fa-plus"></i> 新建团课
        </button>
        <button
          v-if="!cancelMode && authStore.hasPermission('business:groupCourse:cancel')"
          class="btn"
          style="background:#fff3e0;border-color:#fde68a;color:#b26e00"
          @click="enterCancelMode"
        >
          <i class="fas fa-ban"></i> 取消团课
        </button>

        <!-- 取消模式操作栏 -->
        <template v-if="cancelMode">
          <span v-if="hasCancelSelection" class="selection-info">
            已选择 <strong>{{ selectedCancelIds.size }}</strong> 个
          </span>
          <button
            class="btn"
            style="background:#ef4444;border-color:#ef4444;color:white"
            :disabled="!hasCancelSelection || cancelLoading"
            @click="openCancelPasswordDialog"
          >
            <i v-if="cancelLoading" class="fas fa-spinner fa-spin"></i>
            <i v-else class="fas fa-ban"></i>
            {{ cancelLoading ? '取消中...' : `确认取消 (${selectedCancelIds.size})` }}
          </button>
          <button class="btn btn-outline" @click="exitCancelMode">退出取消模式</button>
        </template>
      </div>

      <!-- 分类标签 -->
      <div class="filter-row">
        <div class="filter-tabs">
          <button
            v-for="tab in filterTabs"
            :key="tab.key"
            class="tab-btn"
            :class="{ active: filterStatus === tab.key }"
            @click="changeFilter(tab.key)"
          >
            {{ tab.label }}
          </button>
        </div>
        <div class="sort-area">
          <label class="sort-label">排序：</label>
          <select v-model="sortMode" class="sort-select" @change="handleSortChange">
            <option v-for="opt in sortOptions" :key="opt.value" :value="opt.value">
              {{ opt.label }}
            </option>
          </select>
        </div>
      </div>
    </div>

    <!-- 表格 -->
    <div class="table-wrapper">
      <div class="table-header">
        <h3>
          {{ filterTabs.find((t) => t.key === filterStatus)?.label }}团课
          ({{ totalElements }} 条)
        </h3>
        <span v-if="cancelMode" class="cancel-hint">选择要取消的团课，仅正常状态可选</span>
      </div>

      <!-- skeleton -->
      <table v-if="loading">
        <thead>
          <tr>
            <th v-if="cancelMode" style="width:40px"></th>
            <th>ID</th><th>课程名称</th><th>封面</th><th>课程类型</th><th>时间</th>
            <th>人数</th><th>地点</th><th>状态</th><th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="i in 5" :key="'sk-' + i">
            <td v-for="j in (cancelMode ? 10 : 9)" :key="j">
              <span class="sk-td skeleton"></span>
            </td>
          </tr>
        </tbody>
      </table>

      <!-- real table -->
      <table v-else>
        <thead>
          <tr>
            <th v-if="cancelMode" style="width:40px">
              <input
                type="checkbox"
                :checked="selectAll"
                @change="toggleSelectAll"
                :disabled="cancellableCount === 0"
              />
            </th>
            <th>ID</th><th>课程名称</th><th>封面</th><th>课程类型</th><th>时间</th>
            <th>人数</th><th>地点</th><th>状态</th><th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr
            v-for="c in pagedCourses"
            :key="c.id"
            :class="{
              'row-disabled': cancelMode && (c.status == null || Number(c.status) !== 0 || (c.deletedAt != null && c.deletedAt !== '')),
              'row-selected': cancelMode && selectedCancelIds.has(c.id),
            }"
            @click="cancelMode ? toggleCancelSelect(c.id) : undefined"
          >
            <td v-if="cancelMode" @click.stop>
              <input
                type="checkbox"
                :checked="selectedCancelIds.has(c.id)"
                :disabled="c.status == null || Number(c.status) !== 0 || (c.deletedAt != null && c.deletedAt !== '')"
                @change="toggleCancelSelect(c.id)"
              />
            </td>
            <td>{{ c.id }}</td>
            <td>{{ c.courseName }}</td>
            <td>
              <img
                v-if="c.coverImage"
                :src="getProxyUrl(c.coverImage)"
                class="table-cover-thumb"
                @error="($event.target as HTMLImageElement).style.display='none'"
              />
              <span v-else class="no-cover">--</span>
            </td>
            <td>{{ typeMap[c.courseType] || c.courseType }}</td>
            <td>
              <span
                class="time-cell"
                :class="{
                  'time-actual': isActualTime(c),
                  'time-theory': !isActualTime(c)
                }"
              >
                <template v-if="isActualTime(c)">
                  {{ formatTimeShort(c.actualStartTime || c.startTime) }} — {{ formatTimeShort(c.actualEndTime || c.endTime) }}
                </template>
                <template v-else>
                  {{ formatTimeShort(c.startTime) }} — {{ formatTimeShort(c.endTime) }}
                </template>
              </span>
            </td>
            <td>{{ c.currentMembers }}/{{ c.maxMembers }}</td>
            <td>{{ c.location }}</td>
            <td>
              <span
                class="status-badge"
                :class="getStatusClass(c.status, c.deletedAt)"
              >{{ getStatusLabel(c.status, c.deletedAt) }}</span>
            </td>
            <td class="actions">
              <button
                v-if="!cancelMode && authStore.hasPermission('business:groupCourse:edit')"
                class="btn-sm"
                :disabled="c.deletedAt != null && c.deletedAt !== ''"
                @click="handleEdit(c)"
              >编辑</button>
              <button
                v-if="!cancelMode && authStore.hasPermission('business:groupCourse:delete')"
                class="btn-sm danger"
                @click="handleDeleteClick(c.id)"
              >删除</button>
            </td>
          </tr>
          <tr v-if="pagedCourses.length === 0">
            <td :colspan="cancelMode ? 10 : 9" class="empty">
              暂无{{ filterTabs.find((t) => t.key === filterStatus)?.label }}团课
            </td>
          </tr>
        </tbody>
      </table>

      <!-- 时间图例 -->
      <div class="time-legend">
        <span class="legend-item">
          <span class="legend-dot theory"></span> 理论时间
        </span>
        <span class="legend-item">
          <span class="legend-dot actual"></span> 实际时间
        </span>
      </div>

      <!-- pagination -->
      <div class="pagination" v-if="totalPages > 1">
        <button :disabled="currentPage === 0" @click="changePage(currentPage - 1)">上一页</button>
        <span>第 {{ currentPage + 1 }} / {{ totalPages }} 页</span>
        <button :disabled="currentPage >= totalPages - 1" @click="changePage(currentPage + 1)">下一页</button>
      </div>
    </div>

    <!-- 新建/编辑弹窗 -->
    <div v-if="showModal" class="modal-overlay" @click.self="showModal = false">
      <div class="modal">
        <h3>{{ isEdit ? '编辑团课' : '新建团课' }}</h3>
        <div class="form-group">
          <label>课程名称 <span class="required">*</span></label>
          <input v-model="form.courseName" placeholder="请输入课程名称" />
        </div>
        <div class="form-row">
          <div class="form-group">
            <label>教练</label>
            <select v-model.number="form.coachId" class="form-select">
              <option :value="undefined" disabled>请选择教练</option>
              <option
                v-for="c in coaches"
                :key="c.id"
                :value="c.id"
              >
                {{ c.nickname }} ({{ c.username }})
              </option>
            </select>
          </div>
          <div class="form-group">
            <label>课程类型</label>
            <select v-model.number="form.courseType" class="form-select">
              <option :value="undefined" disabled>请选择课程类型</option>
              <option v-for="(name, id) in typeMap" :key="id" :value="Number(id)">
                {{ name }}
              </option>
            </select>
          </div>
        </div>
        <div class="form-row">
          <div class="form-group">
            <label>开始日期 <span class="required">*</span></label>
            <input v-model="startDate" type="date" @change="onStartDateChange" />
          </div>
          <div class="form-group">
            <label>开始时间 <span class="required">*</span></label>
            <input v-model="startTimeStr" type="time" @change="onStartTimeChange" />
          </div>
        </div>
        <div class="form-row">
          <div class="form-group">
            <label>结束日期</label>
            <input :value="startDate" type="date" disabled class="input-disabled" />
            <span class="field-hint">与开始日期相同</span>
          </div>
          <div class="form-group">
            <label>结束时间 <span class="required">*</span></label>
            <input v-model="endTimeStr" type="time" @change="onEndTimeChange" />
          </div>
        </div>

        <!-- 实际时间（不可编辑）-->
        <div v-if="isEdit && editingCourse && (editingCourse.actualStartTime || editingCourse.actualEndTime)" class="actual-time-section">
          <h4 class="section-title">
            <i class="fas fa-clock"></i> 实际时间记录
          </h4>
          <div class="actual-time-grid">
            <div class="actual-time-item">
              <span class="at-label">实际开课</span>
              <span class="at-value">{{ editingCourse.actualStartTime ? formatTime(editingCourse.actualStartTime) : '未开课' }}</span>
            </div>
            <div class="actual-time-item">
              <span class="at-label">实际结课</span>
              <span class="at-value">{{ editingCourse.actualEndTime ? formatTime(editingCourse.actualEndTime) : '未结课' }}</span>
            </div>
          </div>
          <p class="at-note" v-if="editingCourse.actualEndTime">
            {{ isAutoEnded(editingCourse) ? '超时自动结课：教练未在课程结束后10分钟内点击结课，系统自动记录为已结课' : '教练手动结课' }}
          </p>
        </div>
        <div class="form-row">
          <div class="form-group">
            <label>最大人数</label>
            <input v-model.number="form.maxMembers" type="number" />
          </div>
          <div class="form-group">
            <label>地点</label>
            <input v-model="form.location" placeholder="上课地点" />
          </div>
        </div>
        <div class="form-row">
          <div class="form-group">
            <label>点卡额度</label>
            <input v-model.number="form.pointCardAmount" type="number" />
          </div>
          <div class="form-group">
            <label>储值卡金额</label>
            <input v-model.number="form.storedValueAmount" type="number" step="0.01" />
          </div>
        </div>
        <div class="form-group">
          <label>课程描述</label>
          <textarea v-model="form.description" placeholder="请输入课程描述" rows="3"></textarea>
        </div>
        <div class="form-group">
          <label>封面图片</label>
          <div class="cover-image-area">
            <div v-if="coverImagePreview" class="cover-preview-wrapper">
              <img :src="displayCoverUrl()" alt="封面预览" class="cover-preview" />
              <button class="btn-sm" @click="coverImageFile = null; coverImagePreview = ''; coverImageUrl = ''">移除</button>
            </div>
            <label class="upload-label" v-if="!coverImagePreview">
              <i class="fas fa-upload"></i> 选择图片
              <input type="file" accept="image/*" hidden @change="handleCoverImageChange" />
            </label>
          </div>
        </div>
        <div class="modal-actions">
          <button class="btn btn-outline" @click="showModal = false">取消</button>
          <button class="btn btn-primary" @click="handleSave" :disabled="saveLoading || uploading">
            <i v-if="saveLoading || uploading" class="fas fa-spinner fa-spin"></i>
            {{ uploading ? '上传中...' : saveLoading ? '保存中...' : '保存' }}
          </button>
        </div>
      </div>
    </div>

    <!-- 密码验证弹窗 -->
    <div v-if="showPasswordModal" class="modal-overlay" @click.self="showPasswordModal = false">
      <div class="modal modal-sm">
        <h3>
          <i class="fas fa-shield-alt" style="margin-right:8px;color:#4f7cff"></i>
          管理员身份验证
        </h3>
        <p class="modal-desc">
          <template v-if="passwordAction === 'cancel'">
            即将取消 <strong>{{ selectedCancelIds.size }}</strong> 个团课，请输入管理员密码确认操作。
          </template>
          <template v-else>
            即将删除团课 <strong>ID={{ pendingDeleteId }}</strong>，请输入管理员密码确认操作。
          </template>
        </p>

        <div class="form-group">
          <label>管理员账号</label>
          <input :value="authStore.username" type="text" disabled class="input-disabled" />
        </div>
        <div class="form-group">
          <label>管理员密码 <span class="required">*</span></label>
          <div class="input-wrapper">
            <i class="fas fa-lock"></i>
            <input
              v-model="adminPassword"
              type="password"
              placeholder="请输入密码"
              @keyup.enter="handleVerifyPassword"
            />
          </div>
        </div>
        <p v-if="passwordError" class="field-error">{{ passwordError }}</p>

        <div class="modal-actions">
          <button class="btn btn-outline" @click="showPasswordModal = false">取消</button>
          <button
            class="btn"
            style="background:#ef4444;border-color:#ef4444;color:white"
            @click="handleVerifyPassword"
            :disabled="verifying"
          >
            <i v-if="verifying" class="fas fa-spinner fa-spin"></i>
            {{ verifying ? '验证中...' : '确认' }}
          </button>
        </div>
      </div>
    </div>
  </AppLayout>
</template>

<style scoped>
.toolbar { margin-bottom: 20px; }

.search-row {
  display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
}

.search-input {
  padding: 9px 16px; border-radius: 12px; border: 1px solid #e2e8f0;
  font-size: 14px; width: 260px; outline: none; transition: 0.15s;
}

.search-input:focus {
  border-color: #4f7cff; box-shadow: 0 0 0 3px rgba(79,124,255,0.12);
}

/* 分类标签 */
.filter-row {
  display: flex; justify-content: space-between; align-items: center;
  margin-top: 12px; flex-wrap: wrap; gap: 8px;
}

.filter-tabs {
  display: flex; gap: 8px; flex-wrap: wrap;
}

.sort-area {
  display: flex; align-items: center; gap: 6px;
}

.sort-label {
  font-size: 13px; color: #4e627c; font-weight: 500; white-space: nowrap;
}

.sort-select {
  padding: 6px 12px; border-radius: 20px; border: 1px solid #e2e8f0;
  font-size: 13px; outline: none; background: white; cursor: pointer;
  color: #1e293b; font-weight: 500;
}

.sort-select:focus {
  border-color: #4f7cff; box-shadow: 0 0 0 3px rgba(79,124,255,0.12);
}

.tab-btn {
  padding: 6px 16px; border-radius: 20px; border: 1px solid #e2e8f0;
  background: white; font-size: 13px; cursor: pointer; transition: 0.15s;
  color: #4e627c; font-weight: 500;
}

.tab-btn:hover { background: #f1f5f9; }

.tab-btn.active {
  background: #4f7cff; border-color: #4f7cff; color: white;
}

.selection-info {
  font-size: 13px; color: #4e627c; margin: 0 4px;
}
.selection-info strong { color: #1e293b; }

.cancel-hint {
  font-size: 13px; color: #f59e0b; font-weight: 500;
}

.btn {
  padding: 9px 20px; border-radius: 40px; border: 1px solid #e2e8f0;
  background: white; font-weight: 500; font-size: 13px; color: #1e293b;
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer; transition: 0.15s;
}

.btn-primary {
  background: #4f7cff; border-color: #4f7cff; color: white;
  box-shadow: 0 4px 10px rgba(79,124,255,0.25);
}

.btn-primary:hover:not(:disabled) { background: #3f6ae0; }
.btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }
.btn-outline:hover { background: #f0f5ff; }

.table-wrapper {
  background: white; border-radius: 20px; border: 1px solid #edf2f9;
  padding: 20px 24px 8px 24px;
}

.table-header {
  display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;
}

.table-header h3 {
  font-weight: 600; font-size: 16px; color: #0b1a33;
}

table {
  width: 100%; border-collapse: collapse; font-size: 14px;
}

th {
  text-align: left; padding: 12px 8px 12px 0;
  font-weight: 600; color: #4e627c; border-bottom: 1px solid #eef2f8;
}

td {
  padding: 14px 8px 14px 0; border-bottom: 1px solid #f1f5fa; color: #1e2f44;
}

tr:last-child td { border-bottom: none; }

td.empty {
  text-align: center; padding: 40px; color: #8b9ab0;
}

/* 取消模式 - 被禁用的行 */
.row-disabled {
  opacity: 0.4; pointer-events: none;
}
.row-disabled td { color: #94a3b8; }

.row-selected {
  background: #eff6ff;
}

/* ---- skeleton ---- */
.skeleton {
  background: linear-gradient(90deg, #eef2f8 25%, #e0e7f0 50%, #eef2f8 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  border-radius: 6px;
}

.sk-td {
  display: block;
  height: 16px;
  width: 80%;
}

@keyframes shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

tr { animation: fadeIn 0.35s ease; }

/* ---- badge ---- */
.status-badge {
  padding: 4px 12px; border-radius: 40px; font-size: 12px;
  font-weight: 500; display: inline-block;
  background: #e6f0ff; color: #4f7cff;
}

.status-badge.green { background: #e0f5ec; color: #0b8b5e; }
.status-badge.orange { background: #fff3e0; color: #b26e00; }
.status-badge.gray { background: #f1f5f9; color: #64748b; }

.actions { white-space: nowrap; }

.btn-sm {
  padding: 5px 12px; border-radius: 20px; border: 1px solid #e2e8f0;
  background: white; font-size: 12px; cursor: pointer; margin-right: 6px; transition: 0.15s;
}

.btn-sm:hover:not(:disabled) { background: #f0f5ff; }
.btn-sm:disabled { opacity: 0.4; cursor: not-allowed; }
.btn-sm.warn { color: #f59e0b; border-color: #fde68a; }
.btn-sm.warn:hover { background: #fffbeb; }
.btn-sm.danger { color: #ef4444; border-color: #fecaca; }
.btn-sm.danger:hover { background: #fef2f2; }

.pagination {
  display: flex; justify-content: center; align-items: center;
  gap: 16px; padding: 16px 0; font-size: 13px; color: #4e627c;
}

.pagination button {
  padding: 6px 16px; border-radius: 8px; border: 1px solid #e2e8f0;
  background: white; cursor: pointer; font-size: 13px; transition: 0.15s;
}

.pagination button:hover:not(:disabled) { background: #f0f5ff; }
.pagination button:disabled { opacity: 0.4; cursor: not-allowed; }

/* ---- modal ---- */
.modal-overlay {
  position: fixed; inset: 0; background: rgba(0,0,0,0.3);
  display: flex; align-items: center; justify-content: center; z-index: 200;
  animation: fadeIn 0.15s ease;
}

.modal {
  background: white; border-radius: 20px; padding: 28px;
  width: 560px; max-width: 92vw; max-height: 85vh; overflow-y: auto;
  box-shadow: 0 12px 40px rgba(0,0,0,0.12);
  animation: fadeIn 0.2s ease;
}

.modal-sm { width: 420px; }

.modal h3 {
  font-size: 18px; font-weight: 600; color: #0b1a33; margin-bottom: 20px;
}

.modal-desc {
  font-size: 14px; color: #4e627c; margin-bottom: 16px; line-height: 1.5;
}

.form-group { margin-bottom: 14px; }
.form-group label {
  display: block; font-size: 13px; font-weight: 600; color: #1e293b; margin-bottom: 4px;
}
.form-group .required { color: #ef4444; }

.form-group input,
.form-group textarea,
.form-group select {
  width: 100%; padding: 9px 12px; border-radius: 10px;
  border: 1px solid #e2e8f0; font-size: 14px; outline: none;
  transition: 0.15s; background: #f8fafd; font-family: inherit;
}
.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
  border-color: #4f7cff; box-shadow: 0 0 0 3px rgba(79,124,255,0.12);
}

.form-select {
  appearance: auto;
}

.input-disabled {
  background: #f1f5f9; color: #94a3b8; cursor: not-allowed;
}

.field-hint {
  font-size: 12px; color: #94a3b8; margin-top: 4px; display: block;
}

.input-wrapper {
  position: relative;
}
.input-wrapper i {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  color: #94a3b8; font-size: 14px;
}
.input-wrapper input {
  padding-left: 36px;
}

.field-error {
  color: #ef4444; font-size: 13px; margin: -8px 0 8px 0;
}

.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

.modal-actions {
  display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px;
}

/* ---- cover image upload ---- */
.cover-image-area {
  margin-top: 4px;
}

.table-cover-thumb {
  width: 48px;
  height: 48px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid #e2e8f0;
}

.no-cover {
  color: #94a3b8;
  font-size: 12px;
}

.upload-label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 12px 24px;
  border: 2px dashed #cbd5e1;
  border-radius: 12px;
  cursor: pointer;
  font-size: 14px;
  color: #64748b;
  transition: 0.15s;
  background: #f8fafd;
}

.upload-label:hover {
  border-color: #4f7cff;
  color: #4f7cff;
  background: #f0f5ff;
}

.cover-preview-wrapper {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}

.cover-preview {
  width: 200px;
  height: 130px;
  object-fit: cover;
  border-radius: 12px;
  border: 1px solid #e2e8f0;
}

/* ---- 时间列 ---- */
.time-cell {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
}

.time-theory {
  background: #f1f5f9;
  color: #475569;
}

.time-actual {
  background: #fef3c7;
  color: #92400e;
}

/* ---- 时间图例 ---- */
.time-legend {
  display: flex;
  gap: 20px;
  padding: 12px 0 0 0;
  font-size: 13px;
  color: #64748b;
}

.legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.legend-dot {
  width: 14px;
  height: 14px;
  border-radius: 4px;
  display: inline-block;
}

.legend-dot.theory {
  background: #f1f5f9;
  border: 1px solid #d1d5db;
}

.legend-dot.actual {
  background: #fef3c7;
  border: 1px solid #f59e0b;
}

/* ---- 实际时间记录（编辑弹窗）---- */
.actual-time-section {
  background: #fffbeb;
  border: 1px solid #fde68a;
  border-radius: 12px;
  padding: 16px;
  margin-bottom: 14px;
}

.section-title {
  font-size: 14px;
  font-weight: 600;
  color: #92400e;
  margin-bottom: 12px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.actual-time-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-bottom: 10px;
}

.actual-time-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.at-label {
  font-size: 12px;
  color: #a16207;
  font-weight: 500;
}

.at-value {
  font-size: 14px;
  color: #92400e;
  font-weight: 600;
}

.at-note {
  font-size: 13px;
  color: #92400e;
  margin: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  line-height: 1.4;
}
</style>
