<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="page-title">Printer</text>
          <LocationPicker />
        </view>
        <view class="top-right" @click="isMenuOpen = true">
          <AppIcon name="menu" size="sm" color="white" />
        </view>
      </view>
    </view>

    <view class="content">
      <!-- Printer type: Bluetooth | Built-in -->
      <view v-if="availablePrinterTypes.length > 1" class="type-tabs">
        <view
          v-if="isBluetoothModeAvailable"
          class="tab"
          :class="{ active: printerType === 'bluetooth' }"
          @click="switchType('bluetooth')"
        >
          <AppIcon name="bluetooth" size="sm" :color="printerType === 'bluetooth' ? 'white' : 'gray'" />
          <text class="tab-text">Bluetooth</text>
        </view>
        <view
          v-if="isBuiltinModeAvailable"
          class="tab"
          :class="{ active: printerType === 'builtin' }"
          @click="switchType('builtin')"
        >
          <AppIcon name="printer" size="sm" :color="printerType === 'builtin' ? 'white' : 'gray'" />
          <text class="tab-text">Built-in</text>
        </view>
      </view>

      <!-- Error -->
      <view v-if="errorMsg" class="error-banner">
        <AppIcon name="alert" size="sm" color="red" />
        <text class="error-text">{{ errorMsg }}</text>
      </view>

      <!-- Connected -->
      <view v-if="connectedDevice" class="connected-card">
        <view class="connected-header">
          <view class="connected-icon">
            <AppIcon :name="printerType === 'builtin' ? 'printer' : 'bluetooth'" size="md" color="white" />
          </view>
          <view class="connected-info">
            <text class="connected-name">{{ connectedDevice.name }}</text>
            <text class="connected-status">Connected</text>
          </view>
        </view>
        <view class="btn-test-print" :class="{ disabled: testPrinting }" @click="handleTestPrint">
          <AppIcon name="printer" size="sm" color="white" />
          <text class="btn-test-print-text">Test Print</text>
        </view>
        <view class="btn-disconnect" @click="handleDisconnect">
          <text class="btn-disconnect-text">Disconnect</text>
        </view>
      </view>

      <!-- Bluetooth: scan & list -->
      <template v-if="printerType === 'bluetooth'">
        <view class="section-header">
          <text class="section-title">Available Printers</text>
          <view class="btn-scan" :class="{ scanning: isScanning }" @click="handleScan">
            <AppIcon name="refresh" size="sm" :color="isScanning ? 'white' : 'primary'" />
            <text class="btn-scan-text">{{ isScanning ? 'Scanning...' : 'Scan' }}</text>
          </view>
        </view>
        <view v-if="isBuiltinModeAvailable" class="switch-to-builtin-row">
          <view class="btn-switch-to-builtin" @click="switchToBuiltinMode">
            <text class="btn-switch-to-builtin-text">Built-in Printing</text>
          </view>
        </view>

        <view v-if="isScanning && devices.length === 0" class="scanning-wrap">
          <view class="scan-anim">
            <AppIcon name="bluetooth" size="lg" color="blue" />
          </view>
          <text class="scan-text">Searching for nearby printers...</text>
        </view>

        <view v-else-if="!isScanning && devices.length === 0 && !connectedDevice" class="empty-wrap">
          <AppIcon name="bluetooth" size="lg" color="gray" />
          <text class="empty-title">No Devices Found</text>
          <text class="empty-desc">Turn on your Bluetooth printer and tap Scan.</text>
        </view>

        <view v-else class="device-list">
          <view
            v-for="dev in devices"
            :key="dev.deviceId"
            class="device-card"
            :class="{ connecting: connectingId === dev.deviceId }"
            @click="handleConnect(dev)"
          >
            <view class="device-icon printer">
              <AppIcon name="printer" size="md" color="blue" />
            </view>
            <view class="device-info">
              <text class="device-name">{{ dev.name || 'Unknown' }}</text>
              <text class="device-id">{{ dev.deviceId }}</text>
              <view class="device-meta">
                <text class="device-tag">Printer</text>
                <text v-if="dev.RSSI" class="device-rssi">Signal: {{ dev.RSSI }}dBm</text>
              </view>
            </view>
            <view v-if="connectingId === dev.deviceId" class="device-action">
              <text class="connecting-text">Connecting...</text>
            </view>
            <view v-else class="device-action">
              <AppIcon name="chevronRight" size="sm" color="gray" />
            </view>
          </view>
        </view>
      </template>

      <!-- Built-in: single option -->
      <template v-else>
        <view class="section-header">
          <text class="section-title">Built-in Printer</text>
        </view>
        <view class="builtin-card">
          <text class="builtin-desc">For TCP-built-in printers (localhost). If your all-in-one uses "Virtual BT Printer" Bluetooth, use Bluetooth mode above and connect it.</text>
          <view
            v-if="!connectedDevice || getPrinterType() !== 'builtin'"
            class="btn-builtin"
            @click="handleUseBuiltin"
          >
            <AppIcon name="printer" size="sm" color="white" />
            <text class="btn-builtin-text">Use Built-in Printer</text>
          </view>
        </view>

        <view class="builtin-advanced-entry">
          <view class="btn-advanced" @click="showBuiltinAdvanced = !showBuiltinAdvanced">
            <text class="btn-advanced-text">{{ showBuiltinAdvanced ? 'Hide advanced options' : 'Show advanced options' }}</text>
          </view>
        </view>

        <!-- UPOS options: builtin / serial -->
        <view v-if="showBuiltinAdvanced" class="upos-card">
          <text class="upos-title">UPOS options (for AIO devices)</text>
          <text class="upos-desc">
            If printing does not feed paper, try switching to Serial and set a correct port/baudrate.
          </text>

          <view class="upos-row">
            <text class="upos-label">Driver protocol</text>
            <view class="upos-seg">
              <view class="upos-seg-btn" :class="{ on: builtinDriverKey === 'generic-tsc' }" @click="onBuiltinDriverSegmentTap('generic-tsc')">
                TSC (Label)
              </view>
              <view class="upos-seg-btn" :class="{ on: builtinDriverKey === 'generic-esc' }" @click="onBuiltinDriverSegmentTap('generic-esc')">
                ESC/POS (Receipt)
              </view>
            </view>
          </view>

          <view class="upos-row">
            <text class="upos-label">Prefer</text>
            <view class="upos-seg">
              <view class="upos-seg-btn" :class="{ on: uposPrefer === 'builtin' }" @click="uposPrefer = 'builtin'">
                Builtin
              </view>
              <view class="upos-seg-btn" :class="{ on: uposPrefer === 'serial' }" @click="uposPrefer = 'serial'">
                Serial
              </view>
            </view>
          </view>

          <view class="upos-row">
            <text class="upos-label">Serial port</text>
            <input
              class="upos-input"
              type="text"
              :value="uposSerialPath"
              placeholder="/dev/ttyS0"
              @input="uposSerialPath = String(($event as any).detail?.value || '')"
            />
          </view>

          <view class="upos-row">
            <text class="upos-label">Baudrate</text>
            <input
              class="upos-input"
              type="number"
              :value="String(uposBaudrate)"
              placeholder="9600"
              @input="uposBaudrate = Number(String(($event as any).detail?.value || '9600')) || 9600"
            />
          </view>

          <view class="upos-row upos-row-switch">
            <text class="upos-label">Force UPOS</text>
            <switch :checked="uposForce" @change="uposForce = !!($event as any).detail?.value" />
          </view>

          <view class="upos-actions">
            <view class="upos-btn" @click="handleSaveUposOptions">
              <text class="upos-btn-text">Save UPOS options</text>
            </view>
            <view class="upos-btn upos-btn-secondary" @click="handleResetUposOptions">
              <text class="upos-btn-text">Reset</text>
            </view>
          </view>
        </view>
      </template>

      <view class="collapse-section collapse-section--tips">
        <view class="collapse-header collapse-header--tips" @click="troubleshootingExpanded = !troubleshootingExpanded">
          <text class="collapse-title">Troubleshooting</text>
          <view class="collapse-action-wrap">
            <text class="collapse-action">{{ troubleshootingExpanded ? 'Collapse' : 'Expand' }}</text>
          </view>
        </view>
        <view v-if="troubleshootingExpanded" class="collapse-body">
          <view class="tips-card">
            <text class="tips-item">1. Ensure the printer is powered on and in pairing mode</text>
            <text class="tips-item">2. On Android: enable Location (required for Bluetooth scan)</text>
            <text class="tips-item">3. Place the printer within 10 m and tap Scan again</text>
            <text class="tips-item">4. Devices with no name show as "Unknown Device"—you can still connect</text>
            <text class="tips-item">5. GP-D320FX (d320fx_xxxx): use Bluetooth mode, tap Scan—shows paired + nearby devices, no filtering</text>
            <text class="tips-item">6. Restart the printer or app if not visible</text>
            <text class="tips-item tips-item-last">
              7. Built-in TSC: defaults to TSPL label commands via UPOS; use Advanced → ESC/POS only if your device is receipt-only.
            </text>
          </view>
        </view>
      </view>

      <view class="collapse-section collapse-section--debug">
        <view class="collapse-header collapse-header--debug" @click="debugExpanded = !debugExpanded">
          <text class="collapse-title">Debug Status</text>
          <view class="collapse-action-wrap">
            <text class="collapse-action">{{ debugExpanded ? 'Collapse' : 'Expand' }}</text>
          </view>
        </view>
        <view v-if="debugExpanded" class="collapse-body">
          <view class="debug-card">
            <text class="debug-item">Current Mode: {{ debugInfo.currentMode }}</text>
            <text class="debug-item">Available Mode: {{ availablePrinterTypesLabel }}</text>
            <text class="debug-item">Device Model: {{ deviceIdentity.model || '-' }}</text>
            <text class="debug-item">Device Brand: {{ deviceIdentity.brand || '-' }}</text>
            <text class="debug-item">Device Product: {{ deviceIdentity.product || '-' }}</text>
            <text class="debug-item">Classic Module: {{ debugInfo.classicModuleReady ? 'Ready' : 'Not Ready' }}</text>
            <text class="debug-item">Native Plugin: {{ nativeDebug.available ? 'Ready' : 'Missing' }}</text>
            <text class="debug-item">Native Backend: {{ nativeDebug.backend || '-' }}</text>
            <text class="debug-item">Native Stage: {{ nativeDebug.stage || '-' }}</text>
            <text class="debug-item">Native Command Bytes: {{ nativeDebug.commandBytes || 0 }}</text>
            <text class="debug-item">Paired Count: {{ debugInfo.pairedCount }}</text>
            <text class="debug-item">Virtual BT Printer: {{ debugInfo.foundVirtualPrinter ? 'Found' : 'Not Found' }}</text>
            <text class="debug-item">Classic Scan: {{ debugInfo.lastClassicEvent }}</text>
            <text class="debug-item">BLE Scan: {{ debugInfo.lastBleEvent }}</text>
            <text v-if="debugInfo.lastBleError" class="debug-item debug-error">{{ debugInfo.lastBleError }}</text>
            <text v-if="nativeDebug.lastError" class="debug-item debug-error">{{ nativeDebug.lastError }}</text>
            <text v-if="debugInfo.locationServiceRequired" class="debug-item debug-warn">
              Android system Location service is OFF. Turn it on before BLE scan.
            </text>
          </view>
        </view>
      </view>
    </view>

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

