<script setup lang="ts">
import { nextTick, onMounted, reactive, ref } from 'vue'
import { Message } from '@arco-design/web-vue'
import { http } from '../../api/http'
import RichEditorField from '../../components/RichEditorField.vue'
import { listTableRowIndex } from '../../utils/listTableRowIndex'
import { resolvePublicMediaUrl } from '../../utils/mediaUrl'

/** 主列表横向滚动宽度（与各列宽之和大致对齐） */
const VENUE_LIST_SCROLL_X = 2110

type DictItem = {
  id: number
  item_label: string
  item_value: string
  item_remark?: string
}
type Venue = {
  id: number
  name: string
  venue_type?: string
  venue_types?: string[]
  unit_name?: string
  district?: string
  ticket_type?: string
  appointment_type?: string | null
  booking_mode?: string | null
  open_mode?: string | null
  open_time?: string
  reservation_notice?: string
  ticket_content?: string
  booking_method?: string
  visit_form?: string
  consultation_hours?: string
  /** 预约方式旁展示的二维码多图 */
  booking_qr_media?: Array<{ type: 'image'; url: string }>
  address?: string
  contact_phone?: string | null
  /** 后端 decimal 序列化可能为字符串 */
  lat?: number | string | null
  lng?: number | string | null
  cover_image?: string
  gallery_media?: Array<{ type: 'image' | 'video'; url: string }>
  detail_html?: string
  sort?: number
  is_active: boolean
  is_included_in_stats?: boolean
  audit_status?: 'approved' | 'pending' | 'rejected'
  audit_remark?: string | null
}
type CurrentUser = {
  role: string
  full_admin_access?: boolean
}

const loading = ref(false)
const saving = ref(false)
const visible = ref(false)
const mapVisible = ref(false)
const mapLoading = ref(false)
const mapKeyword = ref('')
const mapResults = ref<Array<{ title: string; address: string; lat: number; lng: number }>>([])
const isCreate = ref(false)
const editId = ref<number | null>(null)
const rows = ref<Venue[]>([])
const districtOptions = ref<DictItem[]>([])
const venueTypeOptions = ref<DictItem[]>([])
const appointmentTypeOptions = ref<DictItem[]>([])
const bookingModeOptions = ref<DictItem[]>([])
const openModeOptions = ref<DictItem[]>([])
const ticketTypeOptions = ref<DictItem[]>([])
const currentUser = ref<CurrentUser | null>(null)
const mapContainerRef = ref<HTMLElement | null>(null)
const editorRenderKey = ref(0)
const mediaPreviewVisible = ref(false)
const mediaPreviewType = ref<'image' | 'video'>('image')
const mediaPreviewUrl = ref('')
let mapInstance: any = null
let markerLayer: any = null
const pickedPoint = ref<{ lat: number; lng: number; address: string } | null>(null)
const DEFAULT_CENTER = { lat: 31.299379, lng: 120.585315 } // 苏州市人民政府
const modalBodyStyle = { maxHeight: '70vh', overflow: 'auto' }

// 表单验证错误信息
const formErrors = reactive<Record<string, string>>({
  name: '',
  venue_types: '',
  district: '',
  unit_name: '',
  ticket_type: '',
  booking_mode: '',
  open_mode: '',
  visit_form: '',
  open_time: '',
  consultation_hours: '',
  contact_phone: '',
  address: '',
  lat: '',
  lng: '',
  booking_method: '',
  ticket_content: '',
  cover_image: '',
  gallery_media: '',
  detail_html: '',
})

function clearFormErrors() {
  Object.keys(formErrors).forEach((key) => {
    formErrors[key] = ''
  })
}

const filters = reactive({
  keyword: '',
  district: '',
  venue_type: '',
  ticket_type: '',
  booking_mode: '',
  open_mode: '',
  appointment_type: '',
  /** 预约方式（富文本）是否已填写：all=不限 */
  is_active: '' as '' | '1' | '0',
  is_included_in_stats: '' as '' | '1' | '0',
})

const pagination = reactive({
  current: 1,
  pageSize: 10,
  total: 0,
})

const rejectVenueId = ref<number | null>(null)
const rejectVenueRemark = ref('')
const rejectVenueVisible = ref(false)

function parseCoord(v: unknown): number | undefined {
  if (v === null || v === undefined || v === '') return undefined
  const n = typeof v === 'number' ? v : parseFloat(String(v).trim())
  return Number.isFinite(n) ? n : undefined
}

/** Quill 工具栏 handlers 里 this.quill 由 Quill 注入 */
function quillImageHandler(this: { quill: any }) {
  const quill = this.quill
  const input = document.createElement('input')
  input.type = 'file'
  input.accept = 'image/*'
  input.onchange = async () => {
    const file = input.files?.[0]
    if (!file) return
    try {
      const url = await uploadFile(file)
      const range = quill.getSelection(true)
      const index = range?.index ?? Math.max(0, quill.getLength() - 1)
      quill.insertEmbed(index, 'image', url, 'user')
      quill.setSelection(index + 1, 0)
      Message.success('图片已上传并插入')
    } catch (error: any) {
      Message.error(error?.response?.data?.message ?? '图片上传失败')
    }
  }
  input.click()
}

function quillVideoHandler(this: { quill: any }) {
  const quill = this.quill
  const input = document.createElement('input')
  input.type = 'file'
  input.accept = 'video/*'
  input.onchange = async () => {
    const file = input.files?.[0]
    if (!file) return
    try {
      const url = await uploadFile(file)
      const range = quill.getSelection(true)
      const index = range?.index ?? Math.max(0, quill.getLength() - 1)
      quill.insertEmbed(index, 'video', url, 'user')
      quill.setSelection(index + 1, 0)
      Message.success('视频已上传并插入')
    } catch (error: any) {
      Message.error(error?.response?.data?.message ?? '视频上传失败')
    }
  }
  input.click()
}

const quillToolbarModules = {
  toolbar: {
    container: [
      [{ header: [1, 2, 3, false] }],
      ['bold', 'italic', 'underline', 'strike'],
      [{ color: [] }, { background: [] }],
      [{ list: 'ordered' }, { list: 'bullet' }],
      [{ align: [] }],
      ['link', 'image', 'video'],
      ['clean'],
    ],
    handlers: {
      image: quillImageHandler,
      video: quillVideoHandler,
    },
  },
}

