export const AUDIO_INTERRUPTION_BEGIN_EVENT = "audio-interruption-begin";
export const AUDIO_INTERRUPTION_END_EVENT = "audio-interruption-end";

export const audioFils = {
  tententen: "https://static.shelingxingqiu.com/shootmini/static/audio/tententen.mp3",
  点击按钮: "https://static.shelingxingqiu.com/shootmini/static/audio/%E7%82%B9%E5%87%BB%E6%8C%89%E9%92%AE.mp3",
  设备连接已断开:
    "https://static.shelingxingqiu.com/shootmini/static/audio/%E8%AE%BE%E5%A4%87%E8%BF%9E%E6%8E%A5%E5%B7%B2%E6%96%AD%E5%BC%80.MP3",
  设备已连接:
    "https://static.shelingxingqiu.com/shootmini/static/audio/%E8%AE%BE%E5%A4%87%E5%B7%B2%E8%BF%9E%E6%8E%A5.MP3",
  "20CM全环靶": "https://static.shelingxingqiu.com/shootmini/static/audio/20CM%E5%85%A8%E7%8E%AF%E9%9D%B6-%E6%97%A0%E6%95%88.mp3",
  "40CM全环靶": "https://static.shelingxingqiu.com/shootmini/static/audio/40CM%E5%85%A8%E7%8E%AF%E9%9D%B6-%E6%97%A0%E6%95%88.mp3",
  // 激光已校准:
  //   "https://static.shelingxingqiu.com/attachment/2025-10-29/ddupaur1vdkyhzaqdc.mp3",
  胜利: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcuo9yjp0kt5msvmvd.mp3",
  失败: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcuo9yht2sdwhuqygy.mp3",
  请射箭测试距离:
    "https://static.shelingxingqiu.com/attachment/2025-09-17/dcuvj8avzqyw4hpq7t.mp3",
  距离合格:
    "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutwrda0amn5kqr4j.mp3",
  距离不足:
    "https://static.shelingxingqiu.com/attachment/2025-11-12/de6hr2faw28t0ianh0.mp3",
  "未发现靶纸，请瞄准靶纸射箭":
    "https://static.shelingxingqiu.com/shootmini/static/audio/%E6%9C%AA%E5%8F%91%E7%8E%B0%E9%9D%B6%E7%BA%B8%EF%BC%8C%E8%AF%B7%E7%9E%84%E5%87%86%E9%9D%B6%E7%BA%B8%E5%B0%84%E7%AE%AD.MP3",
  轮到你了:
    "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutzdrn4lxcpv8aqr.mp3",
  第一轮:
    "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutyyr9a7m1vz2w13.mp3",
  第二轮:
    "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutyyr9ldnfexjxtw.mp3",
  第三轮:
    "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutyyr97m4ipxaze4.mp3",
  第四轮:
    "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutyyr9x5addohlzf.mp3",
  第五轮:
    "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutyyr9d7lw2gebpv.mp3",
  决金箭轮:
    "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutwrd9zs4oi2kujv.mp3",
  请蓝方射箭:
    "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutzdrxcbe5ll46as.mp3",
  请红方射箭:
    "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutzdrl3re3dhlfjd.mp3",
  中场休息:
    "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutwrd9zdk1xyolst.mp3",
  比赛结束:
    "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutya59b6pu0ur4um.mp3",
  比赛开始:
    "https://static.shelingxingqiu.com/attachment/2025-09-17/dcuu5z3a3lumkutske.mp3",
  下半场开始:
    "https://static.shelingxingqiu.com/shootmini/static/audio/%E4%B8%8B%E5%8D%8A%E5%9C%BA%E5%BC%80%E5%A7%8B.mp3",
  请开始射击:
    "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutzdrl5u0iromqhf.mp3",
  射击无效:
    "https://static.shelingxingqiu.com/shootmini/static/audio/%E5%B0%84%E7%AE%AD%E6%97%A0%E6%95%88%E6%A3%80%E6%9F%A5%E8%B7%9D%E7%A6%BB%E5%92%8C%E9%9D%B6%E7%BA%B8.mp3",
  未上靶:
    "https://static.shelingxingqiu.com/attachment/2025-11-12/de6n45o3tsm1v4unam.mp3",
  "1环":
    "https://static.shelingxingqiu.com/shootaudio/v3/1.mp3",
  "2环":
    "https://static.shelingxingqiu.com/shootaudio/v3/2.mp3",
  "3环":
    "https://static.shelingxingqiu.com/shootaudio/v3/3.mp3",
  "4环":
    "https://static.shelingxingqiu.com/shootaudio/v3/4.mp3",
  "5环":
    "https://static.shelingxingqiu.com/shootaudio/v3/5.mp3",
  "6环":
    "https://static.shelingxingqiu.com/shootaudio/v3/6.mp3",
  "7环":
    "https://static.shelingxingqiu.com/shootaudio/v3/7.mp3",
  "8环":
    "https://static.shelingxingqiu.com/shootaudio/v3/8.mp3",
  "9环":
    "https://static.shelingxingqiu.com/shootaudio/v3/9.mp3",
  "10环":
    "https://static.shelingxingqiu.com/shootaudio/v3/10.mp3",
  X环: "https://static.shelingxingqiu.com/shootaudio/v4/v4/X%E7%8E%AF.mp3",
  向上调整:
    "https://static.shelingxingqiu.com/attachment/2025-11-12/de6ellf5pfvu3l8dhr.mp3",
  向右上调整:
    "https://static.shelingxingqiu.com/attachment/2025-11-12/de6ellf45v88pirarr.mp3",
  向右调整:
    "https://static.shelingxingqiu.com/attachment/2025-11-12/de6elleqnhrenggxsb.mp3",
  向右下调整:
    "https://static.shelingxingqiu.com/attachment/2025-11-12/de6elleo6q16qctf6a.mp3",
  向下调整:
    "https://static.shelingxingqiu.com/attachment/2025-11-12/de6ellek2mu2cri2n9.mp3",
  向左下调整:
    "https://static.shelingxingqiu.com/attachment/2025-11-12/de6ellf25yu1pt2k5r.mp3",
  向左调整:
    "https://static.shelingxingqiu.com/attachment/2025-11-12/de6ellen3zoalxcb06.mp3",
  向左上调整:
    "https://static.shelingxingqiu.com/attachment/2025-11-12/de6ellf37a2iw6w4pu.mp3",
  最后30秒:
    "https://static.shelingxingqiu.com/attachment/2025-11-13/de7kzzllq0futwynso.mp3",
  练习开始:
    "https://static.shelingxingqiu.com/attachment/2025-11-14/de88w0lmmt43nnfmoi.mp3",
  练习结束:
    "https://static.shelingxingqiu.com/shootmini/static/audio/%E7%BB%83%E4%B9%A0%E7%BB%93%E6%9D%9F.mp3",
  射箭声音:
	"https://static.shelingxingqiu.com/shootaudio/v4/v4/%E7%AE%AD%E9%A3%9E%E8%A1%8C.mp3",
  命中:
	"https://static.shelingxingqiu.com/shootaudio/%E5%91%BD%E4%B8%AD.mp3"
};