<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import AppIcon from '../../components/AppIcon.vue'
import SideMenu from '../../components/SideMenu.vue'
import LocationPicker from '../../components/LocationPicker.vue'
import { getStatusBarHeight } from '../../utils/statusBar'
import { getDeviceIdentity } from '../../utils/deviceInfo'
import classicBluetooth from '../../utils/print/bluetoothTool.js'
import {
  getPrinterType,
  getAvailablePrinterTypes,
  clearPrinter,
  type PrinterType,
  PrinterStorageKeys,
  setUposOptions,
  setBuiltinPrinter,
} from '../../utils/print/printerConnection'
import { ensureBluetoothPermissions } from '../../utils/print/bluetoothPermissions'
import {
  getNativeFastPrinterDebugInfo,
  getNativeFastPrinterState,
  isNativeFastPrinterAvailable,
} from '../../utils/print/nativeFastPrinter'
import {
  hasUserAcknowledgedBuiltinTsc,
  logBuiltinTscCapability,
  setUserAcknowledgedBuiltinTsc,
} from '../../utils/print/builtinTscCapabilityLog'
import {
  connectBluetoothPrinter,
  describeDiscoveredPrinter,
  disconnectCurrentPrinter,
  getCurrentPrinterSummary,
  testPrintCurrentPrinter,
  useBuiltinPrinter,
} from '../../utils/print/manager/printerManager'

