import _classCallCheck from '@babel/runtime/helpers/classCallCheck';
import _createClass from '@babel/runtime/helpers/createClass';
import { DEFAULT_SERVICE_UUID, DEFAULT_CHARACTERISTIC_UUID, DEFAULT_RSSI_THRESHOLD, DEFAULT_COOLDOWN_MS, DEFAULT_LINGER_BEFORE_STOP_MS, DEFAULT_POLL_INTERVAL_MS, DEFAULT_PRIVACY_CONTENT, DEFAULT_PEER_TTL_MS, DEFAULT_ADVERTISE_UPDATE_THROTTLE_MS } from './constants.mjs';
import { genTempId, encodePayload, matchesService, shouldTriggerBump, parsePayload } from './utils.mjs';
import { wxBluetoothAdapter } from './wx-adapter.mjs';
import '@babel/runtime/helpers/slicedToArray';
import '@babel/runtime/helpers/typeof';

function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
var BluetoothBump = /*#__PURE__*/function () {
  function BluetoothBump() {
    var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
    var deps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    _classCallCheck(this, BluetoothBump);
    // -------- 运行时状态（public） --------
    /** 我方临时 ID（每次启动随机生成，写到广播特征值里） */
    this.myTempId = '';
    /** 当前运行平台 */
    this.platform = 'unknown';
    /** 是否跳过广播（peripheral 初始化失败时降级为只扫描） */
    this.skipAdvertising = false;
    /** 是否已经成功触发过碰一碰 */
    this.hasBumped = false;
    // -------- 运行时状态（private） --------
    this.peripheralServer = null;
    this.isDiscovering = false;
    this.bumpedMap = new Map();
    this.gracefulStopTimer = null;
    this.pollTimer = null;
    /** mutual 模式：候选 peer 池，key = peerTempId */
    this.seenPeers = new Map();
    /** mutual 模式：当前广播中"点名"的 peerId（为空表示还没看到任何人） */
    this.currentAdvertisedPeer = '';
    /** mutual 模式：上次更新广播的时间戳（用于节流） */
    this.lastAdvertiseUpdateAt = 0;
    /** mutual 模式：updateAdvertisedValue 连续失败后降级为 simple（仅当前会话内） */
    this.advertiseUpdateBroken = false;
    this.deviceFoundCb = null;
    this.adapterStateChangeCb = null;
    this.serviceUuid = opts.serviceUuid || DEFAULT_SERVICE_UUID;
    this.characteristicUuid = opts.characteristicUuid || DEFAULT_CHARACTERISTIC_UUID;
    this.rssiThreshold = typeof opts.rssiThreshold === 'number' ? opts.rssiThreshold : DEFAULT_RSSI_THRESHOLD;
    this.cooldownMs = typeof opts.cooldownMs === 'number' ? opts.cooldownMs : DEFAULT_COOLDOWN_MS;
    this.lingerBeforeStopMs = typeof opts.lingerBeforeStopMs === 'number' ? opts.lingerBeforeStopMs : DEFAULT_LINGER_BEFORE_STOP_MS;
    this.pollIntervalMs = typeof opts.pollIntervalMs === 'number' ? opts.pollIntervalMs : DEFAULT_POLL_INTERVAL_MS;
    this.privacyContent = opts.privacyContent || DEFAULT_PRIVACY_CONTENT;
    this.mode = opts.mode === 'mutual' ? 'mutual' : 'simple';
    this.peerTtlMs = typeof opts.peerTtlMs === 'number' ? opts.peerTtlMs : DEFAULT_PEER_TTL_MS;
    this.advertiseUpdateThrottleMs = typeof opts.advertiseUpdateThrottleMs === 'number' ? opts.advertiseUpdateThrottleMs : DEFAULT_ADVERTISE_UPDATE_THROTTLE_MS;
    this.onBump = opts.onBump;
    this.onError = opts.onError;
    this.onLog = opts.onLog;
    this.onDeviceFoundCallback = opts.onDeviceFound;
    this.adapter = deps.adapter || wxBluetoothAdapter;
    this.now = deps.now || function () {
      return Date.now();
    };
    this.setTimeoutFn = deps.setTimeoutFn || function (fn, ms) {
      return setTimeout(fn, ms);
    };
    this.clearTimeoutFn = deps.clearTimeoutFn || function (h) {
      return clearTimeout(h);
    };
    this.setIntervalFn = deps.setIntervalFn || function (fn, ms) {
      return setInterval(fn, ms);
    };
    this.clearIntervalFn = deps.clearIntervalFn || function (h) {
      return clearInterval(h);
    };
  }
  // ==================== 对外 API ====================
  /** 启动碰一碰（必须在用户点击事件回调中调用） */
  return _createClass(BluetoothBump, [{
    key: "start",
    value: function start() {
      var _this = this;
      var _a;
      if (!this.adapter.isAvailable()) {
        this.log('当前环境不是微信小游戏，无法使用蓝牙 API');
        (_a = this.onError) === null || _a === void 0 ? void 0 : _a.call(this, {
          errMsg: 'not in wx env'
        });
        return Promise.resolve();
      }
      this.myTempId = genTempId();
      this.platform = this.adapter.detectPlatform();
      this.skipAdvertising = false;
      this.seenPeers.clear();
      this.currentAdvertisedPeer = '';
      this.lastAdvertiseUpdateAt = 0;
      this.advertiseUpdateBroken = false;
      this.log("start: platform=".concat(this.platform, ", myTempId=").concat(this.myTempId, ", mode=").concat(this.mode));
      return this.adapter.ensurePrivacyAuthorized(this.privacyContent, function (m) {
        return _this.log(m);
      }).then(function () {
        _this.log('✅ 隐私协议已授权');
        return _this.adapter.openAdapter(_this.platform, function (m) {
          return _this.log(m);
        });
      }).then(function (openRes) {
        _this.skipAdvertising = openRes.skipAdvertising;
        _this.log('✅ 蓝牙适配器已打开');
        if (_this.skipAdvertising) {
          _this.log('⚠️ 跳过广播（仅扫描模式）');
          return undefined;
        }
        return _this.adapter.startAdvertising({
          serviceUuid: _this.serviceUuid,
          characteristicUuid: _this.characteristicUuid,
          tempId: _this.myTempId,
          // 始终广播完整 payload（simple 模式下 seenPeerId 留空）：
          // 这样对端不论 simple/mutual 都能稳定解析出我们的 myTempId，
          // 业务层 onBump 拿到的 peerTempId 才不会为空，两台手机的"我方ID/对方ID"才能对上。
          // mutual 模式后续会通过 updateAdvertisedValue 把 seenPeerId 填上。
          payload: encodePayload(_this.myTempId, '')
        }).then(function (advRes) {
          _this.peripheralServer = advRes.server;
          _this.log("\u2705 \u5DF2\u5F00\u59CB\u5E7F\u64AD\uFF0C\u4E34\u65F6ID=".concat(_this.myTempId));
        })["catch"](function (advErr) {
          _this.log("\u26A0\uFE0F \u5E7F\u64AD\u542F\u52A8\u5931\u8D25\uFF0C\u964D\u7EA7\u4E3A\u53EA\u626B\u63CF\u6A21\u5F0F\uFF1A".concat((advErr === null || advErr === void 0 ? void 0 : advErr.errMsg) || advErr));
        });
      }).then(function () {
        return _this.adapter.startDiscovery({
          platform: _this.platform,
          serviceUuid: _this.serviceUuid
        });
      }).then(function () {
        _this.isDiscovering = true;
        _this.log('✅ 已开始扫描附近设备');
        _this.bindDeviceFound();
        _this.log("\uD83C\uDFAF \u7B49\u5F85\u9760\u8FD1\u53E6\u4E00\u53F0\u624B\u673A\uFF08RSSI > ".concat(_this.rssiThreshold, " \u65F6\u89E6\u53D1\uFF09"));
      })["catch"](function (err) {
        _this.handleStartError(err);
      });
    }
    /** 停止：解绑所有监听 + 停止扫描 + 停止广播 + 关闭适配器 */
  }, {
    key: "stop",
    value: function stop() {
      if (this.gracefulStopTimer) {
        this.clearTimeoutFn(this.gracefulStopTimer);
        this.gracefulStopTimer = null;
      }
      if (this.pollTimer) {
        this.clearIntervalFn(this.pollTimer);
        this.pollTimer = null;
      }
      if (this.deviceFoundCb) {
        this.adapter.offDeviceFound(this.deviceFoundCb);
        this.deviceFoundCb = null;
      }
      if (this.adapterStateChangeCb) {
        this.adapter.offAdapterStateChange(this.adapterStateChangeCb);
        this.adapterStateChangeCb = null;
      }
      if (this.isDiscovering) {
        this.adapter.stopDiscovery();
        this.isDiscovering = false;
      }
      if (this.peripheralServer) {
        this.adapter.stopAdvertising(this.peripheralServer);
        this.peripheralServer = null;
      }
      this.seenPeers.clear();
      this.currentAdvertisedPeer = '';
      this.adapter.closeAdapter();
      this.log('🛑 已停止');
    }
    // ==================== 内部流程 ====================
    /** 启动失败的统一处理（业务方可通过 onError 拿到原始错误） */
  }, {
    key: "handleStartError",
    value: function handleStartError(err) {
      var _a;
      var errCode = err === null || err === void 0 ? void 0 : err.errCode;
      var errMsg = (err === null || err === void 0 ? void 0 : err.errMsg) || JSON.stringify(err);
      this.log("\u274C \u542F\u52A8\u5931\u8D25\uFF1A".concat(errMsg));
      if (errCode === 10001) {
        this.log('⚠️ 系统蓝牙未开启，监听适配器状态以便自动重试');
        this.watchAdapterStateForRetry();
      }
      (_a = this.onError) === null || _a === void 0 ? void 0 : _a.call(this, {
        errCode: errCode,
        errMsg: errMsg,
        raw: err
      });
    }
    /** 监听蓝牙适配器状态变化：用户打开蓝牙后自动重试 */
  }, {
    key: "watchAdapterStateForRetry",
    value: function watchAdapterStateForRetry() {
      var _this2 = this;
      if (this.adapterStateChangeCb) return;
      this.adapterStateChangeCb = function (res) {
        _this2.log('🔄 蓝牙适配器状态变化', res);
        if (res.available && !_this2.isDiscovering) {
          _this2.log('✅ 检测到蓝牙已开启，自动重试');
          if (_this2.adapterStateChangeCb) {
            _this2.adapter.offAdapterStateChange(_this2.adapterStateChangeCb);
            _this2.adapterStateChangeCb = null;
          }
          _this2.start();
        }
      };
      this.adapter.onAdapterStateChange(this.adapterStateChangeCb);
    }
    /** 监听设备发现 + 启动 iOS 兜底轮询 */
  }, {
    key: "bindDeviceFound",
    value: function bindDeviceFound() {
      var _this3 = this;
      this.deviceFoundCb = function (res) {
        var _a;
        var devices = (res === null || res === void 0 ? void 0 : res.devices) || [];
        var _iterator = _createForOfIteratorHelper(devices),
          _step;
        try {
          for (_iterator.s(); !(_step = _iterator.n()).done;) {
            var dev = _step.value;
            (_a = _this3.onDeviceFoundCallback) === null || _a === void 0 ? void 0 : _a.call(_this3, dev);
            _this3.handleDevice(dev);
          }
        } catch (err) {
          _iterator.e(err);
        } finally {
          _iterator.f();
        }
      };
      this.adapter.onDeviceFound(this.deviceFoundCb);
      this.startPolling();
    }
    /** 调试用：定期 getBluetoothDevices，避免 iOS 上 allowDuplicatesKey 不上报重复的问题 */
  }, {
    key: "startPolling",
    value: function startPolling() {
      var _this4 = this;
      if (this.pollTimer) return;
      var tick = function tick() {
        if (_this4.hasBumped || !_this4.isDiscovering) {
          if (_this4.pollTimer) {
            _this4.clearIntervalFn(_this4.pollTimer);
            _this4.pollTimer = null;
          }
          return;
        }
        _this4.adapter.getDevices().then(function (res) {
          var list = res.devices || [];
          var _iterator2 = _createForOfIteratorHelper(list),
            _step2;
          try {
            for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
              var dev = _step2.value;
              if (matchesService(dev, _this4.serviceUuid)) {
                _this4.handleDevice(dev);
              }
            }
          } catch (err) {
            _iterator2.e(err);
          } finally {
            _iterator2.f();
          }
        })["catch"](function () {
          // 轮询失败忽略，等下次 tick
        });
      };
      this.pollTimer = this.setIntervalFn(tick, this.pollIntervalMs);
    }
    /** 处理扫到的单个设备 */
  }, {
    key: "handleDevice",
    value: function handleDevice(dev) {
      if (this.hasBumped) return;
      // iOS 不传 services 时，需要手动过滤同伴
      if (this.platform === 'ios' && !matchesService(dev, this.serviceUuid)) {
        return;
      }
      // mutual 模式：走双向互认流程
      if (this.mode === 'mutual' && !this.advertiseUpdateBroken) {
        this.handleDeviceMutual(dev);
        return;
      }
      var lastTime = this.bumpedMap.get(dev.deviceId) || 0;
      var now = this.now();
      var triggerable = shouldTriggerBump({
        rssi: dev.RSSI,
        threshold: this.rssiThreshold,
        lastBumpTime: lastTime,
        now: now,
        cooldownMs: this.cooldownMs
      });
      if (!triggerable) return;
      this.bumpedMap.set(dev.deviceId, now);
      // simple 模式下也尽量解析出对方 payload 里的 myTempId（解析失败也无所谓，传空串）
      var peerPayload = parsePayload(dev, this.serviceUuid);
      if (!peerPayload) {
        // 调试：解析失败时把对端广播的"形态"打出来，方便定位是 serviceData/name 没下发还是格式不对
        var sdKeys = dev.serviceData ? Object.keys(dev.serviceData).join(',') : '(无)';
        var nameStr = dev.localName || dev.name || '(无)';
        this.log("\u26A0\uFE0F parsePayload \u5931\u8D25\uFF1AdeviceId=".concat(dev.deviceId, " name=\"").concat(nameStr, "\" serviceData.keys=[").concat(sdKeys, "] hasAdvertisData=").concat(!!dev.advertisData));
      }
      this.triggerBump(dev, (peerPayload === null || peerPayload === void 0 ? void 0 : peerPayload.myTempId) || '');
    }
    /**
     * mutual 模式（v2，方案2 实现）：协议双向 simple
     *
     * 设计取舍：
     *   v1 mutual 用"动态更新广播 payload 的 seenPeerId"做双向互认，但 iOS 上
     *   wx 的 updateBLEAdvertising → stop+addService+start 链路在真机上极不可靠：
     *     - deviceName 不会真正刷新（CoreBluetooth 缓存）
     *     - addService 重复注册会失败、走兜底分支后广播就停了
     *     - 高频 update 还会让 iOS 端事件循环卡死
     *   导致弱信号端永远收不到"被点名"广播 → 不弹窗。
     *
     * v2 直接退一步：
     *   - 不再动态更新广播 payload；广播一次启动后保持初始 payload 不变
     *   - 触发条件：扫到的对端必须能解析出 BUMP_ 协议 payload（协议匹配） + 本机 RSSI ≥ 阈值
     *   - "双向"靠两端都跑这套规则、各自独立达阈值时触发；阈值收紧（≈10cm）就足够防误触
     *   - iOS↔Android RSSI 不对称的代价：可能两端弹窗时差最大到秒级（取决于谁先稳定过阈值）
     *     但相比 v1 的"一端永远不弹"，这是更可接受的退化
     */
  }, {
    key: "handleDeviceMutual",
    value: function handleDeviceMutual(dev) {
      var payload = parsePayload(dev, this.serviceUuid);
      if (!payload) return; // 不是本协议的广播，忽略（核心防误触：必须是 BUMP_ 协议设备）
      if (payload.myTempId === this.myTempId) return; // 扫到自己，忽略
      var lastTime = this.bumpedMap.get(dev.deviceId) || 0;
      var now = this.now();
      var triggerable = shouldTriggerBump({
        rssi: dev.RSSI,
        threshold: this.rssiThreshold,
        lastBumpTime: lastTime,
        now: now,
        cooldownMs: this.cooldownMs
      });
      if (!triggerable) return;
      this.bumpedMap.set(dev.deviceId, now);
      this.triggerBump(dev, payload.myTempId);
    }
    /** mutual 模式：更新自己广播里的 seenPeerId（带节流 + 失败降级） */
  }, {
    key: "updateAdvertisedPeer",
    value: function updateAdvertisedPeer(peerId) {
      var _this5 = this;
      if (this.skipAdvertising || !this.peripheralServer) return;
      if (this.advertiseUpdateBroken) return;
      var now = this.now();
      if (now - this.lastAdvertiseUpdateAt < this.advertiseUpdateThrottleMs) return;
      this.lastAdvertiseUpdateAt = now;
      var payload = encodePayload(this.myTempId, peerId);
      this.log("\uD83D\uDCE2 \u66F4\u65B0\u5E7F\u64AD payload = ".concat(payload));
      this.adapter.updateAdvertisedValue(this.peripheralServer, {
        serviceUuid: this.serviceUuid,
        characteristicUuid: this.characteristicUuid,
        tempId: this.myTempId,
        payload: payload
      }).then(function () {
        _this5.currentAdvertisedPeer = peerId;
      })["catch"](function (err) {
        _this5.log("\u26A0\uFE0F \u66F4\u65B0\u5E7F\u64AD\u5931\u8D25\uFF0C\u672C\u4F1A\u8BDD\u5185\u964D\u7EA7\u4E3A simple \u6A21\u5F0F\uFF1A".concat((err === null || err === void 0 ? void 0 : err.errMsg) || err));
        _this5.advertiseUpdateBroken = true;
      });
    }
    /** 触发碰一碰成功：通知业务方 + 优雅停止（保持广播一段时间） */
  }, {
    key: "triggerBump",
    value: function triggerBump(dev, peerTempId) {
      var _this6 = this;
      var _a;
      this.hasBumped = true;
      this.log("\uD83C\uDF89 \u78B0\u4E00\u78B0\u6210\u529F\uFF01peerId=".concat(dev.deviceId, ", peerTempId=").concat(peerTempId || '(未解析)', ", RSSI=").concat(dev.RSSI));
      try {
        (_a = this.onBump) === null || _a === void 0 ? void 0 : _a.call(this, dev.deviceId, dev.RSSI, dev, peerTempId);
      } catch (e) {
        this.log("onBump \u56DE\u8C03\u629B\u9519\uFF1A".concat((e === null || e === void 0 ? void 0 : e.message) || e));
      }
      // 不立刻 stop()：保持广播一段时间，让对方也扫到自己
      this.stopDiscoveryOnly();
      this.log("\u23F3 \u5DF2\u505C\u6B62\u626B\u63CF\uFF0C\u4FDD\u6301\u5E7F\u64AD ".concat(this.lingerBeforeStopMs, "ms \u8BA9\u5BF9\u65B9\u8BC6\u522B\u81EA\u5DF1"));
      if (this.gracefulStopTimer) this.clearTimeoutFn(this.gracefulStopTimer);
      this.gracefulStopTimer = this.setTimeoutFn(function () {
        _this6.log('🛑 延迟关闭：彻底停止');
        _this6.stop();
      }, this.lingerBeforeStopMs);
    }
    /** 仅停止扫描（保留广播） */
  }, {
    key: "stopDiscoveryOnly",
    value: function stopDiscoveryOnly() {
      if (this.pollTimer) {
        this.clearIntervalFn(this.pollTimer);
        this.pollTimer = null;
      }
      if (this.deviceFoundCb) {
        this.adapter.offDeviceFound(this.deviceFoundCb);
        this.deviceFoundCb = null;
      }
      if (this.isDiscovering) {
        this.adapter.stopDiscovery();
        this.isDiscovering = false;
      }
    }
    // ==================== 工具 ====================
  }, {
    key: "log",
    value: function log(msg, data) {
      if (this.onLog) {
        this.onLog(msg, data);
      } else if (data !== undefined) {
        // eslint-disable-next-line no-console
        console.log('[BluetoothBump]', msg, data);
      } else {
        // eslint-disable-next-line no-console
        console.log('[BluetoothBump]', msg);
      }
    }
  }]);
}();

export { BluetoothBump };
