<template>
  <view class="page">
    <view class="header-hero" :style="{ paddingTop: statusBarHeight + 'px' }">
      <view class="top-bar">
        <view class="top-left" @click="goBack">
          <AppIcon name="chevronLeft" size="sm" color="white" />
        </view>
        <view class="top-center">
          <text class="app-name">Labeling</text>
          <LocationPicker />
        </view>
        <view class="top-right" @click="isMenuOpen = true">
          <AppIcon name="menu" size="sm" color="white" />
        </view>
      </view>

      <view class="bt-bar" @click="goBluetoothPage">
        <view class="bt-left">
          <AppIcon name="bluetooth" size="sm" color="white" />
          <text class="bt-text">{{ btConnected ? btDeviceName : 'No printer connected' }}</text>
        </view>
        <view class="bt-status" :class="{ connected: btConnected }">
          <text class="bt-dot" />
          <text class="bt-label">{{ btConnected ? 'Connected' : 'Disconnected' }}</text>
        </view>
        <AppIcon name="chevronRight" size="sm" color="white" />
      </view>
    </view>

    <view class="body">
      <view class="sidebar">
        <view v-if="listLoading && labelTree.length === 0" class="sidebar-loading">
          <text class="sidebar-loading-text">Loading…</text>
        </view>
        <view
          v-for="cat in labelTree"
          :key="cat.id"
          class="cat-item"
          :class="{ active: selectedCategoryId === cat.id }"
          @click="selectedCategoryId = cat.id"
        >
          <view v-if="selectedCategoryId === cat.id" class="active-bar" />
          <view
            class="cat-icon"
            :class="[
              labelCategoryVisual(cat).mode === 'image' ? 'cat-icon--photo' : 'cat-icon--fallback',
              'cat-icon--fixed',
            ]"
            :style="labelCategoryIconBoxStyle(cat)"
          >
            <image
              v-if="labelCategoryVisual(cat).mode === 'image'"
              :src="resolveMediaUrlForApp(labelCategoryVisual(cat).imageUrl) || ''"
              class="cat-photo"
              mode="aspectFill"
            />
            <view
              v-else-if="labelCategoryVisual(cat).mode === 'colorText'"
              class="cat-icon-text-inner"
            >
              <text
                class="cat-icon-text cat-icon-text--on-color cat-icon-text--wrap"
                :style="{ color: labelCategoryVisual(cat).textColor || '#ffffff' }"
              >
                {{ labelCategoryVisual(cat).text }}
              </text>
            </view>
            <view v-else-if="labelCategoryVisual(cat).mode === 'text'" class="cat-icon-text-inner">
              <text class="cat-icon-text cat-icon-text--wrap">
                {{ labelCategoryVisual(cat).text }}
              </text>
            </view>
          </view>
          <text class="cat-name">{{ cat.categoryName }}</text>
        </view>
      </view>

      <scroll-view class="main-panel" scroll-y>
        <view class="panel-inner">
          <view v-if="!locationId" class="empty">
            <AppIcon name="mapPin" size="lg" color="gray" />
            <text class="empty-text">Select a store to load labels.</text>
          </view>

          <template v-else>
            <view class="search-box">
              <view class="search-icon-wrap">
                <AppIcon name="search" size="sm" color="gray" />
              </view>
              <input
                v-model="searchTerm"
                class="search-input"
                placeholder="Search products..."
                placeholder-class="placeholder"
              />
            </view>

            <view v-if="listLoading" class="empty">
              <text class="empty-text">Loading…</text>
            </view>

            <view v-else-if="listError" class="empty">
              <text class="empty-text">{{ listError }}</text>
            </view>

            <view v-else-if="filteredProductCategories.length === 0" class="empty">
              <AppIcon name="search" size="lg" color="gray" />
              <text class="empty-text">No products found</text>
            </view>

            <view v-else class="category-list">
              <view
                v-for="(pCat, pIdx) in filteredProductCategories"
                :key="productCategoryRowKey(pCat, pIdx)"
                class="cat-section"
              >
                <view
                  class="cat-header"
                  :style="catHeaderRowStyle"
                  @click="toggleCategory(productCategoryRowKey(pCat, pIdx))"
                >
                  <view class="cat-header-left" :style="catHeaderLeftStyle">
                    <view
                      class="cat-header-thumb"
                      :class="[
                        productCategoryVisual(pCat).mode === 'image'
                          ? 'cat-header-thumb--photo'
                          : 'cat-header-thumb--fallback',
                        'cat-header-thumb--fixed',
                      ]"
                      :style="catHeaderThumbStyle(pCat)"
                    >
                      <image
                        v-if="productCategoryVisual(pCat).mode === 'image'"
                        :src="resolveMediaUrlForApp(productCategoryVisual(pCat).imageUrl) || ''"
                        class="cat-photo"
                        mode="aspectFill"
                      />
                      <view
                        v-else-if="productCategoryVisual(pCat).mode === 'colorText'"
                        class="cat-header-text-inner"
                      >
                        <text
                          class="cat-icon-text cat-icon-text--on-color cat-icon-text--header cat-icon-text--wrap"
                          :style="{ color: productCategoryVisual(pCat).textColor || '#ffffff' }"
                        >
                          {{ categoryHeaderThumbText(pCat) }}
                        </text>
                      </view>
                      <view
                        v-else-if="productCategoryVisual(pCat).mode === 'color'"
                        class="cat-header-color-fill"
                        :style="{ backgroundColor: productCategoryVisual(pCat).bg }"
                      />
                      <view
                        v-else-if="productCategoryVisual(pCat).mode === 'text'"
                        class="cat-header-text-inner"
                      >
                        <text class="cat-icon-text cat-icon-text--header cat-icon-text--wrap">
                          {{ categoryHeaderThumbText(pCat) }}
                        </text>
                      </view>
                      <view v-else class="cat-header-fallback" :class="colorClassForName(pCat.name)">
                        <AppIcon name="food" size="sm" color="white" />
                      </view>
                    </view>
                    <view class="cat-header-info" :style="catHeaderInfoStyle">
                      <text class="cat-header-name">{{ pCat.name }}</text>
                      <text class="cat-header-count">{{ displayProductCategoryItemCount(pCat) }} items</text>
                    </view>
                  </view>
                  <view class="cat-header-chevron" :style="catHeaderChevronStyle">
                    <AppIcon
                      :name="
                        expandedCategories.indexOf(productCategoryRowKey(pCat, pIdx)) >= 0
                          ? 'chevronUp'
                          : 'chevronDown'
                      "
                      size="sm"
                      color="gray"
                    />
                  </view>
                </view>

                <view
                  v-if="expandedCategories.indexOf(productCategoryRowKey(pCat, pIdx)) >= 0"
                  class="cat-foods"
                  :style="catFoodsStyle"
                >
                  <view class="food-grid">
                    <view
                      v-for="product in pCat.products"
                      :key="product.productId"
                      class="food-card"
                      @click="handleProductClick(product, pCat.name)"
                    >
                      <view
                        class="food-img-wrap"
                        :class="
                          productVisual(product, pCat).mode === 'image'
                            ? 'food-img-wrap--photo'
                            : 'food-img-wrap--fallback'
                        "
                        :style="productThumbWrapStyle(product, pCat)"
                      >
                        <image
                          v-if="productVisual(product, pCat).mode === 'image'"
                          :src="resolveMediaUrlForApp(productVisual(product, pCat).imageUrl) || ''"
                          class="food-img"
                          mode="aspectFill"
                        />
                        <view
                          v-else-if="productVisual(product, pCat).mode === 'colorText'"
                          class="food-thumb-text-wrap"
                        >
                          <text
                            class="food-thumb-text food-thumb-text--on-color"
                            :style="{ color: productVisual(product, pCat).textColor || '#ffffff' }"
                          >
                            {{ productThumbDisplayText(product, pCat) }}
                          </text>
                        </view>
                        <view
                          v-else-if="productVisual(product, pCat).mode === 'text'"
                          class="food-thumb-text-wrap"
                        >
                          <text class="food-thumb-text">
                            {{ productThumbDisplayText(product, pCat) }}
                          </text>
                        </view>
                        <view
                          v-else-if="productVisual(product, pCat).mode === 'color'"
                          class="food-thumb-color-fill"
                          :style="{ backgroundColor: productVisual(product, pCat).bg }"
                        />
                        <view
                          v-else
                          class="food-thumb-fallback"
                          :class="colorClassForName(product.productName)"
                        >
                          <text class="food-thumb-text food-thumb-text--on-color food-thumb-text--wrap">
                            {{ productThumbFallbackText(product) }}
                          </text>
                        </view>
                      </view>
                      <view class="food-info-row">
                        <text class="food-name">{{ product.productName }}</text>
                        <view class="food-meta-badges">
                          <text class="food-meta-size">{{ primaryLabelSizeText(product) }}</text>
                          <text
                            v-if="effectiveLabelTypeCount(product) > 0"
                            class="food-meta-types"
                          >
                            {{ effectiveLabelTypeCount(product) }} Types
                          </text>
                        </view>
                      </view>
                      <text v-if="showFoodSubtitle(product)" class="food-desc">{{ product.subtitle }}</text>
                    </view>
                  </view>
                </view>
              </view>
            </view>
          </template>
        </view>
      </scroll-view>
    </view>

    <view v-if="showSubTypeModal" class="modal-mask" @click="showSubTypeModal = false">
      <view class="modal-body" @click.stop>
        <text class="modal-title">Select Label Type</text>
        <text class="modal-desc">{{ selectedProduct ? selectedProduct.productName : '' }}</text>
        <view class="subtype-list">
          <view
            v-for="lt in currentLabelTypes"
            :key="lt.labelTypeId"
            class="subtype-item"
            @click="selectLabelType(lt)"
          >
            <text class="subtype-name">{{ lt.typeName }}</text>
            <AppIcon name="chevronRight" size="sm" color="gray" />
          </view>
        </view>
        <view class="modal-cancel" @click="showSubTypeModal = false">
          <text class="modal-cancel-text">Cancel</text>
        </view>
      </view>
    </view>

    <SideMenu v-model="isMenuOpen" />
  </view>