const statusBarHeight = getStatusBarHeight()
const isMenuOpen = ref(false)
const debugExpanded = ref(false)
const troubleshootingExpanded = ref(false)
const isScanning = ref(false)
const connectingId = ref('')
const errorMsg = ref('')
const btAdapterReady = ref(false)
const availablePrinterTypes = getAvailablePrinterTypes()
const availablePrinterTypesLabel = availablePrinterTypes.map(item => item === 'builtin' ? 'Built-in' : 'Bluetooth').join(' / ')
const deviceIdentity = getDeviceIdentity()
const storedPrinterType = getPrinterType()
const preferredPrinterType = (storedPrinterType && availablePrinterTypes.includes(storedPrinterType as PrinterType))
  ? storedPrinterType as PrinterType
  : availablePrinterTypes[0]
const printerType = ref<PrinterType | ''>(preferredPrinterType || 'bluetooth')
const currentPrinter = ref(getCurrentPrinterSummary())
const showBuiltinAdvanced = ref(false)
const debugInfo = ref({
  currentMode: 'none',
  classicModuleReady: false,
  pairedCount: 0,
  foundVirtualPrinter: false,
  lastClassicEvent: 'idle',
  lastBleEvent: 'idle',
  lastBleError: '',
  locationServiceRequired: false,
})
const nativeDebug = ref(getNativeFastPrinterState() || {})

const uposPrefer = ref<'builtin' | 'serial'>(
  (String(uni.getStorageSync(PrinterStorageKeys.uposPrefer) || '').toLowerCase() === 'serial' ? 'serial' : 'builtin') as
    | 'builtin'
    | 'serial'
)
const uposSerialPath = ref<string>(String(uni.getStorageSync(PrinterStorageKeys.uposSerialPath) || ''))
const uposBaudrate = ref<number>(Number(uni.getStorageSync(PrinterStorageKeys.uposBaudrate)) || 9600)
const uposForce = ref<boolean>(uni.getStorageSync(PrinterStorageKeys.uposForce) === '1')
/** 一体机内置默认 TSC/TSPL（原生模板下发 UPOS）；Advanced 可选 ESC/POS（小票）。 */
const defaultBuiltinDriverKey: 'generic-tsc' | 'generic-esc' = 'generic-tsc'
const storedBuiltinDriver = String(getCurrentPrinterSummary()?.driverKey || '').toLowerCase()
const builtinDriverKey = ref<'generic-tsc' | 'generic-esc'>(
  storedBuiltinDriver === 'generic-esc' || storedBuiltinDriver === 'generic-tsc'
    ? (storedBuiltinDriver as 'generic-tsc' | 'generic-esc')
    : (defaultBuiltinDriverKey as 'generic-tsc' | 'generic-esc')
)

interface BtDevice {
  deviceId: string
  name: string
  RSSI?: number
  type?: 'ble' | 'classic' | 'dual' | 'unknown'
}

const devices = ref<BtDevice[]>([])
const discoveredIds = new Set<string>()
const isBluetoothModeAvailable = availablePrinterTypes.includes('bluetooth')
const isBuiltinModeAvailable = availablePrinterTypes.includes('builtin')

function refreshCurrentPrinter () {
  currentPrinter.value = getCurrentPrinterSummary()
  debugInfo.value.currentMode = currentPrinter.value.type || 'none'
}

async function refreshNativeDebug () {
  nativeDebug.value = getNativeFastPrinterState() || {}
  try {
    const info = await getNativeFastPrinterDebugInfo()
    nativeDebug.value = {
      ...nativeDebug.value,
      ...info,
    }
  } catch (_) {
    nativeDebug.value = getNativeFastPrinterState() || {}
  }
}