const detailEditorOptions = {
  modules: quillToolbarModules,
  placeholder: '请输入场馆详情内容',
}

const reservationEditorOptions = {
  modules: quillToolbarModules,
  placeholder: '',
}

const form = reactive({
  name: '',
  venue_types: [] as string[],
  unit_name: '',
  district: '',
  ticket_type: '',
  appointment_type: '',
  booking_mode: '',
  open_mode: '',
  open_time: '',
  reservation_notice: '',
  ticket_content: '',
  booking_method: '',
  visit_form: '',
  consultation_hours: '',
  booking_qr_media: [] as Array<{ type: 'image'; url: string }>,
  address: '',
  contact_phone: '',
  lat: undefined as number | undefined,
  lng: undefined as number | undefined,
  cover_image: '',
  gallery_media: [] as Array<{ type: 'image' | 'video'; url: string }>,
  detail_html: '',
  sort: 0,
  is_active: true,
  is_included_in_stats: false,
})

function isSuperAdmin() {
  return currentUser.value?.full_admin_access === true
}

async function approveVenue(row: Venue) {
  try {
    await http.post(`/venues/${row.id}/audit/approve`)
    Message.success('已通过审核')
    await loadRows()
  } catch (error: any) {
    Message.error(error?.response?.data?.message ?? '操作失败')
  }
}

function openRejectVenue(row: Venue) {
  rejectVenueId.value = row.id
  rejectVenueRemark.value = ''
  rejectVenueVisible.value = true
}

async function submitRejectVenue(): Promise<boolean> {
  if (!rejectVenueId.value) return false
  try {
    await http.post(`/venues/${rejectVenueId.value}/audit/reject`, { remark: rejectVenueRemark.value || undefined })
    Message.success('已退回')
    rejectVenueVisible.value = false
    await loadRows()
    return true
  } catch (error: any) {
    Message.error(error?.response?.data?.message ?? '操作失败')
    return false
  }
}

async function removeVenue(row: Venue) {
  try {
    await http.delete(`/venues/${row.id}`)
    Message.success('删除成功')
    await loadRows()
  } catch (error: any) {
    Message.error(error?.response?.data?.message ?? '删除失败')
  }
}

function listCellOneLine(s?: string | null) {
  const t = (s || '').trim()
  if (!t) return '-'
  const plain = t.replace(/<[^>]+>/g, ' ')
  return plain.replace(/\s+/g, ' ')
}

function optionLabel(list: DictItem[], value?: string | null) {
  if (!value) return '-'
  return list.find((item) => item.item_value === value)?.item_label || value
}

function optionColor(list: DictItem[], value?: string | null, fallback = 'arcoblue') {
  if (!value) return fallback
  return list.find((item) => item.item_value === value)?.item_remark || fallback
}

function venueTypeValues(record: Venue): string[] {
  const raw = record.venue_types
  if (Array.isArray(raw) && raw.length) return raw.map((v) => String(v))
  return record.venue_type ? [String(record.venue_type)] : []
}

function mapJsKey() {
  return (import.meta.env.VITE_TENCENT_MAP_KEY as string) || ''
}

function normalizeMediaUrl(rawUrl?: string, rawPath?: string) {
  const urlText = String(rawUrl || '').trim()
  if (urlText) {
    return resolvePublicMediaUrl(urlText)
  }
  const pathText = String(rawPath || '').trim()
  if (!pathText) return ''
  return resolvePublicMediaUrl(pathText)
}

async function uploadFile(file: File) {
  const fd = new FormData()
  fd.append('file', file)
  const response = await http.post('/upload', fd)
  const data = response?.data || {}
  return normalizeMediaUrl(data.url, data.path)
}

function resetEditors() {
  editorRenderKey.value += 1
}

function openMediaPreview(type: 'image' | 'video', url: string) {
  if (!url) return
  mediaPreviewType.value = type
  mediaPreviewUrl.value = resolvePublicMediaUrl(url)
  mediaPreviewVisible.value = true
}

function pickUploadFile(payload: any): File | null {
  const visited = new Set<any>()
  const queue: any[] = [payload]

  while (queue.length) {
    const cur = queue.shift()
    if (!cur || visited.has(cur)) continue
    visited.add(cur)

    if (cur instanceof File) return cur
    if (cur?.target?.files?.[0] instanceof File) return cur.target.files[0]

    if (Array.isArray(cur)) {
      for (const item of cur) queue.push(item)
      continue
    }

    if (typeof cur === 'object') {
      const maybeKeys = ['file', 'raw', 'originFile', 'originFileObj', 'fileItem', 'item', 'data']
      for (const key of maybeKeys) {
        if (cur[key]) queue.push(cur[key])
      }
      for (const value of Object.values(cur)) {
        if (value && (typeof value === 'object' || Array.isArray(value))) {
          queue.push(value)
        }
      }
    }
  }

  return null
}

async function loadRows() {
  loading.value = true
  try {
    const { data } = await http.get('/venues', {
      params: {
        keyword: filters.keyword || undefined,
        district: filters.district || undefined,
        venue_type: filters.venue_type || undefined,
        ticket_type: filters.ticket_type || undefined,
        booking_mode: filters.booking_mode || undefined,
        open_mode: filters.open_mode || undefined,
        appointment_type: filters.appointment_type || undefined,
        is_active: filters.is_active || undefined,
        is_included_in_stats: filters.is_included_in_stats || undefined,
      },
    })
    rows.value = data
    pagination.total = data.length
  } catch (error: any) {
    Message.error(error?.response?.data?.message ?? '加载场馆失败')
  } finally {
    loading.value = false
  }
}

async function loadDistricts() {
  const { data } = await http.get('/dict-items', { params: { dict_type: 'district', active_only: 1 } })
  districtOptions.value = data
}

async function loadVenueTypes() {
  const { data } = await http.get('/dict-items', { params: { dict_type: 'venue_type', active_only: 1 } })
  venueTypeOptions.value = data
}