</template>

<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import AppIcon from '../../components/AppIcon.vue'
import SideMenu from '../../components/SideMenu.vue'
import LocationPicker from '../../components/LocationPicker.vue'
import { getStatusBarHeight } from '../../utils/statusBar'
import { getCurrentPrinterSummary } from '../../utils/print/manager/printerManager'
import { getCurrentStoreId } from '../../utils/stores'
import { fetchUsAppLabelingTree } from '../../services/usAppLabeling'
import type {
  UsAppLabelCategoryTreeNodeDto,
  UsAppLabelingProductNodeDto,
  UsAppLabelTypeNodeDto,
  UsAppProductCategoryNodeDto,
} from '../../types/usAppLabeling'
import { resolveMediaUrlForApp } from '../../utils/resolveMediaUrl'
import { isUsAppSessionExpiredError } from '../../utils/usAppApiRequest'
import { resolveCategoryButtonVisualFromDto, type CategoryVisualRender } from '../../utils/categoryButtonAppearance'

const statusBarHeight = getStatusBarHeight()
const isMenuOpen = ref(false)
const locationId = ref(getCurrentStoreId())
const selectedCategoryId = ref('')
const searchTerm = ref('')
const debouncedSearch = ref('')
const expandedCategories = ref<string[]>([])
const showSubTypeModal = ref(false)
const selectedProduct = ref<UsAppLabelingProductNodeDto | null>(null)
const currentLabelTypes = ref<UsAppLabelTypeNodeDto[]>([])