function onBuiltinDriverSegmentTap (key: 'generic-tsc' | 'generic-esc') {
  builtinDriverKey.value = key
  logBuiltinTscCapability(key === 'generic-tsc' ? 'driver_segment_tsc' : 'driver_segment_esc', {
    screen: 'bluetooth_builtin',
  })
}

function handleSaveUposOptions () {
  setBuiltinPrinter(builtinDriverKey.value)
  setUposOptions({
    prefer: uposPrefer.value,
    serialPath: uposSerialPath.value,
    baudrate: uposBaudrate.value,
    force: uposForce.value,
  })
  logBuiltinTscCapability('save_upos_options', {
    screen: 'bluetooth_builtin',
    driverKey: builtinDriverKey.value,
    uposPrefer: uposPrefer.value,
    uposBaudrate: uposBaudrate.value,
  })
  uni.showToast({ title: 'Saved', icon: 'success', duration: 1200 })
  void refreshNativeDebug()
  refreshCurrentPrinter()
}

function handleResetUposOptions () {
  uposPrefer.value = 'builtin'
  uposSerialPath.value = ''
  uposBaudrate.value = 9600
  uposForce.value = false
  builtinDriverKey.value = defaultBuiltinDriverKey as 'generic-tsc' | 'generic-esc'
  setBuiltinPrinter(builtinDriverKey.value)
  setUposOptions({
    prefer: 'builtin',
    serialPath: '',
    baudrate: 9600,
    force: false,
  })
  logBuiltinTscCapability('reset_upos_options', { screen: 'bluetooth_builtin' })
  uni.showToast({ title: 'Reset', icon: 'none', duration: 1200 })
  void refreshNativeDebug()
  refreshCurrentPrinter()
}

function hasPreferredClassicDeviceInList () {
  return devices.value.some((item: any) => {
    const name = String(item?.name || '').toLowerCase()
    const driverKey = String(item?.driverKey || '').toLowerCase()
    return name.includes('virtual bt printer') || driverKey === 'd320fax'
  })
}

function upsertDevice (device: any) {
  const described = describeDiscoveredPrinter(device)
  const existing = devices.value.find(item => item.deviceId === described.deviceId)
  if (!existing) {
    discoveredIds.add(described.deviceId)
    devices.value.push(described)
    return
  }
  Object.assign(existing, described, {
    RSSI: described.RSSI != null ? described.RSSI : existing.RSSI,
  })
}

const connectedDevice = computed(() => {
  const summary = currentPrinter.value
  if (printerType.value === 'builtin' && summary.type === 'builtin') {
    return { name: summary.displayName, deviceId: summary.deviceId }
  }
  if (printerType.value === 'bluetooth' && summary.type === 'bluetooth') {
    return { name: summary.displayName, deviceId: summary.deviceId }
  }
  return null
})

function switchType (type: 'bluetooth' | 'builtin') {
  if (!availablePrinterTypes.includes(type)) return
  printerType.value = type
  showBuiltinAdvanced.value = false
  if (type === 'bluetooth' && getPrinterType() === 'builtin') {
    clearPrinter()
    refreshCurrentPrinter()
  }
}

function switchToBuiltinMode () {
  if (!isBuiltinModeAvailable) return
  switchType('builtin')
}

const goBack = () => uni.navigateBack()

const initBluetooth = (): Promise<void> => {
  return new Promise((resolve, reject) => {
    uni.openBluetoothAdapter({
      success: () => {
        btAdapterReady.value = true
        errorMsg.value = ''
        resolve()
      },
      fail: (err: any) => {
        btAdapterReady.value = false
        if (err.errCode === 10001 || (err.errMsg && err.errMsg.includes('not available'))) {
          errorMsg.value = 'Bluetooth is off. Turn it on in system settings.'
        } else {
          errorMsg.value = 'Bluetooth init failed: ' + (err.errMsg || 'Unknown error')
        }
        reject(err)
      },
    })
  })
}

const startBleScan = () => {
  uni.startBluetoothDevicesDiscovery({
    allowDuplicatesKey: false,
    success: () => {
      isScanning.value = true
      errorMsg.value = ''
      debugInfo.value.lastBleEvent = 'scan running'
    },
    fail: (err: any) => {
      console.error('BLE startBluetoothDevicesDiscovery fail:', err)
      debugInfo.value.lastBleEvent = 'scan failed'
      debugInfo.value.lastBleError = err?.errMsg || 'BLE scan failed'
      if (err?.errCode === 10016 || err?.code === 10016) {
        debugInfo.value.locationServiceRequired = true
        errorMsg.value = 'Bluetooth scan failed: Android system Location service is turned off.'
      }
    },
  })
}

const stopDiscovery = () => {
  uni.stopBluetoothDevicesDiscovery({
    complete: () => {
      isScanning.value = false
    },
  })
  // #ifdef APP-PLUS
  const classic = classicBluetooth
  if (classic && classic.cancelClassicDiscovery) classic.cancelClassicDiscovery()
  // #endif
}

const onDeviceFound = (res: any) => {
  const foundDevices: any[] = res.devices || []
  for (const d of foundDevices) {
    const name = (d.localName || d.name || '').trim()
    upsertDevice({
      deviceId: d.deviceId,
      name: name || 'Unknown Device',
      RSSI: d.RSSI,
      type: 'ble',
    })
  }
  devices.value.sort((a, b) => (b.RSSI || -100) - (a.RSSI || -100))
}