const AUDIO_LOAD_TIMEOUT_MS = 5000;
const AUDIO_WARM_CONCURRENCY = 3;
const AUDIO_WARM_RETRIES = 1;
const AUDIO_WARM_PRIORITY_KEYS = [
  "点击按钮",
  "设备连接已断开",
  "设备已连接",
  "比赛开始",
  "练习开始",
  "练习结束",
  "请开始射击",
  "轮到你了",
  "比赛结束",
  "射击无效",
  "中场休息",
  "下半场开始",
  "决金箭轮",
  "请蓝方射箭",
  "请红方射箭",
  "距离合格",
  "距离不足",
  "未发现靶纸，请瞄准靶纸射箭",
  "未上靶",
  "X环",
];

// 版本控制日志函数
function debugLog(...args) {
  // 获取当前环境信息
  const accountInfo = uni.getAccountInfoSync();
  const envVersion = accountInfo.miniProgram.envVersion;

  // 只在体验版打印日志，正式版(release)和开发版(develop)不打印
  if (envVersion === "trial" || envVersion === "develop") {
    console.log(...args);
  }
}

class AudioManager {
  constructor() {
    this.audioMap = new Map();
    this.currentPlayingKey = null;
    // 显式授权播放标记，防止 iOS 在设置 src 后误播
    this.allowPlayMap = new Map();

    // 后台预热与单条加载状态
    this.isLoading = false;
    this.loadingKeyPromises = new Map();
    this.pendingPlayKeys = new Set();
    this.cacheWritePromises = new Map();
    this.warmAllPromise = null;

    // 连续播放队列相关属性
    this.sequenceQueue = [];
    this.sequenceIndex = 0;
    this.isSequenceRunning = false;

    // 防重复播放保护
    this.lastPlayKey = null;
    this.lastPlayAt = 0;
    this.isInterrupted = false;
    this.interruptedAt = 0;
    this.interruptionFallbackMs = 5000;
    this.playWatchdogMs = 8000;
    this.playWatchdogTimers = new Map();

    // 静音开关
    this.isMuted = false;
    this.pendingPlayKey = null;
    this.readyMap = new Map();
    // 加载代数，用于 reloadAll 时作废旧的异步加载
    this.loadGeneration = 0;
    // 本地路径缓存 Map: { url: localPath }
    this.localFileCache = uni.getStorageSync("audio_local_files") || {};
    // 启动时自动清理过期的缓存文件（URL 已不在 audioFils 中的文件）
    this.cleanObsoleteCache();
    this.bindAudioInterruptionEvents();
  }

