zjk
2026-07-29 429208957f30ab9eedc5533a81aaa284048d09f5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
import Taro from '@tarojs/taro';
import { showToast } from '../../../components/toast';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { TripStatus } from '../../../constants/trip-status';
import { waitForOrderSuccessSubscribePopup } from '../../../../utils/subscribe';
 
type Deps = {
  tripStatus: TripStatus;
  orderId?: string;
  bluetoothAuthSuccess: boolean;
  startBluetoothAuth: () => void;
  /** 当前订单已开门时,跳过后续自动蓝牙鉴权,避免反复连接 */
  skipBluetoothAuth?: boolean;
  /** 车端钥匙下发失败(getUnlocktToken 接口非 200),蓝牙不可解锁 */
  unlockTokenFailed?: boolean;
};
 
type BluetoothAccessStatus = 'system_off' | 'unauthorized' | 'authorized';
type BluetoothErrorType = 'system_off' | 'permission_deny' | 'env_unsupported' | 'unknown';
type MiniappBluetoothAuthState = 'authorized' | 'denied' | 'unknown';
type BluetoothOpenResult = {
  opened: boolean;
  errorType: BluetoothErrorType;
  error: any;
};
type BluetoothPermissionLayerState = 'authorized' | 'denied' | 'not determined' | 'unknown';
type BluetoothPermissionSnapshot = {
  appBluetoothAuthorized: BluetoothPermissionLayerState;
  miniappBluetoothScope: MiniappBluetoothAuthState;
  systemInfo: Record<string, unknown>;
};
 
const BLUETOOTH_AUTH_KEY = 'ROBOTAXI_BLUETOOTH_AUTHORIZED';
const BLUETOOTH_SCOPE = 'scope.bluetooth' as any;
const SYSTEM_OFF_ERROR_RE = /(not available|10001|未开启|开关未打开|bluetooth adapter is not available)/i;
const ENV_UNSUPPORTED_ERROR_RE = /(暂不支持|not support|devtools|Mac 以外)/i;
const PERMISSION_DENY_ERROR_RE = /deny|auth deny|authorize|permission|scope/i;
const BLUETOOTH_ACCESS_LOG_PREFIX = '[蓝牙权限]';
const BLUETOOTH_AUTH_SYNC_DELAY_MS = 300; // 授权完成后首轮刷新延迟,给小程序权限态一点同步时间
const BLUETOOTH_AUTH_PROTECTION_WINDOW_MS = 5000; // 授权成功后的保护窗口,避免瞬时 permission_deny 覆盖 UI
const SUBSCRIBE_POPUP_WAIT_TIMEOUT_MS = 8000; // 进入接驾态时等待订阅弹窗结束的最长兜底时间,超时后仍放行蓝牙初始化
// 蓝牙图标资源:点亮(已授权)与熄灭(未开/未授权)两态
const BLUETOOTH_ICON_ON = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/bluetooth-icon-20260706.png';
export const BLUETOOTH_ICON_OFF = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/close-bluetooth-20260716.png';
// 车端钥匙下发失败时的描述文案与文字颜色
const UNLOCK_TOKEN_FAILED_TEXT = '蓝牙解锁失败, 请点击右下方按键解锁车门';
const UNLOCK_TOKEN_FAILED_COLOR = '#FF4D4F';
 
/**
 * 兼容读取小程序蓝牙权限状态。
 * 优先使用新版 App 级权限接口,旧环境再回退到 getSetting。
 */
async function readMiniappBluetoothAuthState() : Promise<MiniappBluetoothAuthState> {
  try {
    const appAuthorizeSetting = await Promise.resolve((Taro as any).getAppAuthorizeSetting?.());
    const bluetoothAuthorized = appAuthorizeSetting?.bluetoothAuthorized;
    if (typeof bluetoothAuthorized === 'string') {
      if (bluetoothAuthorized === 'authorized') {
        return 'authorized';
      }
      if (bluetoothAuthorized === 'denied') {
        return 'denied';
      }
    }
  } catch (error) {
  }
 
  try {
    const setting = await Taro.getSetting();
    const authSetting = setting?.authSetting || {};
    if (Object.prototype.hasOwnProperty.call(authSetting, BLUETOOTH_SCOPE)) {
      return authSetting[BLUETOOTH_SCOPE] === true ? 'authorized' : 'denied';
    }
  } catch (error) {
  }
 
  return 'unknown';
}
 