/** 合并已缓存的 BLE 设备（uni.getBluetoothDevices 返回之前扫描到的设备，不过滤） */
function mergeCachedBleDevices () {
  uni.getBluetoothDevices({
    success: (res: any) => {
      const list = res.devices || []
      for (const d of list) {
        const name = (d.localName || d.name || '').trim()
        upsertDevice({
          deviceId: d.deviceId,
          name: name || 'Unknown Device',
          RSSI: d.RSSI,
          type: 'ble',
        })
      }
      if (list.length > 0) devices.value.sort((a, b) => (b.RSSI || -100) - (a.RSSI || -100))
    },
  })
}

function addPairedDevices () {
  // #ifdef APP-PLUS
  const classic = classicBluetooth
  if (!classic || !classic.getPairedDevices) return
  try {
    const paired = classic.getPairedDevices()
    debugInfo.value.pairedCount = (paired || []).length
    debugInfo.value.foundVirtualPrinter = (paired || []).some((item: any) => String(item?.name || '').toLowerCase().includes('virtual bt printer'))
    for (const p of paired) {
      upsertDevice({
        deviceId: p.deviceId,
        name: p.name || 'Unknown Device',
        type: p.type || 'classic',
      })
    }
    if (paired.length > 0) {
      debugInfo.value.lastClassicEvent = 'paired devices loaded'
      devices.value.sort((a, b) => (b.RSSI || -100) - (a.RSSI || -100))
    } else {
      debugInfo.value.lastClassicEvent = 'no paired devices'
    }
  } catch (e) {
    console.error('addPairedDevices error:', e)
    debugInfo.value.lastClassicEvent = 'load paired devices failed'
  }
  // #endif
}

function startClassicScan () {
  // #ifdef APP-PLUS
  const classic = classicBluetooth
  if (classic && classic.startClassicDiscovery) {
    try {
      classic.startClassicDiscovery(
        (dev: { name: string; deviceId: string; type: string }) => {
          debugInfo.value.lastClassicEvent = 'device found'
          upsertDevice({
            deviceId: dev.deviceId,
            name: dev.name || 'Unknown Device',
            type: (dev.type as BtDevice['type']) || 'classic',
          })
          devices.value.sort((a, b) => (b.RSSI || -100) - (a.RSSI || -100))
        },
        () => {
          debugInfo.value.lastClassicEvent = 'scan finished'
        },
      )
      isScanning.value = true
      debugInfo.value.lastClassicEvent = 'scan running'
    } catch (e) {
      console.error('Classic discovery failed', e)
      debugInfo.value.lastClassicEvent = 'scan failed'
    }
  } else {
    debugInfo.value.lastClassicEvent = 'classic module unavailable'
  }
  // #endif
}

const handleScan = async () => {
  if (isScanning.value) {
    stopDiscovery()
    return
  }
  errorMsg.value = ''
  devices.value = []
  discoveredIds.clear()
  debugInfo.value.lastBleError = ''
  debugInfo.value.locationServiceRequired = false
  debugInfo.value.lastClassicEvent = 'starting'
  debugInfo.value.lastBleEvent = 'starting'

  try {
    const permissionResult = await ensureBluetoothPermissions({ scan: true, connect: true })
    if (!permissionResult.ok) {
      errorMsg.value = permissionResult.message || 'Bluetooth permission denied.'
      return
    }
    await initBluetooth()

    addPairedDevices()
    startClassicScan()

    if (hasPreferredClassicDeviceInList()) {
      isScanning.value = false
      debugInfo.value.lastBleEvent = 'skipped (paired classic device found)'
      uni.showToast({ title: 'Using paired classic Bluetooth devices', icon: 'none' })
      return
    }

    mergeCachedBleDevices()
    await new Promise<void>((resolve) => {
      uni.getLocation({
        type: 'wgs84',
        success: () => resolve(),
        fail: () => resolve(),
      })
    })
    startBleScan()
    uni.showToast({ title: 'Scanning...', icon: 'none' })
    setTimeout(() => {
      if (isScanning.value) stopDiscovery()
    }, 20000)
  } catch (_) {
    if (!isScanning.value && devices.value.length === 0) {
      errorMsg.value = 'Bluetooth scan failed. Check if Bluetooth is enabled.'
    }
  }
}

const handleConnect = async (dev: BtDevice) => {
  if (connectingId.value) return
  connectingId.value = dev.deviceId
  errorMsg.value = ''

  if (isScanning.value) stopDiscovery()

  const permissionResult = await ensureBluetoothPermissions({ connect: true })
  if (!permissionResult.ok) {
    connectingId.value = ''
    errorMsg.value = permissionResult.message || 'Bluetooth permission denied.'
    return
  }

  try {
    await connectBluetoothPrinter(dev)
    refreshCurrentPrinter()
    await refreshNativeDebug()
    connectingId.value = ''
    uni.showToast({ title: 'Connected!', icon: 'success' })
  } catch (e: any) {
    await refreshNativeDebug()
    errorMsg.value = (e && e.message) ? e.message : 'Connection failed'
    connectingId.value = ''
  }
}

const handleUseBuiltin = () => {
  useBuiltinPrinter(builtinDriverKey.value)
  logBuiltinTscCapability('use_builtin_printer', {
    screen: 'bluetooth_builtin',
    driverKey: builtinDriverKey.value,
  })
  refreshCurrentPrinter()
}