  bindAudioInterruptionEvents() {
    if (this._audioInterruptionBound) return;
    this._audioInterruptionBound = true;

    const begin = () => {
      if (this.isInterrupted) return;
      this.isInterrupted = true;
      this.interruptedAt = Date.now();
      this.stopAll();
      this.isSequenceRunning = false;
      this.sequenceQueue = [];
      this.sequenceIndex = 0;
      this.pendingPlayKey = null;
      uni.$emit(AUDIO_INTERRUPTION_BEGIN_EVENT);
    };

    const end = () => {
      if (!this.isInterrupted) return;
      this.isInterrupted = false;
      this.interruptedAt = 0;
      uni.$emit(AUDIO_INTERRUPTION_END_EVENT);
      void this.reloadAll();
    };

    if (typeof uni?.onAudioInterruptionBegin === "function") {
      uni.onAudioInterruptionBegin(begin);
    }
    if (typeof uni?.onAudioInterruptionEnd === "function") {
      uni.onAudioInterruptionEnd(end);
    }
  }

  // 清理不再使用的缓存文件
  cleanObsoleteCache() {
    const activeUrls = new Set(Object.values(audioFils));
    const cachedUrls = Object.keys(this.localFileCache);
    let hasChanges = false;

    for (const url of cachedUrls) {
      if (!activeUrls.has(url)) {
        debugLog(`发现废弃音频缓存，正在清理: ${url}`);
        const path = this.localFileCache[url];
        // 移除物理文件
        uni.removeSavedFile({
          filePath: path,
          complete: () => {
            // 忽略移除结果，直接移除记录
          },
        });
        // 移除记录
        delete this.localFileCache[url];
        hasChanges = true;
      }
    }

    if (hasChanges) {
      uni.setStorageSync("audio_local_files", this.localFileCache);
      debugLog("废弃缓存清理完成");
    }
  }

  getWarmupKeys() {
    return Array.from(
      new Set([...AUDIO_WARM_PRIORITY_KEYS, ...Object.keys(audioFils)])
    );
  }

  ensureAudio(key) {
    if (!audioFils[key]) return Promise.resolve(false);
    if (this.readyMap.get(key) && this.audioMap.has(key)) {
      return Promise.resolve(true);
    }

    const loadingPromise = this.loadingKeyPromises.get(key);
    if (loadingPromise) return loadingPromise;

    let promise;
    promise = new Promise((resolve) => {
      try {
        this.createAudio(key, () => {
          resolve(this.readyMap.get(key) === true && this.audioMap.has(key));
        });
      } catch (error) {
        debugLog(`音频 ${key} 创建失败`, error);
        resolve(false);
      }
    }).finally(() => {
      if (this.loadingKeyPromises.get(key) === promise) {
        this.loadingKeyPromises.delete(key);
      }
    });
    this.loadingKeyPromises.set(key, promise);
    return promise;
  }