/** 按屏宽比例缩放列表区图标/间距（基线 375px 逻辑宽） */
const layoutScale = ref(1)

function refreshLayoutScale() {
  const w = Number(uni.getSystemInfoSync().windowWidth || 375)
  layoutScale.value = Math.min(1.12, Math.max(0.88, w / 375))
}

function scaleRpx(base: number): number {
  return Math.round(base * layoutScale.value)
}

const ICON_FIXED_RPX = 64

function isTextIconVisual(v: CategoryVisualRender): boolean {
  return v.mode === 'text' || v.mode === 'colorText'
}

/** 固定方形容器（图片/纯色/文字均不撑开） */
function fixedIconBoxStyle(sideRpx = ICON_FIXED_RPX): Record<string, string> {
  const side = scaleRpx(sideRpx)
  return {
    width: `${side}rpx`,
    height: `${side}rpx`,
    minWidth: `${side}rpx`,
    minHeight: `${side}rpx`,
    maxWidth: `${side}rpx`,
    maxHeight: `${side}rpx`,
    overflow: 'hidden',
  }
}

const labelTree = ref<UsAppLabelCategoryTreeNodeDto[]>([])
const listLoading = ref(false)
const listError = ref('')

const btConnected = ref(false)
const btDeviceName = ref('')

let searchTimer: ReturnType<typeof setTimeout> | null = null

watch(searchTerm, () => {
  if (searchTimer) clearTimeout(searchTimer)
  searchTimer = setTimeout(() => {
    debouncedSearch.value = searchTerm.value.trim()
  }, 350)
})

onShow(() => {
  refreshLayoutScale()
  locationId.value = getCurrentStoreId()
  const summary = getCurrentPrinterSummary()
  btConnected.value = summary.type === 'bluetooth' || summary.type === 'builtin'
  btDeviceName.value = summary.displayName || ''
  if (locationId.value) {
    loadTree()
  }
})

const selectedLabelCategory = computed(() =>
  labelTree.value.find((c) => c.id === selectedCategoryId.value)
)

const productCategoriesForSidebar = computed<UsAppProductCategoryNodeDto[]>(() => {
  return selectedLabelCategory.value?.productCategories ?? []
})

const filteredProductCategories = computed(() => {
  const cats = productCategoriesForSidebar.value
  const s = debouncedSearch.value.toLowerCase()
  if (!s) return cats
  return cats
    .map((cat) => ({
      ...cat,
      products: cat.products.filter((p) => {
        const hay = `${p.productName} ${p.productCode} ${p.subtitle || ''} ${cat.name}`.toLowerCase()
        return hay.indexOf(s) >= 0
      }),
    }))
    .filter((cat) => cat.products.length > 0)
})