/**
 * 输出蓝牙权限链路日志,便于排查授权、系统蓝牙和环境问题。
 */
function logBluetoothAccess(message: string, payload?: Record<string, unknown>) {
  if (payload) {
    console.log(BLUETOOTH_ACCESS_LOG_PREFIX, message, payload);
    return;
  }
  console.log(BLUETOOTH_ACCESS_LOG_PREFIX, message);
}
 
function getBluetoothPermissionDeniedMessage(snapshot?: Partial<BluetoothPermissionSnapshot>) {
  if (snapshot?.appBluetoothAuthorized === 'denied') {
    return '请先在微信设置中授权蓝牙';
  }
  return '请先授权蓝牙权限';
}
 
/**
 * 统一分类蓝牙打开失败原因。
 */
function classifyBluetoothError(error: any): BluetoothErrorType {
  const errCode = Number(error?.errCode || 0);
  const errMsg = String(error?.errMsg || '');
  if (errCode === 10001 || SYSTEM_OFF_ERROR_RE.test(errMsg)) {
    return 'system_off';
  }
  if (ENV_UNSUPPORTED_ERROR_RE.test(errMsg)) {
    return 'env_unsupported';
  }
  if (PERMISSION_DENY_ERROR_RE.test(errMsg)) {
    return 'permission_deny';
  }
  return 'unknown';
}
 
/**
 * 安全打开蓝牙适配器,返回统一结果结构。
 */
async function openBluetoothAdapterSafe(): Promise<BluetoothOpenResult> {
  try {
    await Taro.openBluetoothAdapter();
    logBluetoothAccess('openBluetoothAdapter 成功');
    return { opened: true, errorType: 'unknown', error: null };
  } catch (error: any) {
    const errorType = classifyBluetoothError(error);
    logBluetoothAccess('openBluetoothAdapter 失败', {
      errorType,
      errCode: error?.errCode,
      errMsg: error?.errMsg
    });
    return {
      opened: false,
      errorType,
      error
    };
  }
}
 
/**
 * 蓝牙能力编排:
 * 1. 系统蓝牙未开启
 * 2. 系统蓝牙已开启,但小程序未授权
 * 3. 系统蓝牙已开启,且小程序已授权
 */