  async warmKeys(
    keys,
    { concurrency = AUDIO_WARM_CONCURRENCY, retries = AUDIO_WARM_RETRIES } = {}
  ) {
    const queue = Array.from(new Set(keys)).filter((key) => !!audioFils[key]);
    if (!queue.length) return;

    let index = 0;
    const worker = async () => {
      while (index < queue.length) {
        const key = queue[index++];
        let ready = await this.ensureAudio(key);
        for (let attempt = 0; !ready && attempt < retries; attempt += 1) {
          ready = await this.retryLoadAudio(key);
        }
      }
    };

    const workerCount = Math.min(Math.max(1, concurrency), queue.length);
    await Promise.all(Array.from({ length: workerCount }, () => worker()));
  }

  warmButton() {
    return this.warmKeys(["点击按钮"], { concurrency: 1, retries: 1 });
  }

  warmAll() {
    if (this.warmAllPromise) return this.warmAllPromise;

    debugLog("开始后台分级预热全部语音");
    this.isLoading = true;
    this.warmAllPromise = this.warmKeys(this.getWarmupKeys())
      .catch((error) => {
        debugLog("后台语音预热异常", error);
      })
      .finally(() => {
        this.isLoading = false;
        debugLog("后台语音预热结束", this.getLoadProgress());
      });
    return this.warmAllPromise;
  }

  // 保留旧入口兼容测试页或其他历史调用，实际行为改为非阻塞后台预热。
  initAudios() {
    return this.warmAll();
  }

  // 创建单个音频实例：缓存命中优先使用，未命中直接使用 CDN。
  createAudio(key, callback) {
    const src = audioFils[key];
    const generation = this.loadGeneration;
    let completed = false;

    const complete = () => {
      if (completed) return;
      completed = true;
      if (callback) callback();
    };

    if (!src) {
      complete();
      return;
    }

    const existingAudio = this.audioMap.get(key);
    if (existingAudio && !this.readyMap.get(key)) {
      try {
        existingAudio.destroy();
      } catch (_) {}
      this.audioMap.delete(key);
    }

    const isCurrentGeneration = () => generation === this.loadGeneration;

    const setupAudio = (realSrc, resumeAfterReady = false) => {
      if (!isCurrentGeneration()) {
        complete();
        return;
      }

      const audio = uni.createInnerAudioContext();
      audio.autoplay = false;
      try {
        if (typeof audio.volume === "number") {
          audio.volume = this.isMuted ? 0 : 1;
        } else if (typeof audio.muted !== "undefined") {
          audio.muted = this.isMuted;
        }
      } catch (_) {}
      this.allowPlayMap.set(key, false);
      audio.onPlay(() => {
        if (!this.allowPlayMap.get(key)) {
          try {
            audio.stop();
          } catch (_) {}
        }
      });

      let loadTimeout = null;
      let readyHandled = false;
      const clearLoadTimeout = () => {
        if (!loadTimeout) return;
        clearTimeout(loadTimeout);
        loadTimeout = null;
      };

      const handleLoadFailure = (message) => {
        if (this.audioMap.get(key) !== audio) return;
        clearLoadTimeout();
        debugLog(`音频 ${key} ${message}`);

        const isLocalCache =
          realSrc !== src && this.localFileCache[src] === realSrc;
        const shouldResume =
          this.allowPlayMap.get(key) === true || this.currentPlayingKey === key;
        const shouldResumeDirectly = shouldResume && completed;

        this.clearPlayWatchdog(key);
        this.allowPlayMap.set(key, false);
        if (this.currentPlayingKey === key) {
          this.currentPlayingKey = null;
        }
        if (this.lastPlayKey === key) {
          this.lastPlayKey = null;
          this.lastPlayAt = 0;
        }
        this.readyMap.set(key, false);
        this.audioMap.delete(key);
        try {
          audio.destroy();
        } catch (_) {}

        if (isLocalCache && isCurrentGeneration()) {
          debugLog(`本地缓存失效，立即切换 CDN: ${key}`);
          delete this.localFileCache[src];
          uni.setStorageSync("audio_local_files", this.localFileCache);
          uni.removeSavedFile({ filePath: realSrc });
          setupAudio(src, shouldResumeDirectly);
          void this.cacheRemoteFile(src, key);
          return;
        }

        this.finishPlayback(key, {
          advanceSequence: true,
          emitEnded: true,
        });
        complete();
      };

      loadTimeout = setTimeout(() => {
        handleLoadFailure("加载超时");
      }, AUDIO_LOAD_TIMEOUT_MS);

      audio.onCanplay(() => {
        if (this.audioMap.get(key) !== audio) return;
        if (readyHandled) return;
        readyHandled = true;
        if (!isCurrentGeneration()) {
          clearLoadTimeout();
          this.audioMap.delete(key);
          try {
            audio.destroy();
          } catch (_) {}
          complete();
          return;
        }
        if (!this.allowPlayMap.get(key)) {
          try {
            audio.pause();
          } catch (_) {}
        }
        clearLoadTimeout();
        this.readyMap.set(key, true);
        uni.$emit("audioLoaded", key);
        const loadedAudioKeys = uni.getStorageSync("loadedAudioKeys") || {};
        loadedAudioKeys[key] = true;
        uni.setStorageSync("loadedAudioKeys", loadedAudioKeys);
        complete();

        if (resumeAfterReady) {
          setTimeout(() => {
            const isSequenceCurrent =
              this.isSequenceRunning &&
              this.sequenceQueue[this.sequenceIndex] === key;
            if (isSequenceCurrent) this._playSingle(key, false);
          }, 0);
        }
      });

      audio.onError((res) => {
        handleLoadFailure(`加载失败：${res.errMsg || "unknown"}`);
      });

      audio.onEnded(() => {
        this.finishPlayback(key, {
          advanceSequence: true,
          emitEnded: true,
        });
      });

      audio.onStop(() => {
        this.finishPlayback(key);
      });

      this.audioMap.set(key, audio);
      audio.src = realSrc;
    };

    // 检查是否有可用的本地缓存
    this.checkLocalFile(src).then((localPath) => {
      if (!isCurrentGeneration()) {
        complete();
        return;
      }
      if (localPath) {
        debugLog(`命中本地缓存: ${key}`);
        setupAudio(localPath);
      } else {
        setupAudio(src);
        void this.cacheRemoteFile(src, key);
      }
    }).catch(() => {
      setupAudio(src);
      void this.cacheRemoteFile(src, key);
    });
  }

