import { useEffect, useMemo, useRef, useState } from 'react'
import Taro, { useDidShow } from '@tarojs/taro'
import { View, Text, Picker } from '@tarojs/components'
import {
  Button,
  Cell,
  CellGroup,
  ConfigProvider,
  Empty,
  Input,
  InputNumber,
  Popup,
  Space
} from '@nutui/nutui-react-taro'
import { ArrowRight, Location, Ticket } from '@nutui/icons-react-taro'
import dayjs, { type Dayjs } from 'dayjs'
import type { ShopGoods } from '@/api/shop/shopGoods/model'
import { getShopGoods } from '@/api/shop/shopGoods'
import { getShopUserAddress, listShopUserAddress } from '@/api/shop/shopUserAddress'
import type { ShopUserAddress } from '@/api/shop/shopUserAddress/model'
import './use.scss'
import Gap from "@/components/Gap";
import OrderConfirmSkeleton from "@/components/OrderConfirmSkeleton";
import type {ShopStore} from "@/api/shop/shopStore/model";
import {getShopStore, listShopStore} from "@/api/shop/shopStore";
import {getSelectedStoreFromStorage, saveSelectedStoreToStorage} from "@/utils/storeSelection";
import type { GltUserTicket } from '@/api/glt/gltUserTicket/model'
import { getGltUserTicket, listGltUserTicket } from '@/api/glt/gltUserTicket'
import { getGltTicketTemplate, getGltTicketTemplateByGoodsId } from '@/api/glt/gltTicketTemplate'
import { addGltTicketOrder, getGltTicketOrder, updateGltTicketOrder } from '@/api/glt/gltTicketOrder'
import type { GltTicketOrder } from '@/api/glt/gltTicketOrder/model'
import type { ShopStoreRider } from '@/api/shop/shopStoreRider/model'
import { listShopStoreRider } from '@/api/shop/shopStoreRider'
import type { ShopStoreFence } from '@/api/shop/shopStoreFence/model'
import { listShopStoreFence } from '@/api/shop/shopStoreFence'
import { parseFencePoints, parseLngLatFromText, pointInAnyPolygon, pointInPolygon } from '@/utils/geofence'

const DEFAULT_MIN_START_QTY = 10