async function loadAppointmentTypes() {
  const { data } = await http.get('/dict-items', { params: { dict_type: 'venue_appointment_type', active_only: 1 } })
  appointmentTypeOptions.value = data
}

async function loadOpenModes() {
  const { data } = await http.get('/dict-items', { params: { dict_type: 'venue_open_mode', active_only: 1 } })
  openModeOptions.value = data
}

async function loadBookingModes() {
  const { data } = await http.get('/dict-items', { params: { dict_type: 'venue_booking_mode', active_only: 1 } })
  bookingModeOptions.value = data
}

async function loadTicketTypes() {
  const { data } = await http.get('/dict-items', { params: { dict_type: 'ticket_type', active_only: 1 } })
  ticketTypeOptions.value = data
}

async function loadMe() {
  const { data } = await http.get('/me')
  currentUser.value = data
}

function openCreate() {
  isCreate.value = true
  editId.value = null
  clearFormErrors()
  form.name = ''
  form.venue_types = []
  form.unit_name = ''
  form.district = ''
  form.ticket_type = ''
  form.booking_mode = ''
  form.open_mode = ''
  form.open_time = ''
  form.reservation_notice = ''
  form.ticket_content = ''
  form.booking_method = ''
  form.visit_form = ''
  form.consultation_hours = ''
  form.booking_qr_media = []
  form.address = ''
  form.contact_phone = ''
  form.lat = undefined
  form.lng = undefined
  form.cover_image = ''
  form.gallery_media = []
  form.detail_html = ''
  form.sort = 0
  form.is_active = true
  form.is_included_in_stats = false
  resetEditors()
  visible.value = true
}

function openEdit(row: Venue) {
  isCreate.value = false
  editId.value = row.id
  clearFormErrors()
  form.name = row.name
  form.venue_types =
    Array.isArray(row.venue_types) && row.venue_types.length
      ? [...row.venue_types]
      : row.venue_type
        ? [row.venue_type]
        : []
  form.unit_name = row.unit_name ?? ''
  form.district = row.district ?? ''
  form.ticket_type = row.ticket_type ?? ''
  form.appointment_type = row.appointment_type ?? ''
  form.booking_mode = row.booking_mode ?? ''
  form.open_mode = row.open_mode ?? ''
  form.open_time = row.open_time ?? ''
  form.reservation_notice = row.reservation_notice ?? ''
  form.ticket_content = row.ticket_content ?? ''
  form.booking_method = row.booking_method ?? ''
  form.visit_form = row.visit_form ?? ''
  form.consultation_hours = row.consultation_hours ?? ''
  {
    const raw = (row as { booking_qr_media?: Array<{ type?: string; url?: string }> }).booking_qr_media
    if (Array.isArray(raw) && raw.length) {
      form.booking_qr_media = raw
        .filter((x) => x && x.url && (x as { type?: string }).type === 'image')
        .map((x) => ({ type: 'image' as const, url: String(x.url) }))
    } else {
      form.booking_qr_media = []
    }
  }
  form.address = row.address ?? ''
  form.contact_phone = row.contact_phone ?? ''
  form.lat = parseCoord(row.lat)
  form.lng = parseCoord(row.lng)
  form.cover_image = row.cover_image ?? ''
  form.gallery_media = Array.isArray(row.gallery_media) ? [...row.gallery_media] : []
  form.detail_html = row.detail_html ?? ''
  const sortNum = row.sort
  form.sort =
    typeof sortNum === 'number' && Number.isFinite(sortNum)
      ? sortNum
      : parseInt(String(sortNum ?? '0'), 10) || 0
  form.is_active = row.is_active
  form.is_included_in_stats = row.is_included_in_stats ?? false
  resetEditors()
  visible.value = true
}

async function onCoverSelect(fileItem: any) {
  try {
    const file = pickUploadFile(fileItem)
    if (!file) {
      Message.warning('未识别到上传文件')
      return false
    }
    form.cover_image = await uploadFile(file)
    Message.success('封面上传成功')
  } catch (error: any) {
    Message.error(error?.response?.data?.message ?? '封面上传失败')
  }
  return false
}

async function onGallerySelect(fileItem: any) {
  try {
    const file = pickUploadFile(fileItem)
    if (!file) {
      Message.warning('未识别到上传文件')
      return false
    }
    const url = await uploadFile(file)
    if (!url) {
      Message.error('上传成功但未返回可用地址')
      return false
    }
    const isVideo = file.type.startsWith('video/')
    form.gallery_media.push({ type: isVideo ? 'video' : 'image', url })
    Message.success('轮播资源上传成功')
  } catch (error: any) {
    Message.error(error?.response?.data?.message ?? '轮播资源上传失败')
  }
  return false
}

function onCoverChange(...args: any[]) {
  void onCoverSelect(args)
}

function onGalleryChange(...args: any[]) {
  void onGallerySelect(args)
}

function removeGallery(index: number) {
  form.gallery_media.splice(index, 1)
}

async function onBookingQrSelect(file: File) {
  try {
    if (!file) {
      Message.warning('未识别到上传文件')
      return false
    }
    if (!file.type.startsWith('image/')) {
      Message.warning('仅支持图片')
      return false
    }
    const url = await uploadFile(file)
    if (!url) {
      Message.error('上传成功但未返回可用地址')
      return false
    }
    form.booking_qr_media.push({ type: 'image', url })
    Message.success('已上传')
  } catch (error: any) {
    Message.error(error?.response?.data?.message ?? '上传失败')
  }
  return false
}

function removeBookingQr(index: number) {
  form.booking_qr_media.splice(index, 1)
}

function removeCover() {
  form.cover_image = ''
}

function onImageLoadError(ev?: Event) {
  const el = ev?.target as HTMLImageElement | undefined
  const src = (el?.getAttribute?.('src') || el?.src || '').trim()
  // 空 src 或占位仍会触发 error，勿误报 storage
  if (!src || src === 'about:blank') return
  Message.error('图片地址无法访问，请检查后端 storage 访问配置')
}

function onSearch() {
  pagination.current = 1
  void loadRows()
}

function onPageChange(p: number) {
  pagination.current = p
}