watch(labelTree, (tree) => {
  if (!tree.length) {
    selectedCategoryId.value = ''
    return
  }
  if (!selectedCategoryId.value || !tree.some((t) => t.id === selectedCategoryId.value)) {
    selectedCategoryId.value = tree[0].id
  }
})

watch(selectedCategoryId, () => {
  expandedCategories.value = []
})

watch([selectedLabelCategory, listLoading], () => {
  const cats = selectedLabelCategory.value?.productCategories ?? []
  if (cats.length && expandedCategories.value.length === 0) {
    expandedCategories.value = [productCategoryRowKey(cats[0], 0)]
  }
})

async function loadTree() {
  const loc = locationId.value
  if (!loc) {
    labelTree.value = []
    return
  }
  listLoading.value = true
  listError.value = ''
  try {
    const kw = debouncedSearch.value.trim()
    const rows = await fetchUsAppLabelingTree({
      locationId: loc,
      keyword: kw || undefined,
    })
    labelTree.value = rows
  } catch (e: unknown) {
    if (isUsAppSessionExpiredError(e)) return
    listError.value = e instanceof Error ? e.message : 'Failed to load labeling tree.'
    labelTree.value = []
  } finally {
    listLoading.value = false
  }
}

watch(debouncedSearch, () => {
  if (locationId.value) loadTree()
})

function labelCategoryVisual(cat: UsAppLabelCategoryTreeNodeDto): CategoryVisualRender {
  return resolveCategoryButtonVisualFromDto({
    buttonStyleJson: (cat as any).buttonStyleJson,
    buttonAppearance: (cat as any).buttonAppearance,
    displayText: (cat as any).displayText,
    buttonBgColor: (cat as any).buttonBgColor,
    buttonImageUrl: (cat as any).buttonImageUrl,
    buttonTextColor: (cat as any).buttonTextColor,
    categoryPhotoUrl: cat.categoryPhotoUrl,
    categoryName: cat.categoryName,
  })
}

function labelCategoryIconBoxStyle(cat: UsAppLabelCategoryTreeNodeDto): Record<string, string> {
  const v = labelCategoryVisual(cat)
  if (isTextIconVisual(v)) {
    const style = fixedIconBoxStyle()
    if (v.mode === 'colorText') style.backgroundColor = v.bg
    return style
  }
  if (v.mode === 'color') {
    return { ...fixedIconBoxStyle(), backgroundColor: v.bg }
  }
  return fixedIconBoxStyle()
}

function productCategoryVisual(p: UsAppProductCategoryNodeDto): CategoryVisualRender {
  return resolveCategoryButtonVisualFromDto({
    buttonStyleJson: (p as any).buttonStyleJson,
    buttonAppearance: (p as any).buttonAppearance,
    displayText: p.displayText,
    buttonBgColor: p.buttonBgColor,
    buttonImageUrl: p.buttonImageUrl,
    buttonTextColor: p.buttonTextColor,
    categoryPhotoUrl: p.categoryPhotoUrl,
    name: p.name,
  })
}

/** 安卓基座对内联 style 命中最稳；勿与侧栏共用 .cat-icon（64rpx） */
const catHeaderRowStyle: Record<string, string> = {
  padding: '16rpx 20rpx',
  display: 'flex',
  flexDirection: 'row',
  alignItems: 'center',
  justifyContent: 'space-between',
  boxSizing: 'border-box',
  width: '100%',
}

const catHeaderLeftStyle: Record<string, string> = {
  display: 'flex',
  flexDirection: 'row',
  alignItems: 'center',
  flex: '1',
  minWidth: '0',
}

const catHeaderInfoStyle = computed((): Record<string, string> => ({
  flex: '1',
  minWidth: '0',
  marginLeft: `${scaleRpx(20)}rpx`,
  paddingLeft: '0',
}))

const catHeaderChevronStyle: Record<string, string> = {
  flexShrink: '0',
  width: '44rpx',
  height: '44rpx',
  marginLeft: '8rpx',
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'center',
}

const catFoodsStyle: Record<string, string> = {
  boxSizing: 'border-box',
  paddingTop: '4rpx',
  paddingRight: '20rpx',
  paddingBottom: '16rpx',
  paddingLeft: '20rpx',
}

function catHeaderThumbStyle(p: UsAppProductCategoryNodeDto): Record<string, string> {
  const v = productCategoryVisual(p)
  const gap = scaleRpx(18)
  const style: Record<string, string> = {
    marginRight: `${gap}rpx`,
    marginBottom: '0',
    marginTop: '0',
    marginLeft: '0',
    flexShrink: '0',
    borderRadius: '12rpx',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    boxSizing: 'border-box',
    backgroundColor: '#f3f4f6',
  }
  if (isTextIconVisual(v)) {
    Object.assign(style, fixedIconBoxStyle(72))
    if (v.mode === 'colorText') style.backgroundColor = v.bg
  } else {
    Object.assign(style, fixedIconBoxStyle(72))
  }
  if (v.mode === 'color') {
    style.backgroundColor = v.bg
  }
  return style
}