  cacheRemoteFile(url, key) {
    if (this.localFileCache[url]) return Promise.resolve(true);
    const currentPromise = this.cacheWritePromises.get(url);
    if (currentPromise) return currentPromise;

    let promise;
    promise = new Promise((resolve) => {
      if (typeof uni.downloadFile !== "function") {
        resolve(false);
        return;
      }

      uni.downloadFile({
        url,
        timeout: 20000,
        success: (res) => {
          if (!res.tempFilePath) {
            resolve(false);
            return;
          }

          let fileSystemManager;
          try {
            fileSystemManager = uni.getFileSystemManager();
          } catch (_) {
            resolve(false);
            return;
          }

          fileSystemManager.saveFile({
            tempFilePath: res.tempFilePath,
            success: (saveRes) => {
              this.localFileCache[url] = saveRes.savedFilePath;
              uni.setStorageSync("audio_local_files", this.localFileCache);
              debugLog(`音频已后台缓存到本地: ${key}`);
              resolve(true);
            },
            fail: () => resolve(false),
          });
        },
        fail: () => resolve(false),
      });
    }).finally(() => {
      if (this.cacheWritePromises.get(url) === promise) {
        this.cacheWritePromises.delete(url);
      }
    });

    this.cacheWritePromises.set(url, promise);
    return promise;
  }

  // 检查本地文件是否有效
  checkLocalFile(url) {
    return new Promise((resolve) => {
      const path = this.localFileCache[url];
      if (!path) {
        resolve(null);
        return;
      }
      // 检查文件是否存在
      uni.getFileSystemManager().getFileInfo({
        filePath: path,
        success: () => {
          resolve(path);
        },
        fail: () => {
          // 文件不存在，清理记录
          delete this.localFileCache[url];
          uni.setStorageSync("audio_local_files", this.localFileCache);
          resolve(null);
        },
      });
    });
  }