async function ensureMapSdkLoaded() {
  const w = window as any
  if (w.TMap) return
  const key = mapJsKey()
  if (!key) {
    throw new Error('请先配置 VITE_TENCENT_MAP_KEY')
  }
  await new Promise<void>((resolve, reject) => {
    const script = document.createElement('script')
    script.src = `https://map.qq.com/api/gljs?v=1.exp&key=${key}`
    script.async = true
    script.onload = () => resolve()
    script.onerror = () => reject(new Error('腾讯地图SDK加载失败'))
    document.head.appendChild(script)
  })
}

function renderMarker(lat: number, lng: number) {
  const TMap = (window as any).TMap
  if (!mapInstance) return
  if (markerLayer) markerLayer.setMap(null)
  markerLayer = new TMap.MultiMarker({
    map: mapInstance,
    styles: {
      marker: new TMap.MarkerStyle({ width: 24, height: 35 }),
    },
    geometries: [{ id: 'picked', styleId: 'marker', position: new TMap.LatLng(lat, lng) }],
  })
  mapInstance.setCenter(new TMap.LatLng(lat, lng))
}

function refreshMapViewport(lat: number, lng: number) {
  const TMap = (window as any).TMap
  if (!mapInstance || !TMap) return
  const center = new TMap.LatLng(lat, lng)
  // 弹窗场景下首帧可能未完成布局，强制刷新两次可避免灰底无底图
  mapInstance.resize?.()
  mapInstance.setCenter(center)
  mapInstance.setZoom(13)
  setTimeout(() => {
    mapInstance.resize?.()
    mapInstance.setCenter(center)
  }, 120)
}

async function reverseGeocode(lat: number, lng: number) {
  const { data } = await http.get('/map/reverse-geocode', { params: { lat, lng } })
  pickedPoint.value = { lat, lng, address: data.address || '' }
  if (data.district) {
    const exists = districtOptions.value.some((d) => d.item_value === data.district)
    if (exists) form.district = data.district
  }
}

async function initMapPicker() {
  await ensureMapSdkLoaded()
  const TMap = (window as any).TMap
  const lat = typeof form.lat === 'number' ? form.lat : DEFAULT_CENTER.lat
  const lng = typeof form.lng === 'number' ? form.lng : DEFAULT_CENTER.lng
  const center = new TMap.LatLng(lat, lng)
  if (!mapInstance) {
    mapInstance = new TMap.Map(mapContainerRef.value, { center, zoom: 13 })
    mapInstance.on('click', async (evt: any) => {
      const lat = Number(evt.latLng.getLat().toFixed(6))
      const lng = Number(evt.latLng.getLng().toFixed(6))
      renderMarker(lat, lng)
      try {
        await reverseGeocode(lat, lng)
      } catch (error: any) {
        Message.error(error?.response?.data?.message ?? '逆地理编码失败')
      }
    })
  } else {
    mapInstance.setCenter(center)
  }
  refreshMapViewport(lat, lng)
  if (typeof form.lat !== 'undefined' && typeof form.lng !== 'undefined') {
    renderMarker(form.lat, form.lng)
    pickedPoint.value = { lat: form.lat, lng: form.lng, address: form.address || '' }
  } else {
    if (markerLayer) {
      markerLayer.setMap(null)
      markerLayer = null
    }
    pickedPoint.value = null
  }
}

async function openMapPicker() {
  if (!isCreate.value) return
  mapVisible.value = true
  mapKeyword.value = ''
  mapResults.value = []
  mapLoading.value = true
  try {
    await nextTick()
    setTimeout(async () => {
      try {
        await initMapPicker()
      } catch (error: any) {
        Message.error(error?.message ?? '地图初始化失败')
      } finally {
        mapLoading.value = false
      }
    }, 250)
  } catch {
    mapLoading.value = false
  }
}

async function searchMapKeyword() {
  if (!mapKeyword.value.trim()) {
    mapResults.value = []
    return
  }
  mapLoading.value = true
  try {
    const { data } = await http.get('/map/search', { params: { keyword: mapKeyword.value, region: '苏州' } })
    mapResults.value = data
  } catch (error: any) {
    Message.error(error?.response?.data?.message ?? '地图搜索失败')
  } finally {
    mapLoading.value = false
  }
}

async function pickSearchResult(item: { title: string; address: string; lat: number; lng: number }) {
  renderMarker(item.lat, item.lng)
  pickedPoint.value = { lat: item.lat, lng: item.lng, address: item.address || '' }
  try {
    await reverseGeocode(item.lat, item.lng)
  } catch {
    // ignore
  }
}

function confirmMapPick() {
  if (!pickedPoint.value) {
    Message.warning('请先点击地图或选择搜索结果')
    return false
  }
  form.lat = pickedPoint.value.lat
  form.lng = pickedPoint.value.lng
  form.address = pickedPoint.value.address || form.address
  mapVisible.value = false
  return true
}

function validateForm(): boolean {
  clearFormErrors()
  let isValid = true

  // 基础信息验证
  if (!form.name.trim()) {
    formErrors.name = '场馆名称为必填项'
    isValid = false
  }
  if (!form.district.trim()) {
    formErrors.district = '行政区为必填项'
    isValid = false
  }
  if (!form.address.trim()) {
    formErrors.address = '场馆地址为必填项'
    isValid = false
  }
  if (typeof form.lat !== 'number' || typeof form.lng !== 'number') {
    formErrors.lat = '经纬度为必填项'
    isValid = false
  }
  // 主题验证
  if (!form.venue_types.length) {
    formErrors.venue_types = '主题为必填项'
    isValid = false
  }
  // 所属单位验证
  if (!form.unit_name.trim()) {
    formErrors.unit_name = '所属单位为必填项'
    isValid = false
  }
  // 门票类型验证
  if (!form.ticket_type) {
    formErrors.ticket_type = '门票类型为必填项'
    isValid = false
  }
    // 开放模式验证
  if (!form.open_mode) {
    formErrors.open_mode = '开放模式为必填项'
    isValid = false
  }
  // 参观形式验证
  if (!form.visit_form.trim()) {
    formErrors.visit_form = '参观形式为必填项'
    isValid = false
  }
  // 开放时间验证
  if (!form.open_time.trim()) {
    formErrors.open_time = '开放时间为必填项'
    isValid = false
  }
  // 咨询时间验证
  if (!form.consultation_hours.trim()) {
    formErrors.consultation_hours = '咨询时间为必填项'
    isValid = false
  }
  // 咨询电话验证
  if (!form.contact_phone.trim()) {
    formErrors.contact_phone = '咨询电话为必填项'
    isValid = false
  }
  // 预约方式验证
  if (!form.booking_method.trim()) {
    formErrors.booking_method = '预约方式为必填项'
    isValid = false
  }
  // 门票说明验证
  if (!form.ticket_content.trim()) {
    formErrors.ticket_content = '门票说明为必填项'
    isValid = false
  }
  // 科普场馆主图验证
  if (!form.cover_image) {
    formErrors.cover_image = '科普场馆主图为必填项'
    isValid = false
  }
  // 科普场馆展示图片验证
  if (!form.gallery_media.length) {
    formErrors.gallery_media = '科普场馆展示图片为必填项'
    isValid = false
  }
  // 场馆简介验证
  if (!form.detail_html || !form.detail_html.trim()) {
    formErrors.detail_html = '场馆简介为必填项'
    isValid = false
  }

  return isValid
}