/** 分类标题行图标内文案：Display Text 在框内换行展示 */
function categoryHeaderThumbText(p: UsAppProductCategoryNodeDto): string {
  const v = productCategoryVisual(p)
  const raw =
    v.mode === 'colorText' || v.mode === 'text'
      ? (v.text || p.name || '').trim()
      : (p.name || '').trim()
  return raw
}

function productThumbDisplayText(
  p: UsAppLabelingProductNodeDto,
  pCat?: UsAppProductCategoryNodeDto,
): string {
  const v = productVisual(p, pCat)
  if (v.mode !== 'text' && v.mode !== 'colorText') return ''
  const t = (v.text ?? '').trim()
  const name = (p.productName ?? '').trim()
  return t || name || '?'
}

function showFoodSubtitle(p: UsAppLabelingProductNodeDto): boolean {
  const sub = (p.subtitle ?? '').trim()
  if (!sub) return false
  return sub.toLowerCase() !== (p.productName ?? '').trim().toLowerCase()
}

function productCategoryRowKey(p: UsAppProductCategoryNodeDto, index: number): string {
  const id = p.categoryId != null ? String(p.categoryId).trim() : ''
  if (id) return `id:${id}`
  return `name:${p.name}:${index}`
}

function displayProductCategoryItemCount(p: UsAppProductCategoryNodeDto): number {
  const n = typeof p.itemCount === 'number' ? p.itemCount : NaN
  if (!Number.isNaN(n) && n >= 0) return n
  return p.products?.length ?? 0
}

const COLOR_CLASSES = ['bg-red', 'bg-blue', 'bg-green', 'bg-orange', 'bg-purple']

function colorClassForName(name: string): string {
  let h = 0
  for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) | 0
  return COLOR_CLASSES[Math.abs(h) % COLOR_CLASSES.length]
}

function productVisual(
  p: UsAppLabelingProductNodeDto,
  pCat?: UsAppProductCategoryNodeDto,
): CategoryVisualRender {
  const v = resolveCategoryButtonVisualFromDto({
    buttonStyleJson: p.buttonStyleJson,
    buttonAppearance: p.buttonAppearance,
    displayText: p.displayText,
    buttonBgColor: p.buttonBgColor,
    buttonImageUrl: p.buttonImageUrl,
    buttonTextColor: p.buttonTextColor,
    categoryPhotoUrl: p.categoryPhotoUrl,
    categoryName: p.productName,
    name: p.productName,
  })
  if (v.mode !== 'none') return v
  if (pCat) {
    const fromCat = productCategoryVisual(pCat)
    if (fromCat.mode !== 'none') return fromCat
  }
  const legacyImg = (p.productImageUrl ?? '').trim()
  if (legacyImg) return { mode: 'image', imageUrl: legacyImg }
  return { mode: 'none' }
}

function productThumbWrapStyle(
  p: UsAppLabelingProductNodeDto,
  pCat?: UsAppProductCategoryNodeDto,
): Record<string, string> {
  const v = productVisual(p, pCat)
  if (v.mode === 'colorText' || v.mode === 'color') {
    return { backgroundColor: v.bg }
  }
  return {}
}

function productThumbFallbackText(p: UsAppLabelingProductNodeDto): string {
  const name = (p.productName ?? '').trim()
  if (!name) return '?'
  const parts = name.split(/\s+/).filter(Boolean)
  if (parts.length >= 2) {
    return (parts[0].slice(0, 1) + parts[1].slice(0, 1)).toUpperCase()
  }
  return name.slice(0, 4)
}

/** 无商品图时由标签类型尺寸文案拼接展示（接口无单独预览图字段） */
function primaryLabelSizeText(p: UsAppLabelingProductNodeDto): string {
  const fromCard = (p.templateLabelSizeText ?? '').trim()
  if (fromCard) return fromCard
  const types = p.labelTypes || []
  if (types.length === 0) return '—'
  const texts = types.map((t) => (t.labelSizeText || '').trim()).filter(Boolean)
  if (texts.length === 0) return '—'
  const first = texts[0]
  if (texts.length === 1) return first
  const allSame = texts.every((x) => x === first)
  return allSame ? first : `${first} …`
}

function effectiveLabelTypeCount(p: UsAppLabelingProductNodeDto): number {
  const fromArr = p.labelTypes?.length ?? 0
  const fromField = typeof p.labelTypeCount === 'number' ? p.labelTypeCount : 0
  return Math.max(fromArr, fromField)
}