  clearPlayWatchdog(key) {
    const timer = this.playWatchdogTimers.get(key);
    if (timer) {
      clearTimeout(timer);
      this.playWatchdogTimers.delete(key);
    }
  }

  clearAllPlayWatchdogs() {
    for (const timer of this.playWatchdogTimers.values()) {
      clearTimeout(timer);
    }
    this.playWatchdogTimers.clear();
  }

  startPlayWatchdog(key) {
    this.clearPlayWatchdog(key);
    const timer = setTimeout(() => {
      if (this.currentPlayingKey !== key) return;
      debugLog(`音频 ${key} 播放超时，跳过当前音频并继续队列`);
      this.finishPlayback(key, {
        advanceSequence: true,
        emitEnded: true,
        force: true,
      });
      this.reloadAudioKey(key);
    }, this.playWatchdogMs);
    this.playWatchdogTimers.set(key, timer);
  }

  finishPlayback(key, { advanceSequence = false, emitEnded = false, force = false } = {}) {
    const wasCurrent = this.currentPlayingKey === key;
    const isSequenceCurrent =
      this.isSequenceRunning && this.sequenceQueue[this.sequenceIndex] === key;

    this.clearPlayWatchdog(key);
    this.allowPlayMap.set(key, false);

    if (!force && !wasCurrent && !isSequenceCurrent) return false;

    if (wasCurrent) {
      this.currentPlayingKey = null;
    }

    if (advanceSequence && isSequenceCurrent) {
      this.onAudioEnded(key);
    }

    if (emitEnded) {
      uni.$emit("audioEnded", key);
    }

    return true;
  }

  recoverFromInterruptionIfStale(force = false) {
    if (!this.isInterrupted) return false;
    const interruptedFor = Date.now() - (this.interruptedAt || Date.now());
    if (!force && interruptedFor < this.interruptionFallbackMs) return false;

    debugLog("音频中断状态超时，执行兜底恢复");
    this.isInterrupted = false;
    this.interruptedAt = 0;
    uni.$emit(AUDIO_INTERRUPTION_END_EVENT);
    void this.reloadAll();
    return true;
  }

  recoverIfStale(expectedKey) {
    if (this.recoverFromInterruptionIfStale(true)) return;

    const key =
      expectedKey || this.currentPlayingKey || this.sequenceQueue[this.sequenceIndex];
    if (!key) {
      if (this.isSequenceRunning) {
        this.sequenceQueue = [];
        this.sequenceIndex = 0;
        this.isSequenceRunning = false;
      }
      return;
    }

    const isStaleCurrent =
      this.currentPlayingKey === key ||
      (this.isSequenceRunning && this.sequenceQueue[this.sequenceIndex] === key);
    if (!isStaleCurrent) return;

    debugLog(`音频 ${key} 等待超时，执行轻量恢复`);
    const audio = this.audioMap.get(key);
    if (audio) {
      try {
        audio.stop();
      } catch (_) {}
    }
    this.finishPlayback(key, {
      advanceSequence: true,
      emitEnded: true,
      force: true,
    });
    this.reloadAudioKey(key);
  }

  reloadAudioKey(key) {
    return this.retryLoadAudio(key);
  }

  // 重新加载音频
  retryLoadAudio(key) {
    const loadingPromise = this.loadingKeyPromises.get(key);
    if (loadingPromise) return loadingPromise;

    this.clearPlayWatchdog(key);
    const oldAudio = this.audioMap.get(key);
    if (oldAudio) {
      try {
        oldAudio.destroy();
      } catch (_) {}
    }
    this.audioMap.delete(key);
    this.readyMap.set(key, false);
    return this.ensureAudio(key);
  }