async function submit(): Promise<boolean> {
  saving.value = true
  try {
    // 表单验证
    if (!validateForm()) {
      Message.warning('请填写所有必填项')
      return false
    }

    const payload = {
      ...form,
      booking_qr_media: form.booking_qr_media || [],
      sort: isSuperAdmin() ? form.sort : undefined,
    }
    if (isCreate.value) {
      await http.post('/venues', payload)
      Message.success('创建场馆成功')
    } else if (editId.value) {
      await http.put(`/venues/${editId.value}`, payload)
      Message.success('更新场馆成功')
    }
    await loadRows()
    return true
  } catch (error: any) {
    Message.error(error?.response?.data?.message ?? '保存失败')
    return false
  } finally {
    saving.value = false
  }
}

async function onBeforeOk() {
  return await submit()
}

onMounted(async () => {
  await loadMe()
  await Promise.all([
    loadRows(),
    loadDistricts(),
    loadVenueTypes(),
    loadAppointmentTypes(),
    loadBookingModes(),
    loadOpenModes(),
    loadTicketTypes(),
  ])
})
</script>

<template>
  <a-card title="场馆管理 / 场馆列表">
    <template #extra>
      <a-space wrap>
        <a-input v-model="filters.keyword" placeholder="搜索名称/地址/开放时间/预约须知等" style="width: 260px" />
        <a-select v-model="filters.venue_type" allow-clear placeholder="筛选主题" style="width: 180px">
          <a-option v-for="item in venueTypeOptions" :key="item.id" :value="item.item_value">{{ item.item_label }}</a-option>
        </a-select>
        <a-select v-model="filters.district" allow-clear placeholder="筛选行政区" style="width: 180px">
          <a-option v-for="item in districtOptions" :key="item.id" :value="item.item_value">{{ item.item_label }}</a-option>
        </a-select>
        <a-select v-model="filters.ticket_type" allow-clear placeholder="筛选门票类型" style="width: 180px">
          <a-option v-for="item in ticketTypeOptions" :key="item.id" :value="item.item_value">{{ item.item_label }}</a-option>
        </a-select>
        <a-select v-model="filters.booking_mode" allow-clear placeholder="筛选预约模式" style="width: 180px">
          <a-option v-for="item in bookingModeOptions" :key="item.id" :value="item.item_value">{{ item.item_label }}</a-option>
        </a-select>
        <a-select v-model="filters.open_mode" allow-clear placeholder="筛选开放模式" style="width: 180px">
          <a-option v-for="item in openModeOptions" :key="item.id" :value="item.item_value">{{ item.item_label }}</a-option>
        </a-select>
        <a-select v-model="filters.is_included_in_stats" allow-clear placeholder="纳入市科协人数统计系统" style="width: 180px">
          <a-option value="1">纳入统计</a-option>
          <a-option value="0">不纳入</a-option>
        </a-select>
        <a-select v-model="filters.is_active" allow-clear placeholder="上架状态" style="width: 130px">
          <a-option value="1">上架</a-option>
          <a-option value="0">下架</a-option>
        </a-select>
        <a-button type="primary" @click="onSearch">查询</a-button>
        <a-button type="primary" @click="openCreate">新增场馆</a-button>
        <!-- <a-button v-if="isSuperAdmin()" @click="exportVenues">导出场馆</a-button> -->
      </a-space>
    </template>

    <a-table
      class="list-data-table"
      :scroll="{ x: VENUE_LIST_SCROLL_X }"
      :data="rows"
      :loading="loading"
      row-key="id"
      :pagination="{ current: pagination.current, pageSize: pagination.pageSize, total: pagination.total, showTotal: true, showJumper: true }"
      @page-change="onPageChange"
    >
      <template #columns>
        <a-table-column title="" :width="50" :ellipsis="true" :tooltip="true">
          <template #cell="{ rowIndex }">{{
            listTableRowIndex(rowIndex, pagination.current, pagination.pageSize)
          }}</template>
        </a-table-column>
        <a-table-column title="场馆名称" data-index="name" :width="220" :min-width="160" :ellipsis="true" :tooltip="true" />
        <a-table-column title="主题" :width="130">
          <template #cell="{ record }">
            <a-space v-if="venueTypeValues(record).length" wrap :size="4">
              <a-tag
                v-for="(v, i) in venueTypeValues(record)"
                :key="record.id + '-vt-' + i"
                :color="optionColor(venueTypeOptions, v, 'arcoblue')"
              >
                {{ optionLabel(venueTypeOptions, v) }}
              </a-tag>
            </a-space>
            <span v-else>-</span>
          </template>
        </a-table-column>
        <a-table-column title="行政区" data-index="district" :width="120" :ellipsis="true" :tooltip="true" />
        <a-table-column title="所属单位" data-index="unit_name" :width="200" :min-width="140" :ellipsis="true" :tooltip="true" />
        <a-table-column title="门票类型" :width="120">
          <template #cell="{ record }">
            <a-tag :color="optionColor(ticketTypeOptions, record.ticket_type, 'green')">
              {{ optionLabel(ticketTypeOptions, record.ticket_type) }}
            </a-tag>
          </template>
        </a-table-column>
        <a-table-column title="预约模式" :width="180">
          <template #cell="{ record }">
            <span>{{ optionLabel(bookingModeOptions, record.booking_mode) }}</span>
          </template>
        </a-table-column>
        <a-table-column title="开放模式" :width="150">
          <template #cell="{ record }">
            <span>{{ optionLabel(openModeOptions, record.open_mode) }}</span>
          </template>
        </a-table-column>
        <a-table-column title="开放时间" :width="220" :min-width="160" :ellipsis="true" :tooltip="true">
          <template #cell="{ record }">{{ listCellOneLine(record.open_time) }}</template>
        </a-table-column>
        <a-table-column title="地址" data-index="address" :width="280" :min-width="200" :ellipsis="true" :tooltip="true" />
        <a-table-column title="排序" data-index="sort" :width="90" :ellipsis="true" :tooltip="true" />
        <a-table-column title="上架状态" :width="100">
          <template #cell="{ record }">
            <a-tag :color="record.is_active ? 'green' : 'gray'">{{ record.is_active ? '上架' : '下架' }}</a-tag>
          </template>
        </a-table-column>
        <a-table-column title="操作" :width="220" fixed="right" align="left">
          <template #cell="{ record }">
            <a-space wrap justify="start">
              <a-button type="text" @click="openEdit(record)">编辑</a-button>
              <template v-if="isSuperAdmin() && (record.audit_status === 'pending' || record.audit_status === 'rejected')">
                <a-button type="text" status="success" @click="approveVenue(record)">通过</a-button>
                <a-button type="text" status="danger" @click="openRejectVenue(record)">退回</a-button>
              </template>
              <a-popconfirm
                v-if="isSuperAdmin()"
                content="删除后该场馆关联的活动、预约等数据将一并删除，且不可恢复，确认删除？"
                @ok="removeVenue(record)"
              >
                <a-button type="text" status="danger">删除</a-button>
              </a-popconfirm>
            </a-space>
          </template>
        </a-table-column>
      </template>
    </a-table>
  </a-card>

  <a-modal v-model:visible="rejectVenueVisible" title="退回场馆" :on-before-ok="submitRejectVenue">
    <a-textarea v-model="rejectVenueRemark" placeholder="退回说明（选填）" :auto-size="{ minRows: 3, maxRows: 8 }" />
  </a-modal>

  <a-modal
    v-model:visible="visible"
    :title="isCreate ? '新增场馆' : '编辑场馆'"
    width="70%"
    :body-style="modalBodyStyle"
    :confirm-loading="saving"
    :on-before-ok="onBeforeOk"
  >
    <a-form :model="form" layout="vertical" class="admin-modal-form">
      <a-form-item label="场馆名称" required :help="formErrors.name">
        <a-input v-model="form.name" />
        <template v-if="formErrors.name" #help>
          <span style="color: #f53f3f;">{{ formErrors.name }}</span>
        </template>
      </a-form-item>
      <a-form-item label="主题（可多选）" required :help="formErrors.venue_types">
        <a-select v-model="form.venue_types" multiple placeholder="请选择主题">
          <a-option v-for="item in venueTypeOptions" :key="item.id" :value="item.item_value">
            {{ item.item_label }}
          </a-option>
        </a-select>
        <template v-if="formErrors.venue_types" #help>
          <span style="color: #f53f3f;">{{ formErrors.venue_types }}</span>
        </template>
      </a-form-item>
      <a-form-item label="行政区" required :help="formErrors.district">
        <a-select v-model="form.district" allow-clear placeholder="请选择行政区">
          <a-option v-for="item in districtOptions" :key="item.id" :value="item.item_value">
            {{ item.item_label }}
          </a-option>
        </a-select>
        <template v-if="formErrors.district" #help>
          <span style="color: #f53f3f;">{{ formErrors.district }}</span>
        </template>
      </a-form-item>
      <a-form-item label="所属单位" required :help="formErrors.unit_name">
        <a-input v-model="form.unit_name" placeholder="所属单位名称" />
        <template v-if="formErrors.unit_name" #help>
          <span style="color: #f53f3f;">{{ formErrors.unit_name }}</span>
        </template>
      </a-form-item>
      <a-form-item label="门票类型" required :help="formErrors.ticket_type">
        <a-select v-model="form.ticket_type" placeholder="请选择门票类型">
          <a-option v-for="item in ticketTypeOptions" :key="item.id" :value="item.item_value">
            {{ item.item_label }}
          </a-option>
        </a-select>
        <template v-if="formErrors.ticket_type" #help>
          <span style="color: #f53f3f;">{{ formErrors.ticket_type }}</span>
        </template>
      </a-form-item>
      <a-form-item label="预约模式" :help="formErrors.booking_mode">
        <a-select v-model="form.booking_mode" allow-clear placeholder="请选择预约模式">
          <a-option v-for="item in bookingModeOptions" :key="item.id" :value="item.item_value">
            {{ item.item_label }}
          </a-option>
        </a-select>
        <template v-if="formErrors.booking_mode" #help>
          <span style="color: #f53f3f;">{{ formErrors.booking_mode }}</span>
        </template>
      </a-form-item>
      <a-form-item label="开放模式" required :help="formErrors.open_mode">
        <a-select v-model="form.open_mode" placeholder="请选择开放模式">
          <a-option v-for="item in openModeOptions" :key="item.id" :value="item.item_value">
            {{ item.item_label }}
          </a-option>
        </a-select>
        <template v-if="formErrors.open_mode" #help>
          <span style="color: #f53f3f;">{{ formErrors.open_mode }}</span>
        </template>
      </a-form-item>
      <a-form-item label="参观形式" required :help="formErrors.visit_form">
        <a-input v-model="form.visit_form" placeholder="参观形式说明" />
        <template v-if="formErrors.visit_form" #help>
          <span style="color: #f53f3f;">{{ formErrors.visit_form }}</span>
        </template>
      </a-form-item>
      <a-form-item label="开放时间" required :help="formErrors.open_time">
        <a-input v-model="form.open_time" placeholder="如：周一至周五 09:00-17:00，周末 09:00-18:00" />
        <template v-if="formErrors.open_time" #help>
          <span style="color: #f53f3f;">{{ formErrors.open_time }}</span>
        </template>
      </a-form-item>
      <a-form-item label="咨询时间" required :help="formErrors.consultation_hours">
        <a-input v-model="form.consultation_hours" placeholder="咨询时间说明" />
        <template v-if="formErrors.consultation_hours" #help>
          <span style="color: #f53f3f;">{{ formErrors.consultation_hours }}</span>
        </template>
      </a-form-item>
      <a-form-item label="咨询电话" required :help="formErrors.contact_phone">
        <a-input v-model="form.contact_phone" placeholder="前台可点击拨打" />
        <template v-if="formErrors.contact_phone" #help>
          <span style="color: #f53f3f;">{{ formErrors.contact_phone }}</span>
        </template>
      </a-form-item>
      <a-form-item label="排序">
        <a-input-number v-model="form.sort" :min="0" :disabled="!isSuperAdmin()" />
      </a-form-item>
      <a-row :gutter="16">
        <a-col :span="12">
          <a-form-item label="上架状态">
            <a-switch v-model="form.is_active" />
            <span style="margin-left: 8px; color: var(--color-text-3)">{{ form.is_active ? '上架' : '下架' }}</span>
          </a-form-item>
        </a-col>
        <a-col :span="12">
          <a-form-item label="纳入市科协人数统计系统">
            <a-switch v-model="form.is_included_in_stats" />
            <span style="margin-left: 8px; color: var(--color-text-3)">{{ form.is_included_in_stats ? '是' : '否' }}</span>
          </a-form-item>
        </a-col>
      </a-row>
      <a-form-item label="预约方式与预约二维码" required class="admin-modal-form__full">
        <div style="display: flex; gap: 16px; width: 100%">
          <div style="flex: 1 1 50%; min-width: 0">
            <div class="venue-form-split-label">预约方式 <span style="color: #f53f3f">*</span></div>
            <a-input v-model="form.booking_method" placeholder="单行文字说明" style="width: 100%" />
          </div>
          <div style="flex: 1 1 50%; min-width: 0">
            <div class="venue-form-split-label">预约二维码</div>
            <a-upload
              :auto-upload="false"
              :show-file-list="false"
              :limit="20"
              multiple
              accept="image/*"
              :on-before-upload="onBookingQrSelect"
            >
              <template #upload-button><a-button type="primary" size="small">上传图片</a-button></template>
            </a-upload>
            <a-typography-text type="secondary" style="margin-top: 8px; display: block; font-size: 12px">推荐 1200×600</a-typography-text>
            <div v-if="form.booking_qr_media.length" class="venue-gallery-grid" style="margin-top: 8px">
              <div v-for="(m, i) in form.booking_qr_media" :key="`booking-qr-${i}`" class="venue-gallery-item">
                <img
                  :src="resolvePublicMediaUrl(m.url)"
                  class="venue-gallery-thumb"
                  @error="onImageLoadError"
                  @click="openMediaPreview('image', m.url)"
                />
                <a-button size="mini" status="danger" @click="removeBookingQr(i)">删除</a-button>
              </div>
            </div>
          </div>
        </div>
        <template #help>
          <span v-if="formErrors.booking_method" style="color: #f53f3f;">{{ formErrors.booking_method }}</span>
        </template>
      </a-form-item>
      <a-form-item label="门票说明" required class="admin-modal-form__full" :help="formErrors.ticket_content">
        <a-textarea v-model="form.ticket_content" :auto-size="{ minRows: 3, maxRows: 12 }" placeholder="门票说明" />
        <template v-if="formErrors.ticket_content" #help>
          <span style="color: #f53f3f;">{{ formErrors.ticket_content }}</span>
        </template>
      </a-form-item>
      <a-form-item label="场馆地址与经纬度" required class="admin-modal-form__full">
        <div class="venue-address-coord-row">
          <a-input v-model="form.address" class="venue-address-coord-row__address" placeholder="场馆地址" allow-clear />
          <a-input-number
            v-model="form.lng"
            class="venue-address-coord-row__lng"
            :precision="7"
            placeholder="经度"
            hide-button
            disabled
          />
          <a-input-number
            v-model="form.lat"
            class="venue-address-coord-row__lat"
            :precision="7"
            placeholder="纬度"
            hide-button
            disabled
          />
          <a-button v-if="isCreate" type="primary" class="venue-address-coord-row__map" @click="openMapPicker">地图选点</a-button>
        </div>
        <template #extra>经纬度不可手动编辑，请使用地图选点自动填充。</template>
        <template #help>
          <span v-if="formErrors.address || formErrors.lat || formErrors.lng" style="color: #f53f3f;">
            {{ formErrors.address || formErrors.lat || formErrors.lng }}
          </span>
        </template>
      </a-form-item>
      <a-form-item label="科普场馆图片" required class="admin-modal-form__full">
        <div class="venue-cover-carousel-wrap">
          <div class="venue-cover-carousel-row__col">
            <div class="venue-cover-carousel-row__sub">科普场馆主图 <span style="color: #f53f3f">*</span></div>
            <a-space direction="vertical" fill style="width: 100%">
              <a-upload :auto-upload="false" :show-file-list="false" accept="image/*" :before-upload="onCoverSelect" @change="onCoverChange">
                <template #upload-button><a-button>上传封面</a-button></template>
              </a-upload>
              <a-typography-text type="secondary">图片尺寸推荐 1200×600</a-typography-text>
              <a-space v-if="form.cover_image" direction="vertical" align="start">
                <img
                  :src="resolvePublicMediaUrl(form.cover_image)"
                  style="width: 80px; border: 1px solid #e5e6eb; border-radius: 4px; cursor: zoom-in"
                  @error="onImageLoadError"
                  @click="openMediaPreview('image', form.cover_image)"
                />
                <a-button size="mini" status="danger" @click="removeCover">删除封面</a-button>
              </a-space>
            </a-space>
            <div v-if="formErrors.cover_image" style="color: #f53f3f; margin-top: 4px; font-size: 12px;">{{ formErrors.cover_image }}</div>
          </div>
          <div class="venue-cover-carousel-row__col">
            <div class="venue-cover-carousel-row__sub">科普场馆展示图片 <span style="color: #f53f3f">*</span></div>
            <div style="display: flex; flex-direction: column; align-items: flex-start; width: 100%">
              <div style="width: 100%; margin-bottom: 8px">
                <a-upload
                  :auto-upload="false"
                  :show-file-list="false"
                  multiple
                  accept="image/*,video/*"
                  :before-upload="onGallerySelect"
                  @change="onGalleryChange"
                >
                  <template #upload-button><a-button type="primary">新增轮播资源</a-button></template>
                </a-upload>
                <a-typography-text type="secondary" style="margin-top: 12px; display: block">图片尺寸推荐 1200×600</a-typography-text>
              </div>
              <div class="venue-gallery-grid">
                <div
                  v-for="(item, idx) in form.gallery_media"
                  :key="item.url + idx"
                  class="venue-gallery-item"
                >
                  <img
                    v-if="item.type === 'image'"
                    :src="resolvePublicMediaUrl(item.url)"
                    class="venue-gallery-thumb"
                    @error="onImageLoadError"
                    @click="openMediaPreview('image', item.url)"
                  />
                  <video
                    v-else
                    :src="resolvePublicMediaUrl(item.url)"
                    controls
                    class="venue-gallery-thumb venue-gallery-thumb--video"
                    @click.stop="openMediaPreview('video', item.url)"
                  />
                  <a-button size="mini" status="danger" @click="removeGallery(idx)">删除</a-button>
                </div>
              </div>
            </div>
            <div v-if="formErrors.gallery_media" style="color: #f53f3f; margin-top: 4px; font-size: 12px;">{{ formErrors.gallery_media }}</div>
          </div>
        </div>
      </a-form-item>
      <a-form-item label="预约须知" class="admin-modal-form__full">
        <RichEditorField
          v-model="form.reservation_notice"
          :editor-options="reservationEditorOptions"
          field-key="venue-reservation"
          :key="`venue-reservation-${editorRenderKey}`"
        />
      </a-form-item>
      <a-form-item label="场馆简介" required class="admin-modal-form__full">
        <template #help>
          <span v-if="formErrors.detail_html" style="color: #f53f3f;">{{ formErrors.detail_html }}</span>
        </template>
        <RichEditorField
          v-model="form.detail_html"
          :editor-options="detailEditorOptions"
          field-key="venue-detail"
          :min-height="260"
          :key="`venue-detail-${editorRenderKey}`"
        />
      </a-form-item>
    </a-form>
  </a-modal>

  <a-modal v-model:visible="mediaPreviewVisible" title="媒体预览" width="72%" :footer="false">
    <img
      v-if="mediaPreviewType === 'image' && mediaPreviewUrl"
      :src="mediaPreviewUrl"
      style="display: block; max-width: 100%; max-height: 72vh; margin: 0 auto"
      @error="onImageLoadError"
    />
    <video
      v-else-if="mediaPreviewType === 'video' && mediaPreviewUrl"
      :src="mediaPreviewUrl"
      controls
      autoplay
      style="display: block; width: 100%; max-height: 72vh"
    />
  </a-modal>

  <a-modal
    v-model:visible="mapVisible"
    title="地图选点（腾讯地图）"
    width="70%"
    :body-style="modalBodyStyle"
    :on-before-ok="confirmMapPick"
  >
    <a-space style="margin-bottom: 12px; width: 100%">
      <a-input v-model="mapKeyword" placeholder="输入关键词搜索，如：苏州博物馆" style="width: 420px" />
      <a-button type="primary" :loading="mapLoading" @click="searchMapKeyword">搜索</a-button>
    </a-space>
    <div style="display: flex; gap: 12px">
      <div ref="mapContainerRef" style="height: 520px; flex: 1; border: 1px solid #e5e6eb" />
      <div style="width: 280px; height: 520px; overflow: auto; border: 1px solid #e5e6eb; padding: 8px">
        <div style="margin-bottom: 8px; color: #86909c">搜索结果</div>
        <a-list size="small">
          <a-list-item v-for="(item, idx) in mapResults" :key="idx" @click="pickSearchResult(item)">
            <a-typography-paragraph :ellipsis="{ rows: 1 }">{{ item.title }}</a-typography-paragraph>
            <a-typography-text type="secondary">{{ item.address }}</a-typography-text>
          </a-list-item>
        </a-list>
      </div>
    </div>
    <a-alert v-if="pickedPoint" type="info" style="margin-top: 12px">
      已选坐标：{{ pickedPoint.lng }}, {{ pickedPoint.lat }}<br />
      地址：{{ pickedPoint.address || '-' }}
    </a-alert>
  </a-modal>