const testPrinting = ref(false)
function withTimeout<T> (promise: Promise<T>, ms: number, message: string): Promise<T> {
  return new Promise<T>((resolve, reject) => {
    const tid = setTimeout(() => reject(new Error(message)), ms)
    promise.then(
      (v) => {
        clearTimeout(tid)
        resolve(v)
      },
      (e) => {
        clearTimeout(tid)
        reject(e)
      }
    )
  })
}

function buildTestPrintDebugText (): string {
  const summary = getCurrentPrinterSummary()
  const classicState = typeof classicBluetooth?.getDebugState === 'function'
    ? classicBluetooth.getDebugState()
    : null
  const lines: string[] = [
    `driver=${summary.driverKey || '-'}/${summary.driverName || '-'}`,
    `protocol=${summary.protocol || '-'}`,
    `deviceType=${summary.deviceType || '-'}`,
    `transport=${(summary as any).transportMode || '-'}`,
  ]
  if (classicState) {
    lines.push(`state=${classicState.connectionState || '-'}`)
    lines.push(`connected=${String(!!classicState.socketConnected)}`)
    lines.push(`outputReady=${String(!!classicState.outputReady)}`)
    lines.push(`sendMode=${classicState.lastSendMode || '-'}`)
    if (classicState.lastSendError || classicState.lastError) {
      lines.push(`lastError=${classicState.lastSendError || classicState.lastError}`)
    }
  }
  return lines.join('\n')
}

/** 自检测试页，不落库接口 9（仅预览页业务打印成功后上报） */
const handleTestPrint = async () => {
  if (testPrinting.value) return
  const summaryPreflight = getCurrentPrinterSummary()
  const classicStatePreflight = typeof classicBluetooth?.getDebugState === 'function'
    ? classicBluetooth.getDebugState()
    : null
  const isVirtualBtPrinter = String(classicStatePreflight?.deviceName || '').toLowerCase().includes('virtual bt printer')
    || String(summaryPreflight.driverName || '').toLowerCase().includes('virtual bt printer')
  if (
    summaryPreflight.type === 'bluetooth' &&
    summaryPreflight.driverKey === 'd320fax' &&
    isVirtualBtPrinter &&
    !isNativeFastPrinterAvailable()
  ) {
    uni.showModal({
      title: 'Print Not Supported In Current Build',
      content:
        'Virtual BT Printer (D320FAX) requires native-fast-printer in this app build. Please use a build that includes this plugin, or switch to a non-virtual Bluetooth printer.',
      showCancel: false,
    })
    return
  }
  testPrinting.value = true
  let watchdog: ReturnType<typeof setTimeout> | null = null
  try {
    uni.showLoading({ title: 'Sending test job...', mask: true })
    watchdog = setTimeout(() => {
      try { uni.hideLoading() } catch (_) {}
      console.warn('[bluetooth] TEST_PRINT_TIMEOUT watchdog', buildTestPrintDebugText())
    }, 35000)

    await Promise.race([
      testPrintCurrentPrinter((percent) => {
        if (percent > 0 && percent < 100) {
          uni.showLoading({ title: `Sending ${percent}%`, mask: true })
        }
      }),
      new Promise((_, reject) => setTimeout(() => reject(new Error('TEST_PRINT_TIMEOUT')), 25000)),
    ])
    await withTimeout(refreshNativeDebug(), 1500, 'DEBUG_TIMEOUT')
    uni.hideLoading()
    uni.showToast({ title: 'Test print sent!', icon: 'success' })
  } catch (e: any) {
    try { await withTimeout(refreshNativeDebug(), 1200, 'DEBUG_TIMEOUT') } catch (_) {}
    uni.hideLoading()
    const msg = (e && e.message) ? e.message : 'Please check printer connection.'
    if (msg === 'TEST_PRINT_TIMEOUT' || msg === 'DEBUG_TIMEOUT') {
      console.warn('[bluetooth] test print timeout', msg, buildTestPrintDebugText())
      return
    }
    if (msg === 'BUILTIN_PLUGIN_NOT_FOUND') {
      uni.showModal({
        title: 'Use Bluetooth Mode',
        content: 'For GP-D320FX, switch to Bluetooth mode and tap Scan to connect (for example d320fx_xxxx). Built-in mode needs custom app packaging.',
        showCancel: false,
        success: () => { switchType('bluetooth') },
      })
    } else {
      const debugText = buildTestPrintDebugText()
      uni.showModal({
        title: 'Print Failed',
        content: `${msg}\n\n${debugText}`.trim(),
        showCancel: false,
      })
    }
  } finally {
    if (watchdog) clearTimeout(watchdog)
    testPrinting.value = false
  }
}

const handleDisconnect = async () => {
  await disconnectCurrentPrinter()
  refreshCurrentPrinter()
  await refreshNativeDebug()
  uni.showToast({ title: 'Disconnected', icon: 'none' })
}

onMounted(() => {
  debugInfo.value.classicModuleReady = !!classicBluetooth
  refreshNativeDebug()
  uni.onBluetoothDeviceFound(onDeviceFound)
  uni.onBluetoothAdapterStateChange((res: any) => {
    if (!res.available) {
      btAdapterReady.value = false
      isScanning.value = false
      errorMsg.value = 'Bluetooth has been turned off.'
    } else {
      btAdapterReady.value = true
      errorMsg.value = ''
    }
  })
  printerType.value = preferredPrinterType || 'bluetooth'
  refreshCurrentPrinter()
})