  // 播放指定音频或音频数组（数组则按顺序连续播放）
  play(input, interrupt = true) {
    if (this.isInterrupted) {
      this.recoverFromInterruptionIfStale();
    }
    if (this.isInterrupted) {
      debugLog("音频处理中断状态，忽略播放请求");
      return;
    }
    // 统一规范化为队列
    let queue = [];
    if (Array.isArray(input)) {
      queue = input.filter((k) => !!audioFils[k]);
    } else if (typeof input === "string") {
      queue = !!audioFils[input] ? [input] : [];
    } else {
      debugLog("play 参数类型无效，仅支持字符串或字符串数组");
      return;
    }

    if (queue.length === 0) {
      debugLog("连续播放队列为空或无效");
      return;
    }

    if (interrupt) {
      // 立即打断并启动新的播放序列
      this.stopAll();
      this.isSequenceRunning = false;
      this.sequenceQueue = [];
      this.sequenceIndex = 0;

      this.sequenceQueue = queue;
      this.sequenceIndex = 0;
      this.isSequenceRunning = true;
      this._playSingle(queue[0], false);
      return;
    }

    // 不打断当前播放：把新的队列加入到序列中，等待当前播放结束后衔接
    if (this.currentPlayingKey) {
      if (this.isSequenceRunning) {
        // 已有序列在跑：直接追加
        this.sequenceQueue = this.sequenceQueue.concat(queue);
      } else {
        // 没有序列但当前有正在播放的：以当前为序列的起点
        this.isSequenceRunning = true;
        this.sequenceQueue = [this.currentPlayingKey].concat(queue);
        this.sequenceIndex = 0;
        // 不触发 _playSingle，等待当前音频自然结束后由 onAudioEnded 接管
      }
    } else {
      // 当前没有播放：直接启动新的序列
      this.sequenceQueue = queue;
      this.sequenceIndex = 0;
      this.isSequenceRunning = true;
      this._playSingle(queue[0], false);
    }
  }

  waitForAudioAndPlay(key) {
    if (this.pendingPlayKeys.has(key)) return;
    this.pendingPlayKeys.add(key);

    this.ensureAudio(key)
      .then((ready) => {
        const isSequenceCurrent =
          this.isSequenceRunning && this.sequenceQueue[this.sequenceIndex] === key;
        if (!isSequenceCurrent) return;

        if (ready) {
          this._playSingle(key, false);
          return;
        }

        this.finishPlayback(key, {
          advanceSequence: true,
          emitEnded: true,
          force: true,
        });
      })
      .finally(() => {
        this.pendingPlayKeys.delete(key);
      });
  }

  // 内部方法：播放单个 key
  _playSingle(key, forceStopAll = false) {
    if (this.isInterrupted) {
      this.recoverFromInterruptionIfStale();
    }
    if (this.isInterrupted) {
      debugLog(`音频处理中断状态，跳过播放: ${key}`);
      return;
    }
    // 200ms 内的同 key 重复播放直接忽略，避免“比比赛开始”这类重复首音
    const now = Date.now();
    if (this.lastPlayKey === key && now - this.lastPlayAt < 250) {
      debugLog(`忽略快速重复播放: ${key}`);
      this.finishPlayback(key, {
        advanceSequence: true,
        emitEnded: true,
        force: true,
      });
      return;
    }

    if (forceStopAll) {
      this.stopAll();
    } else if (this.currentPlayingKey && this.currentPlayingKey !== key) {
      this.stop(this.currentPlayingKey);
    } else if (this.currentPlayingKey === key) {
      // 同一音频正在播放：不重启，避免听到重复开头
      return;
    }

    const audio = this.audioMap.get(key);
    if (audio && this.readyMap.get(key)) {
      // 播放前确保遵循当前静音状态
      try {
        if (typeof audio.volume === "number") {
          audio.volume = this.isMuted ? 0 : 1;
        } else if (typeof audio.muted !== "undefined") {
          audio.muted = this.isMuted;
        }
      } catch (_) {}
      // 同一音频：避免 stop() 触发 onStop 清除授权，使用 pause()+seek(0)
      try {
        audio.pause();
      } catch (_) {}
      try {
        if (typeof audio.seek === "function") {
          audio.seek(0);
        } else {
          audio.startTime = 0;
        }
      } catch (_) {
        audio.startTime = 0;
      }

      // 显式授权播放并立即播放
      this.allowPlayMap.set(key, true);

      try {
        audio.play();
      } catch (err) {
        this.finishPlayback(key, {
          advanceSequence: true,
          emitEnded: true,
          force: true,
        });
        debugLog(`音频 ${key} 播放调用失败`, err?.errMsg || err);
        return;
      }
      this.currentPlayingKey = key;
      this.lastPlayKey = key;
      this.lastPlayAt = Date.now();
      this.startPlayWatchdog(key);
    } else {
      debugLog(`音频 ${key} 尚未就绪，按需加载后播放...`);
      this.waitForAudioAndPlay(key);
    }
  }