</template>

<style scoped>
.venue-address-coord-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  width: 100%;
  align-items: center;
}
.venue-address-coord-row__address {
  flex: 1 1 45%;
  min-width: 320px;
  max-width: 100%;
}
.venue-address-coord-row__lng,
.venue-address-coord-row__lat {
  width: 200px;
  flex: 1 1 180px;
  min-width: 180px;
}
.venue-address-coord-row__map {
  flex-shrink: 0;
}

.venue-form-split-label {
  font-size: 13px;
  font-weight: 500;
  color: var(--color-text-2);
  margin-bottom: 8px;
}

.venue-cover-carousel-wrap {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  width: 100%;
  align-items: flex-start;
}
.venue-cover-carousel-row__col {
  flex: 1 1 320px;
  min-width: min(100%, 320px);
}
.venue-cover-carousel-row__sub {
  font-weight: 500;
  margin-bottom: 8px;
  color: var(--color-text-1);
}
.venue-gallery-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  width: 100%;
  align-items: flex-start;
}
.venue-gallery-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}
.venue-gallery-thumb {
  width: 80px;
  height: 50px;
  object-fit: cover;
  border: 1px solid #e5e6eb;
  border-radius: 4px;
  cursor: zoom-in;
}
.venue-gallery-thumb--video {
  display: block;
}
</style>