onUnmounted(() => {
  if (isScanning.value) stopDiscovery()
  try {
    if (typeof uni.offBluetoothDeviceFound === 'function') {
      uni.offBluetoothDeviceFound()
    }
  } catch (_) {}
  try {
    if (typeof uni.offBluetoothAdapterStateChange === 'function') {
      uni.offBluetoothAdapterStateChange()
    }
  } catch (_) {}
  uni.closeBluetoothAdapter({ complete: () => {} })
})
</script>

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

.header-hero {
  background: linear-gradient(135deg, var(--theme-primary), var(--theme-primary-dark));
  padding: 16rpx 32rpx 24rpx;
}

.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;
}

.page-title {
  font-size: 34rpx;
  font-weight: 600;
  color: #fff;
}

.content {
  padding: 32rpx;
  padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
}
.collapse-section {
  width: 100%;
  margin-top: 32rpx;
  border-radius: 16rpx;
  overflow: hidden;
  box-sizing: border-box;
  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.collapse-section--tips,
.collapse-section--debug {
  border: 1rpx solid rgba(31, 58, 138, 0.22);
  background: var(--theme-primary-light, #e8ecf5);
}
.collapse-header {
  width: 100%;
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: space-between;
  padding: 28rpx 32rpx;
  box-sizing: border-box;
}
.collapse-header--tips,
.collapse-header--debug {
  background: #d6dff0;
}
.collapse-title {
  flex: 1;
  min-width: 0;
  font-size: 30rpx;
  font-weight: 600;
  color: var(--theme-primary, #1f3a8a);
  text-align: left;
}
.collapse-action-wrap {
  flex-shrink: 0;
  margin-left: 24rpx;
  display: flex;
  align-items: center;
  justify-content: flex-end;
}
.collapse-action {
  font-size: 28rpx;
  font-weight: 500;
  color: var(--theme-primary);
  white-space: nowrap;
  text-align: right;
}
.collapse-body {
  border-top: 1rpx solid rgba(31, 58, 138, 0.12);
}

.type-tabs {
  display: flex;
  gap: 16rpx;
  margin-bottom: 24rpx;
}

.tab {
  flex: 1;
  height: 72rpx;
  border-radius: 14rpx;
  border: 2rpx solid #e5e7eb;
  background: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8rpx;
}

.tab.active {
  background: var(--theme-primary);
  border-color: var(--theme-primary);
}

.tab-text {
  font-size: 28rpx;
  font-weight: 500;
  color: #6b7280;
}

.tab.active .tab-text {
  color: #fff;
}

.error-banner {
  display: flex;
  align-items: center;
  gap: 16rpx;
  padding: 20rpx 24rpx;
  background: #fef2f2;
  border-radius: 12rpx;
  margin-bottom: 24rpx;
}

.error-text {
  font-size: 26rpx;
  color: #b91c1c;
  flex: 1;
}

.connected-card {
  background: #fff;
  padding: 32rpx;
  border-radius: 20rpx;
  margin-bottom: 32rpx;
  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
  border: 2rpx solid #4ade80;
}

.connected-header {
  display: flex;
  align-items: center;
  gap: 24rpx;
  margin-bottom: 24rpx;
}

.connected-icon {
  width: 80rpx;
  height: 80rpx;
  border-radius: 20rpx;
  background: var(--theme-primary);
  display: flex;
  align-items: center;
  justify-content: center;
}

.upos-card {
  background: #fff;
  padding: 28rpx;
  border-radius: 20rpx;
  margin-top: 20rpx;
  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
  border: 2rpx solid #e5e7eb;
}

.upos-title {
  font-size: 28rpx;
  font-weight: 700;
  color: #111827;
  margin-bottom: 10rpx;
}

.upos-desc {
  font-size: 24rpx;
  color: #6b7280;
  line-height: 34rpx;
  margin-bottom: 18rpx;
}

.upos-row {
  display: flex;
  align-items: center;
  gap: 16rpx;
  margin-top: 14rpx;
}

.upos-row-switch {
  justify-content: space-between;
}

.upos-label {
  width: 160rpx;
  font-size: 26rpx;
  color: #374151;
}

.upos-input {
  flex: 1;
  height: 68rpx;
  border-radius: 14rpx;
  border: 2rpx solid #e5e7eb;
  padding: 0 20rpx;
  font-size: 26rpx;
  color: #111827;
  background: #fff;
}

.upos-seg {
  flex: 1;
  display: flex;
  gap: 12rpx;
}

.upos-seg-btn {
  flex: 1;
  height: 64rpx;
  border-radius: 14rpx;
  border: 2rpx solid #e5e7eb;
  background: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 26rpx;
  color: #6b7280;
}

.upos-seg-btn.on {
  background: var(--theme-primary);
  border-color: var(--theme-primary);
  color: #fff;
}

.upos-actions {
  display: flex;
  gap: 14rpx;
  margin-top: 18rpx;
}

.upos-btn {
  flex: 1;
  height: 72rpx;
  border-radius: 16rpx;
  background: var(--theme-primary);
  display: flex;
  align-items: center;
  justify-content: center;
}

.upos-btn-secondary {
  background: #111827;
}

.upos-btn-text {
  font-size: 26rpx;
  font-weight: 600;
  color: #fff;
}

.connected-name {
  font-size: 32rpx;
  font-weight: 600;
  color: #111827;
  display: block;
  margin-bottom: 4rpx;
}

.connected-status {
  font-size: 26rpx;
  color: #16a34a;
  font-weight: 500;
}

.btn-test-print {
  width: 100%;
  height: 80rpx;
  background: var(--theme-primary);
  border-radius: 14rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12rpx;
  margin-bottom: 24rpx;
}
.btn-test-print.disabled {
  opacity: 0.6;
  pointer-events: none;
}
.btn-test-print-text {
  font-size: 28rpx;
  font-weight: 600;
  color: #fff;
  line-height: 1;
}

.btn-disconnect {
  width: 100%;
  height: 80rpx;
  background: #fff;
  border: 2rpx solid var(--theme-primary);
  border-radius: 14rpx;
  display: flex;
  align-items: center;
  justify-content: center;
}

.btn-disconnect-text {
  font-size: 28rpx;
  font-weight: 600;
  color: var(--theme-primary);
  line-height: 1;
}

.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 24rpx;
}

.section-title {
  font-size: 32rpx;
  font-weight: 600;
  color: #111827;
}

.btn-scan {
  display: flex;
  align-items: center;
  gap: 8rpx;
  height: 64rpx;
  padding: 0 24rpx;
  background: #fff;
  border: 2rpx solid #e5e7eb;
  border-radius: 14rpx;
}

.btn-scan-text {
  font-size: 26rpx;
  color: var(--theme-primary);
  font-weight: 500;
  line-height: 1;
}

.btn-scan.scanning {
  background: var(--theme-primary);
  border-color: var(--theme-primary);
}

.btn-scan.scanning .btn-scan-text {
  color: #fff;
}

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

.scan-anim {
  width: 120rpx;
  height: 120rpx;
  background: #eff6ff;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 24rpx;
  animation: pulse 1.5s ease-in-out infinite;
}

@keyframes pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.1); opacity: 0.7; }
}