export default function useBluetoothAccess({
  tripStatus,
  orderId,
  bluetoothAuthSuccess,
  startBluetoothAuth,
  skipBluetoothAuth = false,
  unlockTokenFailed = false
}: Deps) {
  const [status, setStatus] = useState<BluetoothAccessStatus>('system_off'); // 当前蓝牙访问态:系统未开 / 小程序未授权 / 已授权
  const [statusReady, setStatusReady] = useState(false); // 蓝牙状态是否已完成首轮判定
  const [showAuthBluetoothPopup, setShowAuthBluetoothPopup] = useState(false); // 蓝牙授权引导弹窗展示态
  const [showSuccessAuthPopup, setShowSuccessAuthPopup] = useState(false); // 蓝牙授权成功弹窗展示态
  const adapterStateHandlerRef = useRef<((res: any) => void) | null>(null); // 蓝牙适配器状态监听函数引用
  const openSettingPollingTimerRef = useRef<ReturnType<typeof setInterval> | null>(null); // 去开启后的短轮询定时器
  const openSettingPollingCountRef = useRef(0); // 去开启后的短轮询计数
  const startBluetoothAuthRef = useRef(startBluetoothAuth); // 蓝牙鉴权动作引用,避免函数引用变化导致状态 effect 反复触发
  const recentAuthorizedAtRef = useRef(0); // 最近一次确认蓝牙授权成功的时间戳,用于保护首次授权后的 UI 状态
  const stopOpenSettingPollingRef = useRef<() => void>(() => {});
  const refreshBluetoothStateRef = useRef<() => Promise<void>>(async () => {});
  const startOpenSettingPollingRef = useRef<() => void>(() => {});
  const startBluetoothStateWatchRef = useRef<() => void>(() => {});
  const stopBluetoothStateWatchRef = useRef<() => void>(() => {});
  const triggerBluetoothAuthRef = useRef<(scene: string) => void>(() => {});
 
  triggerBluetoothAuthRef.current = (scene: string) => {
    if (skipBluetoothAuth) {
      logBluetoothAccess('当前订单已开门,跳过自动蓝牙鉴权触发', {
        scene,
        tripStatus,
        orderId
      });
      return;
    }
    startBluetoothAuthRef.current?.();
  };
 
  const getBluetoothSystemInfoSnapshot = useCallback(() => {
    try {
      const systemInfo = Taro.getSystemInfoSync() as any;
      return {
        platform: systemInfo?.platform,
        brand: systemInfo?.brand,
        model: systemInfo?.model,
        system: systemInfo?.system,
        version: systemInfo?.version,
        SDKVersion: systemInfo?.SDKVersion,
        bluetoothEnabled: systemInfo?.bluetoothEnabled,
        locationEnabled: systemInfo?.locationEnabled,
        locationAuthorized: systemInfo?.locationAuthorized
      };
    } catch (error: any) {
      return {
        systemInfoError: error?.message || String(error || '')
      };
    }
  }, []);
 
  const readBluetoothPermissionSnapshot = useCallback(async (): Promise<BluetoothPermissionSnapshot> => {
    let appBluetoothAuthorized: BluetoothPermissionLayerState = 'unknown';
    let miniappBluetoothScope: MiniappBluetoothAuthState = 'unknown';
    const systemInfo = getBluetoothSystemInfoSnapshot();
 
    try {
      const appAuthorizeSetting = await Promise.resolve((Taro as any).getAppAuthorizeSetting?.());
      const bluetoothAuthorized = appAuthorizeSetting?.bluetoothAuthorized;
      if (typeof bluetoothAuthorized === 'string') {
        appBluetoothAuthorized = bluetoothAuthorized as BluetoothPermissionLayerState;
      }
      logBluetoothAccess('读取微信 App 蓝牙授权状态', {
        bluetoothAuthorized,
        appAuthorizeSetting,
        systemInfo
      });
    } catch (error) {
      logBluetoothAccess('读取微信 App 蓝牙授权状态失败', {
        error,
        systemInfo
      });
    }
 
    try {
      const setting = await Taro.getSetting();
      const authSetting = setting?.authSetting || {};
      if (Object.prototype.hasOwnProperty.call(authSetting, BLUETOOTH_SCOPE)) {
        miniappBluetoothScope = authSetting[BLUETOOTH_SCOPE] === true ? 'authorized' : 'denied';
      }
      logBluetoothAccess('读取小程序蓝牙 scope 状态', {
        bluetoothScope: authSetting?.[BLUETOOTH_SCOPE],
        authSetting,
        systemInfo
      });
    } catch (error) {
      logBluetoothAccess('读取小程序蓝牙 scope 状态失败', {
        error,
        systemInfo
      });
    }
 
    return {
      appBluetoothAuthorized,
      miniappBluetoothScope,
      systemInfo
    };
  }, [getBluetoothSystemInfoSnapshot]);
 
  /**
   * 读取当前小程序蓝牙授权状态。
   */
  const getMiniappBluetoothAuthState = useCallback(async () => {
    const snapshot = await readBluetoothPermissionSnapshot();
    logBluetoothAccess('聚合蓝牙权限快照', snapshot);
    // 优先判断拒绝态:微信 App 没权限 或 小程序没授权,都视为拒绝
    if (snapshot.appBluetoothAuthorized === 'denied' || snapshot.miniappBluetoothScope === 'denied') {
      return 'denied';
    }
    // 其次判断授权态:必须小程序 scope 已授权(App 层即使是 unknown 只要没拒绝就放行)
    if (snapshot.miniappBluetoothScope === 'authorized') {
      return 'authorized';
    }
    logBluetoothAccess('蓝牙权限状态未知,等待用户触发授权或系统回传', snapshot);
    return 'unknown';
  }, [readBluetoothPermissionSnapshot]);
 
  const openBluetoothPermissionSetting = useCallback(async (scene: string, snapshot?: Partial<BluetoothPermissionSnapshot>) => {
    const nextSnapshot = snapshot || await readBluetoothPermissionSnapshot();
    const openAppAuthorizeSetting = (Taro as any).openAppAuthorizeSetting;
    const shouldOpenWechatAppSetting = nextSnapshot.appBluetoothAuthorized === 'denied' && typeof openAppAuthorizeSetting === 'function';
    logBluetoothAccess('准备打开蓝牙相关设置页', {
      scene,
      shouldOpenWechatAppSetting,
      ...nextSnapshot
    });
    if (shouldOpenWechatAppSetting) {
      await openAppAuthorizeSetting();
      return 'wechat_app_setting';
    }
    await Taro.openSetting();
    return 'miniapp_setting';
  }, [readBluetoothPermissionSnapshot]);
 
  /**
   * 已确认拿到小程序蓝牙权限后,统一收口后续初始化与兜底提示。
   */
  const finishBluetoothAuthorization = useCallback(async () => {
    logBluetoothAccess('进入授权完成收口流程', {
      tripStatus
    });
    recentAuthorizedAtRef.current = Date.now();
    Taro.setStorageSync(BLUETOOTH_AUTH_KEY, true);
    setShowAuthBluetoothPopup(false);
    setStatus('authorized');
    setStatusReady(true);
    triggerBluetoothAuthRef.current('finish_authorization');
    startOpenSettingPollingRef.current();
 
    const openResult = await openBluetoothAdapterSafe();
    logBluetoothAccess('授权完成后再次初始化蓝牙适配器', {
      opened: openResult.opened,
      errorType: openResult.errorType,
      errCode: openResult.error?.errCode,
      errMsg: openResult.error?.errMsg
    });
    if (openResult.opened) {
      setTimeout(() => {
        refreshBluetoothStateRef.current();
      }, BLUETOOTH_AUTH_SYNC_DELAY_MS);
      return;
    }
 
    if (openResult.errorType === 'permission_deny') {
      logBluetoothAccess('授权成功后权限态仍在同步,保护窗口内维持已授权态', {
        protectionWindowMs: BLUETOOTH_AUTH_PROTECTION_WINDOW_MS
      });
      return;
    }
 
    if (openResult.errorType === 'system_off') {
      recentAuthorizedAtRef.current = 0;
      stopOpenSettingPollingRef.current();
      setStatus('system_off');
      showToast({ title: '请先开启系统蓝牙', icon: 'none' });
      return;
    }
 
    showToast({ title: '蓝牙初始化失败,请稍后重试', icon: 'none' });
    setTimeout(() => {
      refreshBluetoothStateRef.current();
    }, BLUETOOTH_AUTH_SYNC_DELAY_MS);
  }, [orderId, skipBluetoothAuth, tripStatus]);
 
  useEffect(() => {
    startBluetoothAuthRef.current = startBluetoothAuth;
  }, [startBluetoothAuth]);
 
  /**
   * 停止“去开启”后的短轮询。
   */
  const stopOpenSettingPolling = () => {
    if (openSettingPollingTimerRef.current) {
      clearInterval(openSettingPollingTimerRef.current);
      openSettingPollingTimerRef.current = null;
    }
    openSettingPollingCountRef.current = 0;
  };
 
  stopOpenSettingPollingRef.current = stopOpenSettingPolling;
 
  /**
   * 刷新蓝牙状态:同时判断系统蓝牙开关与小程序授权态。
   */
  const refreshBluetoothStateImpl = async () => {
    let hasMiniappAuth = Boolean(Taro.getStorageSync(BLUETOOTH_AUTH_KEY));
    const isAuthorizeProtectionActive = (Date.now() - recentAuthorizedAtRef.current) < BLUETOOTH_AUTH_PROTECTION_WINDOW_MS;
    const systemInfoSnapshot = getBluetoothSystemInfoSnapshot();
    logBluetoothAccess('开始刷新蓝牙状态', {
      cachedMiniappAuth: hasMiniappAuth,
      tripStatus,
      isAuthorizeProtectionActive,
      bluetoothAuthSuccess,
      systemInfo: systemInfoSnapshot
    });
    try {
      const appAuthorizeSetting = await Promise.resolve((Taro as any).getAppAuthorizeSetting?.());
      const bluetoothAuthorized = appAuthorizeSetting?.bluetoothAuthorized;
      logBluetoothAccess('刷新蓝牙状态时读取设置', {
        bluetoothAuthorized,
        appAuthorizeSetting,
        systemInfo: systemInfoSnapshot
      });
      if (bluetoothAuthorized === 'authorized') {
        hasMiniappAuth = true;
        recentAuthorizedAtRef.current = 0;
      }
    } catch (error) {
      logBluetoothAccess('刷新蓝牙状态时读取 App 级权限失败', {
        error,
        systemInfo: systemInfoSnapshot
      });
    }
 
    if (!hasMiniappAuth) {
      const initialMiniappAuthState = await readMiniappBluetoothAuthState();
      logBluetoothAccess('刷新蓝牙状态时统一确认权限结果', {
        initialMiniappAuthState
      });
      if (initialMiniappAuthState === 'authorized') {
        hasMiniappAuth = true;
        Taro.setStorageSync(BLUETOOTH_AUTH_KEY, true);
        recentAuthorizedAtRef.current = 0;
      }
    }
 
    const syncMiniappAuthAfterSystemPrompt = async (scene: string) => {
      const latestMiniappAuthState = await getMiniappBluetoothAuthState();
      logBluetoothAccess(`${scene}后再次确认小程序蓝牙权限`, {
        latestMiniappAuthState,
        systemInfo: systemInfoSnapshot
      });
      if (latestMiniappAuthState === 'authorized') {
        hasMiniappAuth = true;
        Taro.setStorageSync(BLUETOOTH_AUTH_KEY, true);
        recentAuthorizedAtRef.current = 0;
        return true;
      }
      return false;
    };
 
    let systemBluetoothEnabled = false;
    const openResult = await openBluetoothAdapterSafe();
    logBluetoothAccess('openBluetoothAdapter 结果补充诊断', {
      opened: openResult.opened,
      errorType: openResult.errorType,
      errCode: openResult.error?.errCode,
      errMsg: openResult.error?.errMsg,
      systemInfo: systemInfoSnapshot
    });
    if (openResult.opened) {
      systemBluetoothEnabled = true;
      if (!hasMiniappAuth) {
        await syncMiniappAuthAfterSystemPrompt('蓝牙适配器打开');
      }
    } else if (openResult.errorType === 'system_off') {
      systemBluetoothEnabled = false;
      recentAuthorizedAtRef.current = 0;
    } else if (openResult.errorType === 'permission_deny') {
      systemBluetoothEnabled = true;
      const authGrantedAfterPrompt = await syncMiniappAuthAfterSystemPrompt('蓝牙权限弹窗处理');
      if (!authGrantedAfterPrompt && !isAuthorizeProtectionActive) {
        hasMiniappAuth = false;
        logBluetoothAccess('openBluetoothAdapter 返回 permission_deny,当前按未授权态处理', {
          appPermissionLikelyDenied: true,
          systemInfo: systemInfoSnapshot,
          errCode: openResult.error?.errCode,
          errMsg: openResult.error?.errMsg
        });
      } else if (!authGrantedAfterPrompt) {
        logBluetoothAccess('授权保护窗口内忽略瞬时 permission_deny', {
          errCode: openResult.error?.errCode,
          errMsg: openResult.error?.errMsg,
          systemInfo: systemInfoSnapshot
        });
      }
    } else {
      // 开发工具限制和未知异常按“系统已开启”兜底,避免误判成“去开启”。
      systemBluetoothEnabled = true;
    }
    logBluetoothAccess('蓝牙状态刷新结果', {
      systemBluetoothEnabled,
      hasMiniappAuth,
      opened: openResult.opened,
      errorType: openResult.errorType,
      errCode: openResult.error?.errCode,
      errMsg: openResult.error?.errMsg,
      systemInfo: systemInfoSnapshot
    });
 
    if (!systemBluetoothEnabled) {
      setStatus('system_off');
      setStatusReady(true);
      logBluetoothAccess('蓝牙状态判定完成', {
        nextStatus: 'system_off'
      });
      return;
    }
    const nextStatus = hasMiniappAuth ? 'authorized' : 'unauthorized';
    setStatus(nextStatus);
    setStatusReady(true);
    stopOpenSettingPollingRef.current();
    if (nextStatus !== 'authorized') {
      recentAuthorizedAtRef.current = 0;
    }
    logBluetoothAccess('蓝牙状态判定完成', {
      nextStatus
    });
    if (nextStatus === 'authorized') {
      triggerBluetoothAuthRef.current('refresh_state_authorized');
    }
  };
 
  refreshBluetoothStateRef.current = refreshBluetoothStateImpl;
 
  /**
   * 对外暴露稳定的蓝牙状态刷新函数。
   */
  const refreshBluetoothState = useCallback(() => {
    return refreshBluetoothStateRef.current();
  }, []);
 
  /**
   * 开启“去开启”后的短轮询兜底,处理未触发页面 show 的场景。
   */
  const startOpenSettingPollingImpl = () => {
    stopOpenSettingPollingRef.current();
    openSettingPollingCountRef.current = 0;
    logBluetoothAccess('启动设置页返回后的短轮询');
    openSettingPollingTimerRef.current = setInterval(() => {
      openSettingPollingCountRef.current += 1;
      logBluetoothAccess('轮询刷新蓝牙状态', {
        pollingCount: openSettingPollingCountRef.current
      });
      refreshBluetoothStateRef.current();
      if (openSettingPollingCountRef.current >= 10) {
        stopOpenSettingPollingRef.current();
      }
    }, 500);
  };
 
  startOpenSettingPollingRef.current = startOpenSettingPollingImpl;
 
  /**
   * 开启蓝牙适配器状态监听,系统层蓝牙开关变化时立即刷新状态。
   */
  const startBluetoothStateWatchImpl = () => {
    if (adapterStateHandlerRef.current) return;
    const onBluetoothStateChange = (res: any) => {
      logBluetoothAccess('收到蓝牙适配器状态变更', {
        available: res?.available,
        discovering: res?.discovering
      });
      if (res?.available === false) {
        setStatus('system_off');
        return;
      }
      refreshBluetoothStateRef.current();
    };
    const on = (Taro as any).onBluetoothAdapterStateChange;
    if (typeof on === 'function') {
      on(onBluetoothStateChange);
      adapterStateHandlerRef.current = onBluetoothStateChange;
    }
  };
 
  startBluetoothStateWatchRef.current = startBluetoothStateWatchImpl;
 
  /**
   * 关闭蓝牙适配器状态监听。
   */
  const stopBluetoothStateWatchImpl = () => {
    const off = (Taro as any).offBluetoothAdapterStateChange;
    if (typeof off === 'function' && adapterStateHandlerRef.current) {
      off(adapterStateHandlerRef.current);
    }
    adapterStateHandlerRef.current = null;
  };
 
  stopBluetoothStateWatchRef.current = stopBluetoothStateWatchImpl;
 
  // 仅在 4/5 状态维护蓝牙 UI;其他状态强制收起弹窗
  useEffect(() => {
    if ([TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus)) {
      if (!orderId) {
        logBluetoothAccess('等待 orderId 就绪');
        return;
      }
      logBluetoothAccess('进入蓝牙 UI 维护态', {
        tripStatus,
        orderId
      });
      setStatusReady(false);
      // 方案 B:先等待“下单成功”订阅弹窗结束,再初始化蓝牙
      // (refreshBluetoothState 内部 openBluetoothAdapter 会触发系统蓝牙授权弹窗),
      // 保证订阅弹窗与蓝牙授权弹窗按先后顺序出现,避免叠加;
      // Promise.race 设兜底超时,订阅异常未 complete 时不阻塞蓝牙初始化。
      let cancelled = false;
      const subscribeTimeout = new Promise<void>((resolve) => {
        setTimeout(resolve, SUBSCRIBE_POPUP_WAIT_TIMEOUT_MS);
      });
      Promise.race([waitForOrderSuccessSubscribePopup(), subscribeTimeout]).then(() => {
        if (cancelled) return;
        logBluetoothAccess('订阅弹窗已结束,开始初始化蓝牙');
        refreshBluetoothState();
      });
      startBluetoothStateWatchRef.current();
      return () => {
        cancelled = true;
        stopBluetoothStateWatchRef.current();
      };
    }
    logBluetoothAccess('离开蓝牙 UI 维护态,重置弹窗与监听', {
      tripStatus
    });
    setStatusReady(false);
    stopBluetoothStateWatchRef.current();
    stopOpenSettingPollingRef.current();
    setShowAuthBluetoothPopup(false);
    setShowSuccessAuthPopup(false);
  }, [tripStatus, orderId, refreshBluetoothState]);
 
  // 跳转系统蓝牙设置,并在返回后触发一次状态刷新
  const handleOpenSystemBluetoothSetting = useCallback(async () => {
    const systemInfoSnapshot = getBluetoothSystemInfoSnapshot();
    logBluetoothAccess('用户点击去开启系统蓝牙', {
      systemInfo: systemInfoSnapshot
    });
    try {
      const openSystemBluetoothSetting = (Taro as any).openSystemBluetoothSetting;
      if (typeof openSystemBluetoothSetting === 'function') {
        await openSystemBluetoothSetting();
      } else {
        await Taro.openSetting();
      }
    } catch (error) {
      logBluetoothAccess('打开系统蓝牙设置失败', {
        error,
        systemInfo: systemInfoSnapshot
      });
      showToast({ title: '请手动开启系统蓝牙', icon: 'none' });
    } finally {
      logBluetoothAccess('系统蓝牙设置流程结束,准备刷新状态', {
        systemInfo: systemInfoSnapshot
      });
      startOpenSettingPollingRef.current();
      setTimeout(() => {
        refreshBluetoothState();
      }, 300);
    }
  }, [getBluetoothSystemInfoSnapshot, refreshBluetoothState]);
 
  // 打开蓝牙授权弹窗
  const handleOpenAuthBluetoothPopup = () => setShowAuthBluetoothPopup(true);
 
  // 关闭蓝牙授权弹窗
  const handleCloseAuthBluetoothPopup = () => setShowAuthBluetoothPopup(false);
 
  // 关闭蓝牙授权成功弹窗
  const handleCloseSuccessAuthPopup = () => setShowSuccessAuthPopup(false);
 
  // 执行小程序蓝牙授权,成功后展示成功弹窗
  const handleAuthorizeBluetooth = useCallback(async () => {
    const miniappBluetoothAuthState = await getMiniappBluetoothAuthState();
    const permissionSnapshot = await readBluetoothPermissionSnapshot();
    logBluetoothAccess('用户触发蓝牙授权', {
      miniappBluetoothAuthState,
      ...permissionSnapshot
    });
    if (miniappBluetoothAuthState === 'denied') {
      try {
        const openedSettingType = await openBluetoothPermissionSetting('bluetooth_denied', permissionSnapshot);
        logBluetoothAccess('已从拒绝态打开权限设置页', {
          openedSettingType
        });
      } catch (openSettingError) {
        console.error('打开设置失败:', openSettingError);
        logBluetoothAccess('蓝牙权限已拒绝,打开设置失败', {
          openSettingError,
          ...permissionSnapshot
        });
        showToast({ title: getBluetoothPermissionDeniedMessage(permissionSnapshot), icon: 'none' });
        return;
      }
      const latestAuthState = await getMiniappBluetoothAuthState();
      const latestSnapshot = await readBluetoothPermissionSnapshot();
      logBluetoothAccess('从设置页返回后重新读取蓝牙权限', {
        latestAuthState,
        ...latestSnapshot
      });
      if (latestAuthState !== 'authorized') {
        showToast({ title: getBluetoothPermissionDeniedMessage(latestSnapshot), icon: 'none' });
        return;
      }
      await finishBluetoothAuthorization();
      return;
    }
 
    try {
      await Taro.authorize({ scope: BLUETOOTH_SCOPE });
      logBluetoothAccess('调用 authorize(scope.bluetooth) 成功');
    } catch (authorizeError) {
      console.log('授权蓝牙失败:', authorizeError);
      logBluetoothAccess('调用 authorize(scope.bluetooth) 失败', {
        authorizeError,
        ...permissionSnapshot
      });
      try {
        const openedSettingType = await openBluetoothPermissionSetting('authorize_failed', permissionSnapshot);
        logBluetoothAccess('authorize 失败后已打开权限设置页', {
          openedSettingType
        });
      } catch (openSettingError) {
        console.log('打开设置失败:', openSettingError);
        logBluetoothAccess('authorize 失败后打开设置失败', {
          openSettingError,
          ...permissionSnapshot
        });
        showToast({ title: getBluetoothPermissionDeniedMessage(permissionSnapshot), icon: 'none' });
        return;
      }
      const latestAuthState = await getMiniappBluetoothAuthState();
      const latestSnapshot = await readBluetoothPermissionSnapshot();
      logBluetoothAccess('authorize 失败后从设置页返回,重新读取蓝牙权限', {
        latestAuthState,
        ...latestSnapshot
      });
      if (latestAuthState !== 'authorized') {
        showToast({ title: getBluetoothPermissionDeniedMessage(latestSnapshot), icon: 'none' });
        return;
      }
      await finishBluetoothAuthorization();
      return;
    }
    await finishBluetoothAuthorization();
  }, [finishBluetoothAuthorization, getMiniappBluetoothAuthState, openBluetoothPermissionSetting, readBluetoothPermissionSnapshot]);
 
  // 蓝牙卡片点击分发:未开蓝牙->去开启,未授权->弹授权,已授权->弹成功态说明
  const handleBluetoothCardClick = useCallback(() => {
    logBluetoothAccess('点击蓝牙卡片', {
      status,
      tripStatus
    });
    if (status === 'system_off') {
      handleOpenSystemBluetoothSetting();
      return;
    }
    if (status === 'unauthorized') {
      handleOpenAuthBluetoothPopup();
      return;
    }
    setShowSuccessAuthPopup(true);
  }, [status, handleOpenSystemBluetoothSetting, handleOpenAuthBluetoothPopup]);
 
  // 蓝牙卡片主文案
  const cardDescription = useMemo(() => {
    if (unlockTokenFailed) {
      return UNLOCK_TOKEN_FAILED_TEXT;
    }
    if (status === 'system_off') {
      return '系统蓝牙未开启, 无法自动解锁车辆';
    }
    if (status === 'unauthorized') {
      return '授权蓝牙连接, 解锁无感出行';
    }
    return '手机靠近车辆,可自动解锁车门';
  }, [status, unlockTokenFailed]);
 
 
 
  useEffect(() => {
    return () => {
      stopBluetoothStateWatchRef.current();
      stopOpenSettingPollingRef.current();
    };
  }, []);
 
  // 蓝牙卡片右侧动作文案
  const cardActionText = useMemo(() => {
    // 失败态:无右侧动作(不展示按钮文字与箭头)
    if (unlockTokenFailed) {
      return '';
    }
    if (status === 'system_off') {
      return '去开启';
    }
    if (status === 'authorized') {
      return '';
    }
    return '去授权';
  }, [status, unlockTokenFailed]);
 
  // 蓝牙卡片左侧图标资源:仅当 status === 'authorized'(系统蓝牙已开 + 小程序已授权)时点亮
  const cardIconSrc = useMemo(() => {
    return status === 'authorized' ? BLUETOOTH_ICON_ON : BLUETOOTH_ICON_OFF;
  }, [status]);
 
  // 蓝牙卡片描述文字颜色:仅在车端钥匙下发失败时为红色,其他情况沿用默认色(空串表示不覆盖)
  const cardDescriptionColor = useMemo(() => {
    return unlockTokenFailed ? UNLOCK_TOKEN_FAILED_COLOR : '';
  }, [unlockTokenFailed]);
 
  // 蓝牙卡片是否可点击:失败态、系统蓝牙未开启时不可点击;其他状态可点击
  const cardClickable = useMemo(() => {
    if (unlockTokenFailed) {
      return false;
    }
    if (status === 'system_off') {
      return false;
    }
    return true;
  }, [status, unlockTokenFailed]);
 
  // 对外暴露:状态 + 视图文案 + 交互动作
  return {
    bluetoothStatus: status,
    bluetoothStatusReady: statusReady,
    bluetoothCardDescription: cardDescription,
    bluetoothCardActionText: cardActionText,
    bluetoothCardIconSrc: cardIconSrc,
    bluetoothCardDescriptionColor: cardDescriptionColor,
    bluetoothCardClickable: cardClickable,
    showAuthBluetoothPopup,
    showSuccessAuthPopup,
    handleBluetoothCardClick,
    handleOpenAuthBluetoothPopup,
    handleCloseAuthBluetoothPopup,
    handleCloseSuccessAuthPopup,
    handleAuthorizeBluetooth,
    refreshBluetoothState
  };
}