const OrderConfirm = () => {
  const [goods, setGoods] = useState<ShopGoods | null>(null);
  const [address, setAddress] = useState<ShopUserAddress>()
  const [minStartQty, setMinStartQty] = useState<number>(DEFAULT_MIN_START_QTY)
  const [quantity, setQuantity] = useState<number>(DEFAULT_MIN_START_QTY)
  const [orderRemark, setOrderRemark] = useState<string>('')
  // Delivery date only (no hour/min selection).
  const [sendTime, setSendTime] = useState<Date>(() => dayjs().startOf('day').toDate())
  const [loading, setLoading] = useState<boolean>(true)
  const [error, setError] = useState<string>('')
  const [submitLoading, setSubmitLoading] = useState<boolean>(false)
  const loadAllDataLoadingRef = useRef(false)
  const hasInitialLoadedRef = useRef(false)

  // InputNumber 主题配置
  const customTheme = {
    nutuiInputnumberButtonWidth: '28px',
    nutuiInputnumberButtonHeight: '28px',
    nutuiInputnumberInputWidth: '40px',
    nutuiInputnumberInputHeight: '28px',
    nutuiInputnumberInputBorderRadius: '4px',
    nutuiInputnumberButtonBorderRadius: '4px',
  }

  // 门店选择：用于在下单页展示当前“已选门店”，并允许用户切换（写入 SelectedStore Storage）
  const [storePopupVisible, setStorePopupVisible] = useState(false)
  const [stores, setStores] = useState<ShopStore[]>([])
  const [storeLoading, setStoreLoading] = useState(false)
  const [selectedStore, setSelectedStore] = useState<ShopStore | null>(getSelectedStoreFromStorage())
  const storeAutoPickingRef = useRef(false)
  const storeManualPickedRef = useRef(false)

  // 水票：用于“立即送水”下单（用水票抵扣，无需支付）
  const [tickets, setTickets] = useState<GltUserTicket[]>([])
  const [ticketPopupVisible, setTicketPopupVisible] = useState(false)
  const [ticketLoading, setTicketLoading] = useState(false)
  const [ticketLoaded, setTicketLoaded] = useState(false)
  const noTicketPromptedRef = useRef(false)
  const ticketAutoRetryCountRef = useRef(0)
  const ticketAutoRetryTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)

  // Delivery range (geofence): block ordering if address/current location is outside.
  const [fences, setFences] = useState<ShopStoreFence[]>([])
  const fencesLoadedRef = useRef(false)
  const fencesPromiseRef = useRef<Promise<ShopStoreFence[]> | null>(null)
  const fencesErrorRef = useRef<Error | null>(null)
  const [deliveryRangeChecking, setDeliveryRangeChecking] = useState(false)
  const deliveryRangeCheckingRef = useRef(false)
  const [inDeliveryRange, setInDeliveryRange] = useState<boolean | undefined>(undefined)
  // Prevent using stale `inDeliveryRange` from a previous address when user switches addresses.
  const [deliveryRangeCheckedAddressId, setDeliveryRangeCheckedAddressId] = useState<number | undefined>(undefined)

  // 配送方式：elevator(电梯) / stairs(步梯) / groundFloor(一楼商铺/其他)
  const [deliveryMethod, setDeliveryMethod] = useState<string>('')
  // 步梯是否需要送上楼（null=未选择）
  const [needCarryUpstairs, setNeedCarryUpstairs] = useState<boolean | null>(null)
  // 楼层（从2开始，需要送上楼时选择）
  const [deliveryFloor, setDeliveryFloor] = useState<number>(2)
  // 楼层选择弹窗
  const [floorPickerVisible, setFloorPickerVisible] = useState(false)

  // 计算配送费：每桶每层1元，第1层不收费
  const getDeliveryFee = () => {
    if (deliveryMethod !== 'stairs' || !needCarryUpstairs) return 0
    if (deliveryFloor <= 1) return 0
    return displayQty * (deliveryFloor - 1)
  }

  const router = Taro.getCurrentInstance().router;
  const goodsId = router?.params?.goodsId;
  const orderId = router?.params?.orderId;
  const numericGoodsId = useMemo(() => {
    const n = goodsId ? Number(goodsId) : undefined
    return typeof n === 'number' && Number.isFinite(n) ? n : undefined
  }, [goodsId])

  const numericOrderId = useMemo(() => {
    const n = orderId ? Number(orderId) : undefined
    return typeof n === 'number' && Number.isFinite(n) && n > 0 ? n : undefined
  }, [orderId])

  const isEditMode = !!numericOrderId
  const [editingOrder, setEditingOrder] = useState<GltTicketOrder | null>(null)
  const editingInitRef = useRef(false)

  const userId = useMemo(() => {
    const raw = Taro.getStorageSync('UserId')
    const id = Number(raw)
    return Number.isFinite(id) && id > 0 ? id : undefined
  }, [])

  const parseTime = (raw?: unknown) => {
    if (raw === undefined || raw === null || raw === '') return null
    // Compatible with seconds/milliseconds timestamps.
    if (typeof raw === 'number' || (typeof raw === 'string' && /^\d+$/.test(raw))) {
      const n = Number(raw)
      if (!Number.isFinite(n)) return null
      return dayjs(n < 1e12 ? n * 1000 : n)
    }
    const d = dayjs(raw as any)
    return d.isValid() ? d : null
  }

  const clampSendDateToToday = (d: Dayjs) => {
    const today = dayjs().startOf('day')
    if (!d.isValid()) return today
    return d.isBefore(today, 'day') ? today : d.startOf('day')
  }

  const isPendingDeliveryOrder = (o?: Partial<GltTicketOrder> | null) => {
    if (!o) return false
    const ds = (o as any)?.deliveryStatus
    const hasProgress = !!o.sendStartTime || !!o.sendEndTime || !!o.receiveConfirmTime
    return (
      Number((o as any)?.deleted) !== 1 &&
      Number(o.status) !== 1 &&
      !hasProgress &&
      (ds === 10 || (typeof ds !== 'number' && !!o.riderId))
    )
  }

  const getTicketAvailableQty = (t?: Partial<GltUserTicket> | null) => {
    if (!t) return 0
    const anyT: any = t
    const raw =
      anyT.availableQty ??
      anyT.availableNum ??
      anyT.availableCount ??
      anyT.remainQty ??
      anyT.remainNum ??
      anyT.remainCount
    const n = Number(raw)
    if (Number.isFinite(n)) return n

    // Fallback for tenants that don't return `availableQty`.
    const total = Number(anyT.totalQty ?? anyT.totalNum ?? anyT.totalCount ?? 0)
    const used = Number(anyT.usedQty ?? anyT.usedNum ?? anyT.usedCount ?? 0)
    const frozen = Number(anyT.frozenQty ?? anyT.frozenNum ?? anyT.frozenCount ?? 0)
    const computed = (Number.isFinite(total) ? total : 0) - (Number.isFinite(used) ? used : 0) - (Number.isFinite(frozen) ? frozen : 0)
    return Number.isFinite(computed) ? computed : 0
  }

  const usableTickets = useMemo(() => {
    const list = (tickets || [])
      .filter(t => Number(t?.deleted) !== 1)
      // 1 = 冻结（兼容 status 为字符串）
      .filter(t => Number(t?.status) !== 1)
      .filter(t => Number.isFinite(Number(t?.id)) && Number(t?.id) > 0)
      .filter(t => getTicketAvailableQty(t) > 0)
      // Some tenants return goodsId as string; coerce before comparison.
      .filter((t) => {
        if (!numericGoodsId) return true
        const tg = Number((t as any)?.goodsId)
        const hasGoodsId = Number.isFinite(tg) && tg > 0
        return !hasGoodsId || tg === numericGoodsId
      })
    // Default order in list: older first (reduce disputes). Real consumption order is computed separately.
    return list.sort((a, b) => {
      const ta = new Date(a.createTime || 0).getTime() || 0
      const tb = new Date(b.createTime || 0).getTime() || 0
      if (ta !== tb) return ta - tb
      return (a.id || 0) - (b.id || 0)
    })
  }, [tickets, numericGoodsId])

  const availableTicketTotal = useMemo(() => {
    return usableTickets.reduce((sum, t) => sum + getTicketAvailableQty(t), 0)
  }, [usableTickets])

  // Consume tickets with smaller available qty first; ties: older first.
  const ticketsToConsume = useMemo(() => {
    const list = [...usableTickets]
    return list.sort((a, b) => {
      const qa = getTicketAvailableQty(a)
      const qb = getTicketAvailableQty(b)
      if (qa !== qb) return qa - qb
      const ta = new Date(a.createTime || 0).getTime() || 0
      const tb = new Date(b.createTime || 0).getTime() || 0
      if (ta !== tb) return ta - tb
      return (a.id || 0) - (b.id || 0)
    })
  }, [usableTickets])

  const noUsableTickets = useMemo(() => {
    // Only show "go buy tickets" guidance after we have finished loading.
    return !!userId && ticketLoaded && !ticketLoading && usableTickets.length === 0
  }, [ticketLoaded, ticketLoading, usableTickets.length, userId])

  // After buying tickets and redirecting here, some backends may issue tickets asynchronously.
  // If opened with a `goodsId`, retry a few times to refresh tickets.
  useEffect(() => {
    if (isEditMode) return
    if (!numericGoodsId) return
    if (!ticketLoaded || ticketLoading) return

    if (usableTickets.length > 0) {
      ticketAutoRetryCountRef.current = 0
      return
    }

    if (ticketAutoRetryCountRef.current >= 4) return
    if (ticketAutoRetryTimerRef.current) return

    const delays = [800, 1500, 2500, 4000]
    const delay = delays[ticketAutoRetryCountRef.current] ?? 2500
    ticketAutoRetryCountRef.current += 1
    ticketAutoRetryTimerRef.current = setTimeout(async () => {
      ticketAutoRetryTimerRef.current = null
      await loadUserTickets()
    }, delay)
  }, [isEditMode, numericGoodsId, ticketLoaded, ticketLoading, usableTickets.length])

  useEffect(() => {
    return () => {
      if (ticketAutoRetryTimerRef.current) {
        clearTimeout(ticketAutoRetryTimerRef.current)
        ticketAutoRetryTimerRef.current = null
      }
    }
  }, [])

  const maxQuantity = useMemo(() => {
    const stockMax = goods?.stock ?? 999
    if (!isEditMode) return Math.max(0, Math.min(stockMax, availableTicketTotal))

    const original = Number(editingOrder?.totalNum ?? 0)
    const originalSafe = Number.isFinite(original) ? original : 0
    const ticketId = Number(editingOrder?.userTicketId ?? 0)
    const ticketIdSafe = Number.isFinite(ticketId) && ticketId > 0 ? ticketId : undefined
    const rawTicket = ticketIdSafe ? (tickets || []).find(t => Number(t?.id) === ticketIdSafe) : undefined
    if (!rawTicket) return Math.max(0, Math.min(stockMax, originalSafe))

    const avail = getTicketAvailableQty(rawTicket)
    const upper = Math.max(0, avail + originalSafe)
    return Math.max(0, Math.min(stockMax, upper))
  }, [availableTicketTotal, editingOrder?.totalNum, editingOrder?.userTicketId, goods?.stock, isEditMode, tickets])

  const canStartOrder = useMemo(() => {
    return maxQuantity >= minStartQty
  }, [maxQuantity, minStartQty])

  const displayQty = useMemo(() => {
    if (!canStartOrder) return 0
    return Math.max(minStartQty, Math.min(quantity, maxQuantity))
  }, [quantity, maxQuantity, canStartOrder, minStartQty])

  const sendTimeText = useMemo(() => {
    return dayjs(sendTime).format('YYYY-MM-DD')
  }, [sendTime])

  const distanceMeters = (a: { lng: number; lat: number }, b: { lng: number; lat: number }) => {
    const toRad = (x: number) => (x * Math.PI) / 180
    const R = 6371000 // meters
    const dLat = toRad(b.lat - a.lat)
    const dLng = toRad(b.lng - a.lng)
    const lat1 = toRad(a.lat)
    const lat2 = toRad(b.lat)
    const sin1 = Math.sin(dLat / 2)
    const sin2 = Math.sin(dLng / 2)
    const h = sin1 * sin1 + Math.cos(lat1) * Math.cos(lat2) * sin2 * sin2
    return 2 * R * Math.asin(Math.min(1, Math.sqrt(h)))
  }

  const parseStoreCoords = (s: ShopStore) => {
    return parseLngLatFromText((s.lngAndLat || s.location || '').trim())
  }

  const openAddressPage = async () => {
    if (isEditMode) {
      if (!editingOrder?.id) {
        Taro.showToast({ title: '订单信息加载中，请稍后重试', icon: 'none' })
        return
      }
      if (!isPendingDeliveryOrder(editingOrder)) {
        Taro.showToast({ title: '该订单当前不可修改', icon: 'none' })
        return
      }
    }
    Taro.navigateTo({ url: '/user/address/index' })
  }

  const loadFences = async (): Promise<ShopStoreFence[]> => {
    if (fencesLoadedRef.current) return fences
    if (fencesPromiseRef.current) return fencesPromiseRef.current

    fencesPromiseRef.current = (async () => {
      try {
        const list = await listShopStoreFence()
        const safe = Array.isArray(list) ? list : []
        setFences(safe)
        fencesErrorRef.current = null
        fencesLoadedRef.current = true
        return safe
      } catch (e) {
        console.error('获取电子围栏失败:', e)
        setFences([])
        fencesErrorRef.current = e instanceof Error ? e : new Error('获取电子围栏失败')
        fencesLoadedRef.current = true
        return []
      } finally {
        fencesPromiseRef.current = null
      }
    })()

    return fencesPromiseRef.current
  }

  const isPointInFence = async (p: { lng: number; lat: number }): Promise<boolean> => {
    const list = fencesLoadedRef.current ? fences : await loadFences()
    if (!list.length) {
      // If we failed to fetch fences, block ordering (can't verify delivery range).
      if (fencesErrorRef.current) throw fencesErrorRef.current
      // No fence configured => do not block.
      return true
    }
    const active = (list || [])
      .filter(f => f?.status === 0 || f?.status === undefined)
    const polygons = active
      .map(f => parseFencePoints(f?.points))
      .filter(poly => poly.length >= 3)

    if (!polygons.length) {
      // If backend has active fence rows but points can't be parsed, block and surface the issue.
      const hasPointsText = active.some(f => String(f?.points || '').trim().length > 0)
      if (hasPointsText) throw new Error('电子围栏数据异常，请联系管理员配置')
      // No usable polygon configured => do not block.
      return true
    }

    return pointInAnyPolygon(p, polygons)
  }

  const getCheckPoint = async (): Promise<{ lng: number; lat: number }> => {
    // Immediate water delivery must validate by the delivery address coordinates.
    // Falling back to current GPS may allow ordering with an out-of-fence address.
    const byAddress = parseLngLatFromText(`${address?.lng || ''},${address?.lat || ''}`)
    if (byAddress) return byAddress
    throw new Error('该收货地址缺少经纬度，请在地址里选择地图定位后重试')
  }

  const ensureInDeliveryRange = async (): Promise<boolean> => {
    if (deliveryRangeCheckingRef.current) return false
    deliveryRangeCheckingRef.current = true
    setDeliveryRangeChecking(true)
    try {
      const p = await getCheckPoint()
      const ok = await isPointInFence(p)
      setInDeliveryRange(ok)
      setDeliveryRangeCheckedAddressId(address?.id)
      if (!ok) {
        Taro.showToast({ title: '不在配送范围内，暂不支持下单', icon: 'none' })
      }
      return ok
    } catch (e: any) {
      console.error('配送范围校验失败:', e)
      setInDeliveryRange(undefined)
      setDeliveryRangeCheckedAddressId(undefined)
      // Note: we validate by address coords only; no GPS permission prompt here.

      Taro.showToast({ title: e?.message || '配送范围校验失败，请稍后重试', icon: 'none' })
      return false
    } finally {
      setDeliveryRangeChecking(false)
      deliveryRangeCheckingRef.current = false
    }
  }

  const loadStores = async (): Promise<ShopStore[]> => {
    if (storeLoading) return stores
    try {
      setStoreLoading(true)
      const list = await listShopStore()
      const usable = (list || []).filter(s => s?.isDelete !== 1)
      setStores(usable)
      return usable
    } catch (e) {
      console.error('获取门店列表失败:', e)
      setStores([])
      Taro.showToast({title: '获取门店列表失败', icon: 'none'})
      return []
    } finally {
      setStoreLoading(false)
    }
  }

  const ensureStoreDetail = async (s: ShopStore): Promise<ShopStore> => {
    if (!s?.id) return s
    // If backend already returned "delivery area polygon"/warehouse info, skip extra request.
    if (s.points || s.warehouseId) return s
    try {
      const full = await getShopStore(s.id)
      return full || s
    } catch (_e) {
      return s
    }
  }

  const pickNearestStore = async (p: { lng: number; lat: number }, list: ShopStore[]) => {
    const usable = (list || []).filter(s => s?.isDelete !== 1)
    if (!usable.length) return null

    // 1) If a store has a polygon (points) and the delivery point is inside, prefer those stores.
    const inside: { s: ShopStore; d?: number }[] = []
    const outside: { s: ShopStore; d?: number }[] = []
    for (const s of usable) {
      const poly = parseFencePoints(s?.points)
      const isInside = poly.length >= 3 ? pointInPolygon(p, poly) : false
      const coords = parseStoreCoords(s)
      const d = coords ? distanceMeters(p, coords) : undefined
      ;(isInside ? inside : outside).push({ s, d })
    }

    const sortByDistanceThenSortNo = (a: { s: ShopStore; d?: number }, b: { s: ShopStore; d?: number }) => {
      const da = a.d ?? Number.POSITIVE_INFINITY
      const db = b.d ?? Number.POSITIVE_INFINITY
      if (da !== db) return da - db
      const sa = a.s.sortNumber ?? Number.POSITIVE_INFINITY
      const sb = b.s.sortNumber ?? Number.POSITIVE_INFINITY
      if (sa !== sb) return sa - sb
      return (a.s.id || 0) - (b.s.id || 0)
    }

    const best = (inside.length ? inside.sort(sortByDistanceThenSortNo) : outside.sort(sortByDistanceThenSortNo))[0]?.s
    if (!best) return null
    return ensureStoreDetail(best)
  }

  const resolveStoreForOrder = async (opts?: { silent?: boolean }): Promise<ShopStore | null> => {
    // If user already picked a store on this page, don't auto override.
    if (storeManualPickedRef.current && selectedStore?.id) return selectedStore

    const p = parseLngLatFromText(`${address?.lng || ''},${address?.lat || ''}`)
    if (!p) {
      // If we already have a selected store, allow ordering to continue (can't auto-pick though).
      if (!opts?.silent && !selectedStore?.id) {
        Taro.showToast({ title: '该地址缺少经纬度，无法自动分配门店，请在地址里选择地图定位后重试', icon: 'none' })
      }
      return selectedStore?.id ? selectedStore : null
    }

    if (storeAutoPickingRef.current) return selectedStore?.id ? selectedStore : null
    storeAutoPickingRef.current = true
    try {
      const list = stores.length ? stores : await loadStores()

      // If a store is already selected and its polygon contains the point, keep it.
      if (selectedStore?.id) {
        const full = await ensureStoreDetail(selectedStore)
        const poly = parseFencePoints(full?.points)
        if (poly.length >= 3 && pointInPolygon(p, poly)) {
          if (full !== selectedStore) {
            setSelectedStore(full)
            saveSelectedStoreToStorage(full)
          }
          return full
        }
      }

      const best = await pickNearestStore(p, list)
      if (best?.id) {
        setSelectedStore(best)
        saveSelectedStoreToStorage(best)
        return best
      }
      return selectedStore?.id ? selectedStore : null
    } finally {
      storeAutoPickingRef.current = false
    }
  }

  const resolveAutoRiderForStore = async (storeId: number): Promise<ShopStoreRider | null> => {
    try {
      const list = await listShopStoreRider({ storeId, status: 1 })
      const usable = (list || [])
        .filter(r => r?.isDelete !== 1)
        // Prefer enabled/online/auto-dispatch riders; fallback to any usable rider.
        .sort((a, b) => {
          const score = (r: ShopStoreRider) => {
            const enabled = r.status === 1 ? 1000 : 0
            const online = r.workStatus === 1 ? 100 : (r.workStatus === 2 ? 50 : 0)
            const auto = r.autoDispatchEnabled === 1 ? 10 : 0
            const prio = Number(r.dispatchPriority || 0)
            return enabled + online + auto + prio
          }
          return score(b) - score(a)
        })

      return usable[0] || null
    } catch (e) {
      console.warn('自动获取配送员失败，将由后台/门店人工派单:', e)
      return null
    }
  }

  // @ts-ignore
  const openStorePopup = async () => {
    setStorePopupVisible(true)
    if (!stores.length) {
      await loadStores()
    }
  }

  // 处理数量变化
  const handleQuantityChange = (value: string | number) => {
    const parsed = typeof value === 'string' ? parseInt(value) : value
    const newQuantity = Number.isFinite(parsed) ? Number(parsed) : 0
    const upper = maxQuantity
    if (!canStartOrder || upper <= 0) {
      setQuantity(0)
      return
    }
    setQuantity(Math.max(minStartQty, Math.min(newQuantity || minStartQty, upper)))
  }

  const loadUserTickets = async () => {
    if (ticketLoading) return
    if (!userId) {
      setTickets([])
      setTicketLoaded(false)
      return
    }
    try {
      setTicketLoading(true)
      // Do not pass `status` here: some backends use different status semantics;
      // we filter out frozen tickets on the client for compatibility.
      const list = await listGltUserTicket({ userId })
      setTickets(list || [])
    } catch (e) {
      console.error('获取水票失败:', e)
      setTickets([])
      Taro.showToast({ title: '获取水票失败', icon: 'none' })
    } finally {
      setTicketLoading(false)
      setTicketLoaded(true)
    }
  }

  const goBuyTickets = async () => {
    try {
      setTicketPopupVisible(false)
      // If this page is opened with a goodsId, guide user back to that goods detail to purchase.
      if (numericGoodsId) {
        await Taro.navigateTo({ url: `/shop/goodsDetail/index?id=${numericGoodsId}` })
        return
      }
      await Taro.switchTab({ url: '/pages/index/index' })
    } catch (e) {
      console.error('跳转购买水票失败:', e)
      Taro.showToast({ title: '跳转失败，请稍后重试', icon: 'none' })
    }
  }

  const onSubmit = async () => {
    if (submitLoading) return
    if (deliveryRangeCheckingRef.current) return

    // 基础校验
    if (!userId) {
      Taro.showToast({ title: '请先登录', icon: 'none' })
      return
    }
    if (isEditMode && !editingOrder?.id) {
      Taro.showToast({ title: '订单信息加载中，请稍后重试', icon: 'none' })
      return
    }
    if (isEditMode && !isPendingDeliveryOrder(editingOrder)) {
      Taro.showToast({ title: '该订单当前不可修改', icon: 'none' })
      return
    }
    if (!address?.id) {
      Taro.showToast({ title: '请选择收货地址', icon: 'none' })
      return
    }

    // 配送方式校验（必选）
    if (!deliveryMethod) {
      Taro.showToast({ title: '请选择配送方式', icon: 'none' })
      return
    }

    // 步梯场景：必须选择是否送上楼
    if (deliveryMethod === 'stairs' && needCarryUpstairs === null) {
      Taro.showToast({ title: '请选择是否需要送上楼', icon: 'none' })
      return
    }

    if (!addressHasCoords) {
      Taro.showToast({ title: '该收货地址缺少经纬度，请在地址里选择地图定位后重试', icon: 'none' })
      return
    }

    // Ensure ticket list is loaded.
    if (ticketLoading) {
      Taro.showToast({ title: '水票加载中，请稍后再试', icon: 'none' })
      return
    }
    if (!ticketLoaded) {
      await loadUserTickets()
    }

    const storeForOrder = await resolveStoreForOrder()
    if (!storeForOrder?.id) {
      Taro.showToast({ title: '未找到可配送门店，请先在首页选择门店或联系管理员配置门店坐标', icon: 'none' })
      return
    }
    if (!isEditMode && availableTicketTotal <= 0) {
      Taro.showToast({ title: '暂无可用水票', icon: 'none' })
      return
    }

    const finalQty = displayQty
    if (finalQty <= 0) {
      Taro.showToast({ title: '请选择送水数量', icon: 'none' })
      return
    }
    if (!isEditMode && finalQty > availableTicketTotal) {
      Taro.showToast({ title: '水票可用次数不足', icon: 'none' })
      return
    }
    if (isEditMode && finalQty > maxQuantity) {
      Taro.showToast({ title: '水票可用次数不足', icon: 'none' })
      return
    }
    if (goods?.stock !== undefined && finalQty > goods.stock) {
      Taro.showToast({ title: '商品库存不足', icon: 'none' })
      return
    }
    if (finalQty < minStartQty) {
      Taro.showToast({ title: `最低起送 ${minStartQty} 桶`, icon: 'none' })
      return
    }
    if (!sendTime) {
      Taro.showToast({ title: '请选择配送时间', icon: 'none' })
      return
    }
    if (dayjs(sendTime).isBefore(dayjs().startOf('day'), 'day')) {
      Taro.showToast({ title: '配送时间不能早于今天', icon: 'none' })
      setSendTime(dayjs().startOf('day').toDate())
      return
    }

    // 配送范围校验（电子围栏）
    const ok = await ensureInDeliveryRange()
    if (!ok) return

    const deliveryFee = getDeliveryFee()
    const confirmContent = isEditMode
      ? `配送时间：${sendTimeText}\n送水数量：${finalQty} 桶\n配送方式：${deliveryMethod === 'elevator' ? '电梯' : deliveryMethod === 'stairs' ? '步梯' : '一楼商铺/其他'}${deliveryMethod === 'stairs' && needCarryUpstairs && deliveryFloor > 1 ? `（${deliveryFloor}楼）` : deliveryMethod === 'stairs' && !needCarryUpstairs ? '（不送上楼）' : ''}\n${deliveryFee > 0 ? `配送费：￥${deliveryFee.toFixed(2)}\n` : ''}是否确认修改？`
      : `配送时间：${sendTimeText}\n配送方式：${deliveryMethod === 'elevator' ? '电梯' : deliveryMethod === 'stairs' ? '步梯' : '一楼商铺/其他'}${deliveryMethod === 'stairs' && needCarryUpstairs && deliveryFloor > 1 ? `（${deliveryFloor}楼）` : deliveryMethod === 'stairs' && !needCarryUpstairs ? '（不送上楼）' : ''}\n${deliveryFee > 0 ? `配送费：￥${deliveryFee.toFixed(2)}\n` : ''}将使用 ${finalQty} 张水票下单（优先使用可用数量少的水票），送水 ${finalQty} 桶，是否确认？`

    const confirmRes = await Taro.showModal({
      title: isEditMode ? '确认修改' : '确认下单',
      content: confirmContent
    })
    if (!confirmRes.confirm) return

    try {
      setSubmitLoading(true)
      Taro.showLoading({ title: '提交中...' })

      if (isEditMode) {
        await updateGltTicketOrder({
          id: editingOrder?.id,
          addressId: address.id,
          totalNum: finalQty,
          buyerRemarks: orderRemark,
          sendTime: dayjs(sendTime).startOf('day').format('YYYY-MM-DD HH:mm:ss'),
          deliveryMethod,
          deliveryFloor: deliveryMethod === 'stairs' && needCarryUpstairs ? deliveryFloor : undefined,
          deliveryFee: getDeliveryFee() || undefined
        })
      } else {
        // Best-effort auto dispatch rider. If it fails, backend/manual dispatch can still handle it.
        const autoRider = storeForOrder.id ? await resolveAutoRiderForStore(storeForOrder.id) : null

        // Split this "delivery" into multiple ticket orders (backend order model binds to one userTicketId).
        // Consume tickets with smaller available qty first.
        let remain = finalQty
        for (const t of ticketsToConsume) {
          if (remain <= 0) break
          const avail = getTicketAvailableQty(t)
          const useQty = Math.min(remain, avail)
          if (useQty <= 0) continue
          await addGltTicketOrder({
            userTicketId: Number(t.id),
            storeId: storeForOrder.id,
            addressId: address.id,
            totalNum: useQty,
            buyerRemarks: orderRemark,
            sendTime: dayjs(sendTime).startOf('day').format('YYYY-MM-DD HH:mm:ss'),
            // Backend may take userId from token; pass-through is harmless if backend ignores it.
            userId,
            riderId: Number.isFinite(Number(autoRider?.userId)) ? Number(autoRider?.userId) : undefined,
            riderName: autoRider?.realName,
            riderPhone: autoRider?.mobile,
            comments: goods?.name ? `立即送水：${goods.name}` : '立即送水',
            // 配送方式信息
            deliveryMethod,
            deliveryFloor: deliveryMethod === 'stairs' && needCarryUpstairs ? deliveryFloor : undefined,
            deliveryFee: getDeliveryFee() || undefined
          })
          remain -= useQty
        }

        if (remain > 0) {
          // Ticket counts might have changed between loading and submission.
          throw new Error('水票可用次数不足，请刷新后重试')
        }
      }

      await loadUserTickets()

      Taro.showToast({ title: isEditMode ? '修改成功' : '下单成功', icon: 'success' })
      setTimeout(() => {
        // 跳转到“我的送水订单”
        Taro.redirectTo({ url: '/user/ticket/index?tab=order' })
      }, 800)
    } catch (e: any) {
      console.error(isEditMode ? '送水订单修改失败:' : '水票下单失败:', e)
      Taro.showToast({ title: e?.message || (isEditMode ? '修改失败' : '下单失败'), icon: 'none' })
    } finally {
      Taro.hideLoading()
      setSubmitLoading(false)
    }
  }

  // 统一的数据加载函数
  const loadAllData = async (opts?: { silent?: boolean }) => {
    if (loadAllDataLoadingRef.current) return
    loadAllDataLoadingRef.current = true
    try {
      if (!opts?.silent) setLoading(true)
      setError('')

      const [addressRes, editingOrderRes, goodsByParam] = await Promise.all([
        listShopUserAddress({ isDefault: true }),
        numericOrderId ? getGltTicketOrder(numericOrderId) : Promise.resolve(null),
        numericGoodsId ? getShopGoods(numericGoodsId) : Promise.resolve(null)
      ])

      let goodsRes = goodsByParam
      if (!goodsRes && editingOrderRes?.userTicketId) {
        const ticketId = Number(editingOrderRes.userTicketId)
        if (Number.isFinite(ticketId) && ticketId > 0) {
          try {
            const ticket = await getGltUserTicket(ticketId)
            const gid = Number((ticket as any)?.goodsId)
            if (Number.isFinite(gid) && gid > 0) {
              goodsRes = await getShopGoods(gid)
            }
          } catch (e) {
            console.error('加载订单关联商品失败:', e)
          }
        }
      }

      // 设置商品信息
      if (goodsRes) {
        setGoods(goodsRes)
        hasInitialLoadedRef.current = true
      }

      // 设置默认收货地址
      if (addressRes && addressRes.length > 0) {
        setAddress(addressRes[0])
      }

      if (numericOrderId && editingOrderRes && !editingInitRef.current) {
        editingInitRef.current = true
        setEditingOrder(editingOrderRes)
        Taro.setNavigationBarTitle({ title: '订单确认' })

        const isPending = isPendingDeliveryOrder(editingOrderRes)
        if (!isPending) {
          Taro.showToast({ title: '该订单当前不可修改', icon: 'none' })
          setTimeout(() => {
            Taro.navigateBack()
          }, 600)
          return
        }

        const initQty = Number(editingOrderRes.totalNum ?? minStartQty)
        setQuantity(Number.isFinite(initQty) && initQty > 0 ? initQty : minStartQty)
        setOrderRemark(String(editingOrderRes.buyerRemarks || ''))
        const st = parseTime(editingOrderRes.sendTime)
        if (st) setSendTime(clampSendDateToToday(st).toDate())

        // 回显配送方式
        if (editingOrderRes.deliveryMethod) {
          setDeliveryMethod(editingOrderRes.deliveryMethod)
          if (editingOrderRes.deliveryMethod === 'stairs') {
            const hasFloor = editingOrderRes.deliveryFloor && editingOrderRes.deliveryFloor > 1
            setNeedCarryUpstairs(hasFloor)
            if (hasFloor) setDeliveryFloor(editingOrderRes.deliveryFloor)
          }
        }

        const addrId = Number(editingOrderRes.addressId)
        const addrIdSafe = Number.isFinite(addrId) && addrId > 0 ? addrId : undefined
        if (addrIdSafe) {
          const hit = addressRes?.find(a => Number(a?.id) === addrIdSafe)
          if (hit?.id) {
            setAddress(hit)
          } else {
            try {
              const addr = await getShopUserAddress(addrIdSafe)
              if (addr?.id) setAddress(addr)
            } catch (e) {
              console.error('加载订单收货地址失败:', e)
            }
          }
        }
      }
      // Tickets are non-blocking for first paint; load in background.
      loadUserTickets()
    } catch (err) {
      console.error('加载数据失败:', err)
      if (opts?.silent) {
        Taro.showToast({ title: '刷新失败，请稍后重试', icon: 'none' })
      } else {
        setError('加载数据失败，请重试')
      }
    } finally {
      if (!opts?.silent) setLoading(false)
      loadAllDataLoadingRef.current = false
    }
  }

  useDidShow(() => {
    // 返回/切换到该页面时，刷新一下当前已选门店
    setSelectedStore(getSelectedStoreFromStorage())
    ticketAutoRetryCountRef.current = 0
    if (ticketAutoRetryTimerRef.current) {
      clearTimeout(ticketAutoRetryTimerRef.current)
      ticketAutoRetryTimerRef.current = null
    }
    loadAllData({ silent: hasInitialLoadedRef.current })
  })

  const addressHasCoords = useMemo(() => {
    if (!address?.id) return false
    return !!parseLngLatFromText(`${address?.lng || ''},${address?.lat || ''}`)
  }, [address?.id, address?.lng, address?.lat])

  // Auto-pick nearest store by delivery address (best-effort, won't override manual selection).
  useEffect(() => {
    if (!address?.id) return
    if (storeManualPickedRef.current) return
    resolveStoreForOrder({ silent: true })
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [address?.id, address?.lng, address?.lat])

  // Background pre-check when we already have an address coordinate (no permission prompt).
  useEffect(() => {
    let cancelled = false
    ;(async () => {
      if (!address?.id) {
        setInDeliveryRange(undefined)
        setDeliveryRangeCheckedAddressId(undefined)
        return
      }
      const p = parseLngLatFromText(`${address?.lng || ''},${address?.lat || ''}`)
      if (!p) {
        // Cannot validate without address coords -> treat as out of range to block ordering.
        setInDeliveryRange(false)
        setDeliveryRangeCheckedAddressId(address.id)
        return
      }
      // Avoid keeping stale state from previous address while we validate this one.
      setInDeliveryRange(undefined)
      setDeliveryRangeCheckedAddressId(undefined)
      let ok = true
      try {
        ok = await isPointInFence(p)
      } catch (_e) {
        // Pre-check is best-effort; don't block UI here.
        if (!cancelled) {
          setInDeliveryRange(undefined)
          setDeliveryRangeCheckedAddressId(undefined)
        }
        return
      }
      if (cancelled) return
      setInDeliveryRange(ok)
      setDeliveryRangeCheckedAddressId(address.id)
    })()
    return () => {
      cancelled = true
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [address?.id, address?.lng, address?.lat])

  // When user changes the delivery address to an out-of-fence one, prompt immediately (once per address).
  const outOfRangePromptedAddressIdRef = useRef<number | undefined>(undefined)
  useEffect(() => {
    const id = address?.id
    if (!id) return
    if (deliveryRangeCheckedAddressId !== id) return
    if (inDeliveryRange !== false) return
    if (outOfRangePromptedAddressIdRef.current === id) return
    outOfRangePromptedAddressIdRef.current = id
    Taro.showToast({ title: addressHasCoords ? '该地址不在配送范围，请更换围栏内地址' : '该地址缺少定位，请在地址里选择地图定位后重试', icon: 'none' })
  }, [
    address?.id,
    addressHasCoords,
    deliveryRangeCheckedAddressId,
    inDeliveryRange
  ])

  // When tickets/stock change, clamp quantity into [0..maxQuantity].
  useEffect(() => {
    setQuantity(prev => {
      if (maxQuantity <= 0) return 0
      if (maxQuantity < minStartQty) return 0
      if (!prev || prev < minStartQty) return minStartQty
      return Math.min(prev, maxQuantity)
    })
  }, [maxQuantity, minStartQty])

  const minStartQtyKey = useMemo(() => {
    const gid = Number(goods?.goodsId)
    if (Number.isFinite(gid) && gid > 0) return `g:${gid}`

    // If there is exactly one ticket template available, infer min start qty from it (covers "稍后再送" without goodsId).
    const ids = Array.from(
      new Set(
        (usableTickets || [])
          .map(t => Number(t?.templateId))
          .filter(id => Number.isFinite(id) && id > 0)
      )
    )
    if (ids.length === 1) return `t:${ids[0]}`
    return ''
  }, [goods?.goodsId, usableTickets])

  // Use configured min start-send qty from ticket template (by goodsId or by user's unique templateId).
  useEffect(() => {
    let cancelled = false
    ;(async () => {
      try {
        if (!minStartQtyKey) {
          setMinStartQty(DEFAULT_MIN_START_QTY)
          return
        }
        const [kind, rawId] = minStartQtyKey.split(':')
        const id = Number(rawId)
        const tpl =
          kind === 'g'
            ? await getGltTicketTemplateByGoodsId(id)
            : await getGltTicketTemplate(id)
        const n = Number(tpl?.startSendQty)
        const safe = Number.isFinite(n) && n > 0 ? n : DEFAULT_MIN_START_QTY
        if (!cancelled) setMinStartQty(safe)
      } catch (_e) {
        if (!cancelled) setMinStartQty(DEFAULT_MIN_START_QTY)
      }
    })()
    return () => {
      cancelled = true
    }
  }, [minStartQtyKey])

  // If user has no usable tickets, proactively guide them to purchase (only once per page lifecycle).
  useEffect(() => {
    if (!noUsableTickets) return
    // Editing an existing order: don't interrupt with "no tickets" prompt.
    if (isEditMode) return
    if (noTicketPromptedRef.current) return
    noTicketPromptedRef.current = true

    // ;(async () => {
    //   const r = await Taro.showModal({
    //     title: '暂无可用水票',
    //     content: '您当前没有可用水票，购买后再来下单更方便。',
    //     confirmText: '去购买',
    //     cancelText: '暂不'
    //   })
    //   if (r.confirm) {
    //     await goBuyTickets()
    //   }
    // })()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [noUsableTickets, isEditMode])

  // 重新加载数据
  const handleRetry = () => {
    loadAllData()
  }

  // 错误状态
  if (error) {
    return (
      <View className="order-confirm-page">
        <View className="error-state">
          <Text className="error-text">{error}</Text>
          <Button onClick={handleRetry}>重新加载</Button>
        </View>
      </View>
    )
  }

  // 加载状态
  if (loading) {
    return <OrderConfirmSkeleton/>
  }

  return (
    <div className={'order-confirm-page'}>
      {/*<CellGroup>*/}
      {/*  <Cell*/}
      {/*    title={(*/}
      {/*      <View className="flex items-center gap-2">*/}
      {/*        <Shop className={'text-gray-500'}/>*/}
      {/*        <Text>选择门店</Text>*/}
      {/*      </View>*/}
      {/*    )}*/}
      {/*    extra={(*/}
      {/*      <View className={'flex items-center gap-2'}>*/}
      {/*        <View className={'text-gray-900'}>*/}
      {/*          {selectedStore?.name || '请选择门店'}*/}
      {/*        </View>*/}
      {/*        <ArrowRight className={'text-gray-400'} size={14}/>*/}
      {/*      </View>*/}
      {/*    )}*/}
      {/*    onClick={openStorePopup}*/}
      {/*  />*/}
      {/*</CellGroup>*/}
	      <CellGroup>
	        {
	          address && (
	            <Cell
	              className={'address-bottom-line'}
	              onClick={openAddressPage}
	            >
              <Space>
                <Location className={'text-gray-500'}/>
                <View className={'flex flex-col w-full justify-between items-start'}>
                  <Space className={'flex flex-row w-full'}>
                    <View className={'flex-wrap text-nowrap whitespace-nowrap text-gray-500'}>送至</View>
                    <View className={'font-medium text-sm flex items-center w-full'}>
                      <View
                        style={{width: '64%'}}>{address.province} {address.city} {address.region} {address.address}</View>
                      <ArrowRight className={'text-gray-400'} size={14}/>
                    </View>
                  </Space>
	                  <View className={'pt-1 pb-3'}>
	                    <View className={'text-gray-500'}>{address.name} {address.phone}</View>
	                  </View>
	                </View>
	              </Space>
	            </Cell>
	          )
	        }
        {!address && (
          <Cell className={''} onClick={openAddressPage}>
            <Space>
              <Location/>
              添加收货地址
            </Space>
          </Cell>
        )}
      </CellGroup>

      {/* 配送方式选择（必选） */}
      <CellGroup className={'delivery-method-group'}>
        <Cell>
          <View className={'delivery-method-section'}>
            <View className={'delivery-method-label'}>
              <Text className={'font-medium text-sm'}>配送方式</Text>
              <Text className={'text-red-500 text-xs ml-1'}>*</Text>
            </View>
            <View className={'delivery-method-options'}>
              {[
                { key: 'elevator', label: '电梯', icon: '🏛️' },
                { key: 'stairs', label: '步梯', icon: '🚶' },
                { key: 'groundFloor', label: '一楼商铺/其他', icon: '🏪' },
              ].map(item => (
                <View
                  key={item.key}
                  className={`delivery-method-item ${deliveryMethod === item.key ? 'active' : ''}`}
                  onClick={() => {
                    setDeliveryMethod(item.key)
                    setNeedCarryUpstairs(null)
                    setDeliveryFloor(2)
                  }}
                >
                  <Text className={'delivery-method-icon'}>{item.icon}</Text>
                  <Text className={'text-sm'}>{item.label}</Text>
                </View>
              ))}
            </View>

            {/* 步梯：是否需要送上楼 */}
            {deliveryMethod === 'stairs' && (
              <View className={'carry-upstairs-section'}>
                <Text className={'text-sm text-gray-600 mb-2'}>是否需要送上楼？</Text>
                <View className={'carry-upstairs-options'}>
                  <View
                    className={`carry-upstairs-item ${needCarryUpstairs === true ? 'active' : ''}`}
                    onClick={() => setNeedCarryUpstairs(true)}
                  >
                    <Text>需要送上楼</Text>
                  </View>
                  <View
                    className={`carry-upstairs-item ${needCarryUpstairs === false ? 'active' : ''}`}
                    onClick={() => {
                      setNeedCarryUpstairs(false)
                      setDeliveryFloor(2)
                    }}
                  >
                    <Text>不需要</Text>
                  </View>
                </View>
              </View>
            )}

            {/* 步梯+送上楼：选择楼层 */}
            {deliveryMethod === 'stairs' && needCarryUpstairs === true && (
              <View className={'floor-select-section'}>
                <Text className={'text-sm text-gray-600'}>送至楼层</Text>
                <View
                  className={'floor-select-btn'}
                  onClick={() => setFloorPickerVisible(true)}
                >
                  <Text className={deliveryFloor > 1 ? 'text-gray-900' : 'text-gray-400'}>
                    {deliveryFloor > 1 ? `${deliveryFloor}楼` : '请选择楼层'}
                  </Text>
                  <ArrowRight className={'text-gray-400'} size={14}/>
                </View>
                {deliveryFloor > 1 && (
                  <View className={'floor-fee-tip'}>
                    <Text className={'text-xs text-orange-500'}>
                      配送费：{displayQty}桶 x {deliveryFloor - 1}层 = ￥{getDeliveryFee().toFixed(2)}
                    </Text>
                  </View>
                )}
              </View>
            )}
          </View>
        </Cell>
      </CellGroup>

      <CellGroup>
        <Cell
          title={'配送时间'}
          extra={(
            <Picker
              mode="date"
              start={dayjs().format('YYYY-MM-DD')}
              value={dayjs(sendTime).format('YYYY-MM-DD')}
              onChange={(e) => {
                const v = (e as any)?.detail?.value
                const d = dayjs(v)
                if (!d.isValid()) return
                if (d.isBefore(dayjs().startOf('day'), 'day')) {
                  Taro.showToast({ title: '配送时间不能早于今天', icon: 'none' })
                  setSendTime(dayjs().startOf('day').toDate())
                  return
                }
                setSendTime(d.startOf('day').toDate())
              }}
            >
              <View className={'flex items-center gap-2'}>
                <View className={'text-gray-900'}>{sendTimeText}</View>
                <ArrowRight className={'text-gray-400'} size={14} />
              </View>
            </Picker>
          )}
        />
      </CellGroup>

      <CellGroup>
        <Cell
          title={'送水数量'}
          description={
            canStartOrder
              ? `最低起送 ${minStartQty} 桶`
              : `最低起送 ${minStartQty} 桶（当前最多 ${maxQuantity} 桶）`
          }
          extra={(
	            <ConfigProvider theme={customTheme}>
	              <InputNumber
	                value={displayQty}
	                min={canStartOrder ? minStartQty : 0}
	                max={canStartOrder ? maxQuantity : 0}
	                step={minStartQty >= 10 ? 10 : 1}
                  readOnly
	                disabled={!canStartOrder}
	                onChange={handleQuantityChange}
	              />
	            </ConfigProvider>
	          )}
	        />
      </CellGroup>

	      <CellGroup>
	        <Cell
	          title={(
	            <View className="flex items-center gap-2">
	              <Ticket className={'text-gray-500'}/>
	              <Text>水票明细</Text>
	            </View>
	          )}
		          extra={(
		            <View className={'flex items-center gap-2'}>
		              <View className={'text-gray-900'}>
		                {ticketLoading
		                  ? '加载中...'
		                  : (ticketLoaded
		                    ? (noUsableTickets
		                      ? '暂无可用水票'
		                      : `可用合计 ${availableTicketTotal}（${usableTickets.length}组）`
		                    )
		                    : '点击查看'
		                  )
		                }
		              </View>
		              <ArrowRight className={'text-gray-400'} size={14}/>
		            </View>
		          )}
	          onClick={async () => {
	              if (ticketLoading) return
	              if (!ticketLoaded) {
	                setTicketPopupVisible(true)
	                await loadUserTickets()
	                return
	              }
	              if (noUsableTickets && !isEditMode) {
	                const r = await Taro.showModal({
	                  title: '暂无可用水票',
	                  content: '您还没有可用水票，是否前往购买？',
	                  confirmText: '去购买',
	                  cancelText: '暂不'
	                })
	                if (r.confirm) await goBuyTickets()
	                return
	              }
	              setTicketPopupVisible(true)
	            }}
	        />
          {(noUsableTickets && !isEditMode) && (
            <Cell
              title={<Text className="text-gray-500">还没有购买水票</Text>}
              description="购买水票后即可在这里直接下单送水"
              extra={(
                <Button type="primary" size="small" onClick={goBuyTickets}>
                  去购买
                </Button>
              )}
            />
          )}
	        <Cell
	          title={'本次使用'}
	          extra={<View className={'font-medium'}>{displayQty} 张</View>}
	        />
      </CellGroup>

      <CellGroup>
        <Cell title={'备注'} extra={(
          <Input
            placeholder={'(选填)请填写备注'}
            style={{padding: '0'}}
            value={orderRemark}
            onChange={(value) => setOrderRemark(value)}
            maxLength={100}
          />
        )}/>
      </CellGroup>

	      {/* 水票明细弹窗 */}
	      <Popup
	        visible={ticketPopupVisible}
	        position="bottom"
	        style={{ height: '70vh' }}
	        onClose={() => setTicketPopupVisible(false)}
	      >
	        <View className="p-4">
	          <View className="flex justify-between items-center mb-3">
	            <Text className="text-base font-medium">水票明细</Text>
            <Text
              className="text-sm text-gray-500"
              onClick={() => setTicketPopupVisible(false)}
            >
              关闭
            </Text>
	          </View>
            {!!usableTickets.length && !ticketLoading && (
              <View className="text-xs text-gray-500 mb-2">
                <Text>可用合计 {availableTicketTotal} 张；下单时将优先使用可用数量少的水票</Text>
              </View>
            )}

	          {ticketLoading ? (
	            <View className="py-10 text-center text-gray-500">
	              <Text>加载中...</Text>
	            </View>
	          ) : (
	            <>
	              {!!usableTickets.length ? (
	                <CellGroup>
	                  {ticketsToConsume.map((t) => {
	                    return (
	                      <Cell
	                        key={t.id}
	                        title={<Text>票号 {t.id}</Text>}
	                        description={t.orderNo ? `来源订单：${t.orderNo}` : ''}
	                        extra={<Text className="text-gray-700">可用 {getTicketAvailableQty(t)}</Text>}
	                        onClick={() => setTicketPopupVisible(false)}
	                      />
	                    )
	                  })}
	                </CellGroup>
	              ) : (
                <View className="py-10 text-center">
                  <Empty description="暂无可用水票" />
                  <View className="mt-4 flex justify-center">
                    <Button
                      type="primary"
                      onClick={isEditMode ? () => setTicketPopupVisible(false) : goBuyTickets}
                    >
                      {isEditMode ? '确定修改' : '确定下单'}
                    </Button>
                  </View>
                </View>
	              )}
	            </>
	          )}
	        </View>
	      </Popup>

      {/* 门店选择弹窗 */}
      <Popup
        visible={storePopupVisible}
        position="bottom"
        style={{height: '70vh'}}
        onClose={() => setStorePopupVisible(false)}
      >
        <View className="p-4">
          <View className="flex justify-between items-center mb-3">
            <Text className="text-base font-medium">选择门店</Text>
            <Text
              className="text-sm text-gray-500"
              onClick={() => setStorePopupVisible(false)}
            >
              关闭
            </Text>
          </View>

          {storeLoading ? (
            <View className="py-10 text-center text-gray-500">
              <Text>加载中...</Text>
            </View>
          ) : (
            <CellGroup>
              {stores.map((s) => {
                const isActive = !!selectedStore?.id && selectedStore.id === s.id
                return (
                  <Cell
                    key={s.id}
                    title={<Text className={isActive ? 'text-green-600' : ''}>{s.name || `门店${s.id}`}</Text>}
                    description={s.address || ''}
                    onClick={async () => {
                      let storeToSave: ShopStore = s
                      if (s?.id) {
                        try {
                          const full = await getShopStore(s.id)
                          if (full) storeToSave = full
                        } catch (_e) {
                          // keep base item
                        }
                      }
                      setSelectedStore(storeToSave)
                      storeManualPickedRef.current = true
                      saveSelectedStoreToStorage(storeToSave)
                      setStorePopupVisible(false)
                      Taro.showToast({title: '门店已切换', icon: 'success'})
                    }}
                  />
                )
              })}
              {!stores.length && (
                <Cell title={<Text className="text-gray-500">暂无门店数据</Text>} />
              )}
            </CellGroup>
          )}
        </View>
      </Popup>

      {/* 楼层选择弹窗 */}
      <Popup
        visible={floorPickerVisible}
        position="bottom"
        onClose={() => setFloorPickerVisible(false)}
        style={{height: '40vh'}}
      >
        <View className="floor-picker-popup">
          <View className="floor-picker-popup__header">
            <Text className="text-base font-medium">选择楼层</Text>
            <Text
              className="text-sm text-gray-500"
              onClick={() => setFloorPickerVisible(false)}
            >
              关闭
            </Text>
          </View>
          <View className="floor-picker-popup__content">
            <View className="floor-grid">
              {Array.from({length: 32}, (_, i) => i + 2).map(f => (
                <View
                  key={f}
                  className={`floor-grid-item ${deliveryFloor === f ? 'active' : ''}`}
                  onClick={() => {
                    setDeliveryFloor(f)
                    setFloorPickerVisible(false)
                  }}
                >
                  <Text>{f}楼</Text>
                </View>
              ))}
            </View>
          </View>
          {deliveryFloor > 1 && (
            <View className="floor-picker-popup__footer">
              <Text className={'text-sm text-gray-600'}>
                配送费：{displayQty}桶 x {deliveryFloor - 1}层 = <Text className={'text-red-500 font-bold'}>￥{(displayQty * (deliveryFloor - 1)).toFixed(2)}</Text>
              </Text>
            </View>
          )}
        </View>
      </Popup>

      <Gap height={50}/>

      <div className={'fixed z-50 bg-white w-full bottom-0 left-0 pt-4 pb-10 border-t border-gray-200'}>
        <View className={'btn-bar flex justify-between items-center'}>
          <div className={'flex flex-col justify-center items-start mx-4'}>
            <View className={'flex items-center gap-2'}>
              <span className={'total-price text-sm text-gray-500'}>使用水票：</span>
              <span className={'text-red-500 text-xl font-bold'}>
                {displayQty}
              </span>
              <span className={'text-sm text-gray-500'}>张</span>
            </View>
            {getDeliveryFee() > 0 && (
              <View className={'text-xs text-orange-500'}>
                配送费 ￥{getDeliveryFee().toFixed(2)}（到付）
              </View>
            )}
	          </div>
	          <div className={'buy-btn mx-4'}>
	              {noUsableTickets && !isEditMode ? (
	                <Button type="primary" size="large" onClick={goBuyTickets}>
	                  {isEditMode ? '确定修改' : '确定下单'}
	                </Button>
	              ) : (
		                <Button
		                  type="success"
		                  size="large"
	                  loading={submitLoading || deliveryRangeChecking}
	                  disabled={
		                    deliveryRangeChecking ||
                      !address?.id ||
                      !addressHasCoords ||
		                    (deliveryRangeCheckedAddressId === address?.id && inDeliveryRange === false) ||
		                    (!isEditMode && availableTicketTotal <= 0) ||
		                    !canStartOrder ||
		                    !deliveryMethod ||
		                    (deliveryMethod === 'stairs' && needCarryUpstairs === null)
		                  }
		                  onClick={onSubmit}
		                >
                  {deliveryRangeChecking
                    ? '校验配送范围...'
                    : (!address?.id
                      ? '请选择地址'
                      : (!addressHasCoords
                        ? '地址缺少定位'
                        : ((deliveryRangeCheckedAddressId === address?.id && inDeliveryRange === false)
                          ? '不在配送范围'
                          : (!deliveryMethod
                            ? '请选配送方式'
                            : (deliveryMethod === 'stairs' && needCarryUpstairs === null
                              ? '请选是否送上楼'
                              : (submitLoading ? '提交中...' : (isEditMode ? '确定修改' : '立即提交'))
                            )
                          )
		                        )
		                      )
		                    )
		                  }
		                </Button>
              )}
	          </div>
	        </View>
	      </div>
    </div>
  );
};

export default OrderConfirm;