.scan-text,
.empty-desc {
  font-size: 28rpx;
  color: #6b7280;
}

.empty-title {
  font-size: 30rpx;
  font-weight: 600;
  color: #374151;
  margin-bottom: 12rpx;
}

.device-list {
  margin-bottom: 32rpx;
}

.device-card {
  background: #fff;
  padding: 28rpx 32rpx;
  border-radius: 16rpx;
  margin-bottom: 16rpx;
  display: flex;
  align-items: center;
  gap: 24rpx;
  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
}

.device-card:active {
  background: #f9fafb;
}

.device-card.connecting {
  opacity: 0.7;
}

.device-icon {
  width: 72rpx;
  height: 72rpx;
  border-radius: 16rpx;
  background: #f3f4f6;
  display: flex;
  align-items: center;
  justify-content: center;
}

.device-icon.printer {
  background: #eff6ff;
}

.device-info {
  flex: 1;
  min-width: 0;
}

.device-name {
  font-size: 30rpx;
  font-weight: 600;
  color: #111827;
  display: block;
  margin-bottom: 4rpx;
}

.device-id {
  font-size: 22rpx;
  color: #9ca3af;
  display: block;
  margin-bottom: 6rpx;
}

.device-meta {
  display: flex;
  align-items: center;
  gap: 12rpx;
}

.debug-card {
  background: var(--theme-primary-light, #e8ecf5);
  border: none;
  border-radius: 0;
  padding: 24rpx 32rpx;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 8rpx;
}

.debug-item {
  font-size: 22rpx;
  color: #334155;
}

.debug-error {
  color: #b91c1c;
}

.debug-warn {
  color: #92400e;
  font-weight: 600;
}

.device-tag {
  display: inline-block;
  font-size: 20rpx;
  color: var(--theme-primary);
  background: var(--theme-primary-light);
  padding: 2rpx 12rpx;
  border-radius: 6rpx;
  font-weight: 500;
}

.device-rssi {
  font-size: 20rpx;
  color: #9ca3af;
}

.device-action {
  flex-shrink: 0;
}

.connecting-text {
  font-size: 24rpx;
  color: var(--theme-primary);
  font-weight: 500;
}

.builtin-card {
  background: #fff;
  padding: 32rpx;
  border-radius: 16rpx;
  margin-bottom: 24rpx;
  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
}

.switch-to-builtin-row {
  margin-bottom: 20rpx;
}

.btn-switch-to-builtin {
  height: 72rpx;
  border-radius: 14rpx;
  border: 2rpx solid #d1d5db;
  background: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}

.btn-switch-to-builtin-text {
  font-size: 26rpx;
  color: #374151;
  font-weight: 600;
}

.builtin-advanced-entry {
  margin-top: 14rpx;
  margin-bottom: 8rpx;
}

.btn-advanced {
  height: 64rpx;
  border-radius: 12rpx;
  background: #f3f4f6;
  display: flex;
  align-items: center;
  justify-content: center;
}

.btn-advanced-text {
  font-size: 24rpx;
  color: #4b5563;
  font-weight: 500;
}

.builtin-desc {
  font-size: 28rpx;
  color: #6b7280;
  display: block;
  margin-bottom: 24rpx;
  line-height: 1.5;
}

.btn-builtin {
  height: 80rpx;
  background: var(--theme-primary);
  border-radius: 14rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12rpx;
}

.btn-builtin-text {
  font-size: 28rpx;
  font-weight: 600;
  color: #fff;
}

.tips-card {
  background: var(--theme-primary-light, #e8ecf5);
  border: none;
  border-radius: 0;
  padding: 24rpx 32rpx;
  display: flex;
  flex-direction: column;
  gap: 8rpx;
}

.tips-item {
  font-size: 24rpx;
  color: #334155;
  line-height: 1.5;
}

.tips-item-last {
  margin-bottom: 0;
}
</style>