const toggleCategory = (catId: string) => {
  if (expandedCategories.value.indexOf(catId) >= 0) {
    expandedCategories.value = []
  } else {
    expandedCategories.value = [catId]
  }
}

const handleProductClick = (product: UsAppLabelingProductNodeDto, _productCategoryName: string) => {
  if (product.labelTypes.length > 1) {
    selectedProduct.value = product
    currentLabelTypes.value = product.labelTypes
    showSubTypeModal.value = true
    return
  }
  if (product.labelTypes.length === 1) {
    goPreview(product, product.labelTypes[0], _productCategoryName)
    return
  }
  uni.showToast({ title: 'No label types for this product.', icon: 'none' })
}

const selectLabelType = (lt: UsAppLabelTypeNodeDto) => {
  const product = selectedProduct.value
  if (!product) return
  const cat =
    productCategoriesForSidebar.value.find((c) => c.products.some((p) => p.productId === product.productId))
      ?.name || ''
  showSubTypeModal.value = false
  goPreview(product, lt, cat)
}

function goPreview(
  product: UsAppLabelingProductNodeDto,
  lt: UsAppLabelTypeNodeDto,
  categoryName: string
) {
  const cat =
    categoryName ||
    productCategoriesForSidebar.value.find((c) => c.products.some((p) => p.productId === product.productId))
      ?.name ||
    ''
  const q = [
    `labelCode=${encodeURIComponent(lt.labelCode)}`,
    `productId=${encodeURIComponent(product.productId)}`,
    `productName=${encodeURIComponent(product.productName)}`,
    `categoryName=${encodeURIComponent(cat)}`,
    `typeName=${encodeURIComponent(lt.typeName)}`,
    `templateSize=${encodeURIComponent(lt.labelSizeText || '')}`,
    `templateName=${encodeURIComponent(lt.templateCode || '')}`,
  ]
  uni.navigateTo({ url: `/pages/labels/preview?${q.join('&')}` })
}

const goBack = () => {
  const pages = getCurrentPages()
  if (pages.length > 1) {
    uni.navigateBack()
  } else {
    uni.redirectTo({ url: '/pages/index/index' })
  }
}

const goBluetoothPage = () => {
  uni.navigateTo({ url: '/pages/labels/bluetooth' })
}
</script>

<style scoped>
.page {
  min-height: 100vh;
  background: #f9fafb;
  display: flex;
  flex-direction: column;
}

.header-hero {
  background: linear-gradient(135deg, var(--theme-primary), var(--theme-primary-dark));
  padding-left: 32rpx;
  padding-right: 32rpx;
  padding-bottom: 0;
}