  // 连续播放：在某个音频结束后，若处于队列播放状态则继续下一个
  onAudioEnded(key) {
    if (!this.isSequenceRunning) return;
    const currentKey = this.sequenceQueue[this.sequenceIndex];
    if (currentKey !== key) return;

    const nextIndex = this.sequenceIndex + 1;
    if (nextIndex < this.sequenceQueue.length) {
      this.sequenceIndex = nextIndex;
      const nextKey = this.sequenceQueue[nextIndex];
      this._playSingle(nextKey, false);
    } else {
      // 队列播放完成
      this.isSequenceRunning = false;
      this.sequenceQueue = [];
      this.sequenceIndex = 0;
    }
  }

  // 停止指定音频
  stop(key) {
    const audio = this.audioMap.get(key);
    this.clearPlayWatchdog(key);
    if (audio) {
      audio.stop();
      this.allowPlayMap.set(key, false);
      if (this.currentPlayingKey === key) {
        this.currentPlayingKey = null;
      }
    }
  }

  // 停止所有音频
  stopAll() {
    this.clearAllPlayWatchdogs();
    for (const [k, audio] of this.audioMap.entries()) {
      try {
        audio.stop();
      } catch (_) {}
      this.allowPlayMap.set(k, false);
    }
    this.currentPlayingKey = null;
  }

  // 设置静音开关：true 静音，false 取消静音
  setMuted(muted) {
    this.isMuted = !!muted;
    for (const audio of this.audioMap.values()) {
      try {
        if (typeof audio.volume === "number") {
          audio.volume = this.isMuted ? 0 : 1;
        } else if (typeof audio.muted !== "undefined") {
          audio.muted = this.isMuted;
        }
      } catch (_) {}
    }
    debugLog(`静音状态已设置为: ${this.isMuted}`);
  }

  // 新增：返回音频加载进度（0~1）
  getLoadProgress() {
    const keys = Object.keys(audioFils);
    const total = keys.length;
    if (total === 0) return 0;
    let loaded = 0;
    for (const k of keys) {
      if (this.readyMap.get(k)) loaded++;
    }
    return Number((loaded / total).toFixed(2));
  }

  // 清理本地音频缓存文件
  clearCache() {
    debugLog("开始清理本地音频缓存...");
    const cache = uni.getStorageSync("audio_local_files") || {};
    const paths = Object.values(cache);
    for (const path of paths) {
      uni.removeSavedFile({
        filePath: path,
        complete: (res) => {
          // 无论成功失败都继续
        },
      });
    }
    uni.removeStorageSync("audio_local_files");
    this.localFileCache = {};
    debugLog("本地音频缓存清理完成");
  }

  // 手动重置音频实例，并按之前的预热范围在后台恢复。
  reloadAll() {
    debugLog("执行 reloadAll: 重置音频实例并后台恢复");
    const shouldWarmAll = this.warmAllPromise !== null;
    this.loadGeneration += 1;

    // 1. 停止所有播放
    this.stopAll();

    // 2. 销毁现有音频实例
    for (const audio of this.audioMap.values()) {
      try {
        audio.destroy();
      } catch (_) {}
    }
    this.audioMap.clear();

    // 3. 重置状态
    this.readyMap.clear();
    this.allowPlayMap.clear();
    this.loadingKeyPromises.clear();
    this.pendingPlayKeys.clear();
    this.clearAllPlayWatchdogs();
    this.currentPlayingKey = null;
    this.sequenceQueue = [];
    this.sequenceIndex = 0;
    this.isSequenceRunning = false;

    // 清理一下可能损坏的缓存（可选，如果用户因为缓存坏了卡住，这一步很有用）
    // 这里选择不自动全清，而是依赖 onError 里的单点清除。如果需要彻底重置，可取消注释：
    // this.clearCache();

    // 4. 重置后台预热状态
    this.isLoading = false;
    this.warmAllPromise = null;

    // 5. 之前已启动过全量预热则继续全量恢复，否则只恢复按钮音效。
    return shouldWarmAll ? this.warmAll() : this.warmButton();
  }
}

// 导出单例
export default new AudioManager();