.top-bar {
  height: 96rpx;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.top-left,
.top-right {
  width: 64rpx;
  height: 64rpx;
  border-radius: 999rpx;
  background: rgba(255, 255, 255, 0.15);
  display: flex;
  align-items: center;
  justify-content: center;
}

.top-center {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.app-name {
  font-size: 34rpx;
  font-weight: 600;
  color: #ffffff;
}

.bt-bar {
  display: flex;
  align-items: center;
  gap: 12rpx;
  padding: 16rpx 20rpx;
  margin-top: 16rpx;
  margin-bottom: 16rpx;
  background: rgba(255, 255, 255, 0.12);
  border-radius: 16rpx;
}

.bt-left {
  display: flex;
  align-items: center;
  gap: 12rpx;
  flex: 1;
  min-width: 0;
}

.bt-text {
  font-size: 24rpx;
  color: rgba(255, 255, 255, 0.9);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bt-status {
  display: flex;
  align-items: center;
  gap: 8rpx;
  padding: 6rpx 16rpx;
  background: rgba(255, 255, 255, 0.12);
  border-radius: 999rpx;
  flex-shrink: 0;
}

.bt-dot {
  width: 12rpx;
  height: 12rpx;
  border-radius: 50%;
  background: #fbbf24;
}

.bt-status.connected .bt-dot {
  background: #4ade80;
}

.bt-label {
  font-size: 22rpx;
  color: rgba(255, 255, 255, 0.85);
}

.body {
  flex: 1;
  display: flex;
  min-height: 0;
}

.sidebar {
  width: 200rpx;
  background: #ffffff;
  border-right: 1rpx solid #e5e7eb;
  padding: 16rpx 0;
  overflow-y: auto;
}

.sidebar-loading {
  padding: 24rpx 12rpx;
  text-align: center;
}

.sidebar-loading-text {
  font-size: 22rpx;
  color: #9ca3af;
}

.cat-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20rpx 8rpx;
  position: relative;
}

.cat-item.active {
  background: var(--theme-primary-light);
}

.active-bar {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 6rpx;
  background: var(--theme-primary);
  border-radius: 0 6rpx 6rpx 0;
}

.cat-icon {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 8rpx;
  background: transparent;
  box-sizing: border-box;
}

.cat-icon--fixed {
  width: 64rpx;
  height: 64rpx;
  overflow: hidden;
}

.cat-icon-text-inner {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  padding: 4rpx 6rpx;
  box-sizing: border-box;
}

.cat-icon--photo {
  overflow: hidden;
  border-radius: 14rpx;
}

.cat-icon--fallback {
  overflow: hidden;
  border-radius: 14rpx;
}

.cat-photo {
  width: 100%;
  height: 100%;
  border-radius: 14rpx;
  display: block;
  vertical-align: top;
}

.cat-icon-text {
  max-width: 100%;
  max-height: 100%;
  padding: 0;
  font-size: 20rpx;
  font-weight: 700;
  color: #111827;
  line-height: 1.15;
  text-align: center;
  box-sizing: border-box;
}

.cat-icon-text--wrap,
.cat-icon-text--on-color {
  white-space: normal;
  word-break: break-word;
  overflow: hidden;
}

.cat-icon-text--on-color {
  max-width: 100%;
  max-height: 100%;
  padding: 0;
  font-size: 18rpx;
  font-weight: 700;
  line-height: 1.15;
  text-align: center;
}

.cat-name {
  font-size: 20rpx;
  color: #4b5563;
  text-align: center;
  word-break: break-word;
}

.cat-item.active .cat-name {
  color: var(--theme-primary);
  font-weight: 600;
}

.main-panel {
  flex: 1;
  min-width: 0;
  height: 100%;
}

/**
 * 产品列表布局：手写 rpx（UniApp 安卓部分机型不支持 CSS var，var 会导致 padding 整段失效）。
 */
.page {
  box-sizing: border-box;
}

.panel-inner {
  padding: 16rpx 20rpx;
  box-sizing: border-box;
}

.search-box {
  position: relative;
  display: flex;
  align-items: center;
  margin-bottom: 24rpx;
}

.search-icon-wrap {
  position: absolute;
  left: 20rpx;
  z-index: 1;
}

.search-input {
  flex: 1;
  height: 72rpx;
  padding-left: 64rpx;
  padding-right: 20rpx;
  background: #f3f4f6;
  border-radius: 16rpx;
  font-size: 26rpx;
}

.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 80rpx 24rpx;
}

.empty-text {
  font-size: 28rpx;
  color: #9ca3af;
  margin-top: 24rpx;
  text-align: center;
}

.category-list {
  display: flex;
  flex-direction: column;
  gap: 12rpx;
}

.cat-section {
  background: #fff;
  border-radius: 12rpx;
  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
  overflow: hidden;
  box-sizing: border-box;
}

/* 产品分类标题行：主样式见 script 内联 catHeaderRowStyle（安卓 scoped 易失效） */
.cat-header {
  box-sizing: border-box;
  width: 100%;
}

.cat-header-thumb {
  position: relative;
  box-sizing: border-box;
}

.cat-header-thumb--fixed {
  overflow: hidden;
}

.cat-header-text-inner {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  padding: 4rpx 6rpx;
  box-sizing: border-box;
}

.cat-header .cat-header-thumb .cat-icon-text,
.cat-header .cat-header-thumb .cat-icon-text--on-color,
.cat-header .cat-header-thumb .cat-icon-text--header {
  font-size: 20rpx;
  line-height: 1.15;
  padding: 0;
  max-width: 100%;
  max-height: 100%;
  white-space: normal;
  word-break: break-word;
  overflow: hidden;
}

.cat-header-thumb--photo,
.cat-header-thumb--fallback {
  overflow: hidden;
}

.cat-header-color-fill {
  width: 100%;
  height: 100%;
}

.cat-header-fallback {
  width: 100%;
  height: 100%;
  border-radius: 14rpx;
  display: flex;
  align-items: center;
  justify-content: center;
}

.cat-header-fallback.bg-red {
  background: #ef4444;
}
.cat-header-fallback.bg-blue {
  background: #3b82f6;
}
.cat-header-fallback.bg-green {
  background: #22c55e;
}
.cat-header-fallback.bg-orange {
  background: #f97316;
}
.cat-header-fallback.bg-purple {
  background: #a855f7;
}

.cat-header-info {
  flex: 1;
  min-width: 0;
  margin: 0;
  padding: 0;
}

.cat-header-name {
  font-size: 26rpx;
  font-weight: 600;
  color: #111827;
  display: block;
  line-height: 1.25;
}

.cat-header-count {
  font-size: 20rpx;
  color: #9ca3af;
  display: block;
  margin-top: 4rpx;
  line-height: 1.2;
}

.cat-header-chevron {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44rpx;
  height: 44rpx;
  margin-left: 8rpx;
  margin-right: 0;
}

.cat-foods {
  box-sizing: border-box;
  border-top: 1rpx solid #f3f4f6;
}

.food-grid {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-content: flex-start;
  padding-top: 8rpx;
  margin-left: -5rpx;
  margin-right: -5rpx;
}

.food-card {
  width: 47%;
  max-width: 240rpx;
  flex: 0 0 auto;
  box-sizing: border-box;
  margin: 0 5rpx 12rpx 5rpx;
  background: #f9fafb;
  padding: 8rpx;
  border-radius: 12rpx;
  min-width: 0;
}

.food-card:active {
  background: #f3f4f6;
}

.food-img-wrap {
  width: 100%;
  position: relative;
  height: 0;
  padding-top: 58%;
  border-radius: 8rpx;
  background: #e5e7eb;
  margin-bottom: 8rpx;
  overflow: hidden;
}

.food-img-wrap--fallback {
  display: block;
}

.food-img {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
}

.food-thumb-text-wrap {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 10rpx 12rpx;
  box-sizing: border-box;
  overflow: hidden;
}

.food-thumb-text {
  width: 100%;
  max-width: 100%;
  max-height: 100%;
  font-size: 22rpx;
  font-weight: 700;
  color: #111827;
  line-height: 1.25;
  text-align: center;
  white-space: normal;
  word-break: break-word;
  overflow: hidden;
  box-sizing: border-box;
}

.food-thumb-text--wrap {
  white-space: normal;
  word-break: break-word;
}

.food-thumb-text--on-color {
  font-size: 22rpx;
  color: #ffffff;
}

.food-thumb-color-fill {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
}

.food-thumb-fallback {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 12rpx;
  box-sizing: border-box;
}

.food-thumb-fallback.bg-red {
  background: #ef4444;
}
.food-thumb-fallback.bg-blue {
  background: #3b82f6;
}
.food-thumb-fallback.bg-green {
  background: #22c55e;
}
.food-thumb-fallback.bg-orange {
  background: #f97316;
}
.food-thumb-fallback.bg-purple {
  background: #a855f7;
}

.food-thumb-text--on-color {
  font-size: 20rpx;
  color: #ffffff;
}

.food-info-row {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 8rpx;
  margin-top: 4rpx;
  margin-bottom: 2rpx;
  min-width: 0;
}

.food-meta-badges {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: center;
  padding: 5px;
  flex-shrink: 0;
  max-width: 58%;
  box-sizing: border-box;
  background: #e5e7eb;
  border-radius: 8rpx;
  gap: 2rpx;
}

.food-meta-size,
.food-meta-types {
  font-size: 16rpx;
  color: #4b5563;
  font-weight: 500;
  line-height: 1.2;
  white-space: nowrap;
  text-align: right;
}

.food-name {
  font-size: 22rpx;
  font-weight: 600;
  color: #111827;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.food-desc {
  font-size: 18rpx;
  color: #6b7280;
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.modal-mask {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 1000;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.modal-body {
  width: 100%;
  background: #fff;
  border-radius: 32rpx 32rpx 0 0;
  padding: 40rpx 32rpx;
  max-height: 70vh;
  overflow-y: auto;
}

.modal-title {
  font-size: 36rpx;
  font-weight: 700;
  color: #111827;
  display: block;
  margin-bottom: 8rpx;
}

.modal-desc {
  font-size: 28rpx;
  color: #6b7280;
  display: block;
  margin-bottom: 32rpx;
}

.subtype-list {
  display: flex;
  flex-direction: column;
  gap: 16rpx;
  margin-bottom: 24rpx;
}

.subtype-item {
  display: flex;
  align-items: center;
  gap: 20rpx;
  padding: 28rpx 24rpx;
  background: #f9fafb;
  border-radius: 16rpx;
  border: 2rpx solid #e5e7eb;
}

.subtype-item:active {
  background: var(--theme-primary-light);
  border-color: var(--theme-primary);
}

.subtype-name {
  flex: 1;
  min-width: 0;
  font-size: 30rpx;
  font-weight: 600;
  color: #111827;
}

.modal-cancel {
  width: 100%;
  height: 88rpx;
  background: #f3f4f6;
  border-radius: 16rpx;
  display: flex;
  align-items: center;
  justify-content: center;
}

.modal-cancel-text {
  font-size: 30rpx;
  font-weight: 500;
  color: #374151;
  line-height: 1;
}

@media (min-width: 768px) {
  .panel-inner {
    padding: 20rpx 24rpx;
  }

  /* 平板：内联样式在运行时由 rpx 换算，此处仅作 H5 预览补充 */

  .food-card {
    width: 31%;
    max-width: 280rpx;
    padding: 10rpx;
    margin: 0 7rpx 14rpx 7rpx;
  }

  .food-img-wrap {
    padding-top: 62%;
  }

  .sidebar {
    width: 260rpx;
  }
}
</style>
