import '../chunk-WMJKH4XE.mjs';

// src/iflytek/IflytekSTT.ts
var IflytekSTT = class {
  constructor(config) {
    this.events = {};
    this.session = null;
    this.bound = false;
    this.lastPressTime = 0;
    this.transport = config.transport;
    this.recorder = config.recorder;
    this.sampleRate = config.sampleRate ?? 16e3;
    this.language = config.language ?? "zh_cn";
    this.domain = config.domain ?? "iat";
    this.accent = config.accent ?? "mandarin";
    this.readyTimeout = config.readyTimeout ?? 5e3;
    this.stopWaitTimeout = config.stopWaitTimeout ?? 1500;
    this.debug = config.debug ?? false;
    this.handleReady = this._onReady.bind(this);
    this.handleResult = this._onResult.bind(this);
    this.handleError = this._onError.bind(this);
  }
  // ─── 公共 API ───
  /** 注册事件回调 */
  on(events) {
    this.events = { ...this.events, ...events };
    return this;
  }
  /** 当前阶段 */
  get phase() {
    return this.session?.phase ?? "idle";
  }
  /** 当前会话 ID */
  get sessionId() {
    return this.session?.id ?? null;
  }
  /** 是否正在录音（connecting / recording / stopping） */
  get isActive() {
    return this.session !== null;
  }
  /**
   * 开始录音。
   * 对应 onPressIn / 按钮按下。
   * 返回 true 表示成功启动，false 表示被忽略（去抖 / 已有活跃会话）。
   */
  start() {
    const now = Date.now();
    if (now - this.lastPressTime < 200) return false;
    this.lastPressTime = now;
    if (this.session) return false;
    this.bindTransport();
    const sid = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
    const session = {
      id: sid,
      phase: "connecting",
      ready: false,
      hasAudio: false,
      finalSent: false,
      frameStatus: 0,
      bufferedChunk: null,
      dataListener: null,
      readyTimer: null,
      stopWaitTimer: null
    };
    this.session = session;
    this.setPhase("connecting", sid);
    this.recorder.init({
      sampleRate: this.sampleRate,
      channels: 1,
      bitsPerSample: 16,
      audioSource: 1,
      bufferSize: 4096
    });
    session.dataListener = this.recorder.on("data", (chunk) => {
      this._onAudioData(chunk, sid);
    });
    this.log("start", { sid });
    this.recorder.start();
    this.transport.emit("iflytek:start", { sessionId: sid });
    session.readyTimer = setTimeout(() => {
      if (this.session?.id === sid && this.session.phase === "connecting") {
        this.log("ready timeout");
        this.transport.emit("iflytek:stop", { sessionId: sid });
        this.destroy();
        this.events.onError?.("\u8BAF\u98DE\u670D\u52A1\u672A\u5C31\u7EEA\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5", sid);
      }
    }, this.readyTimeout);
    return true;
  }
  /**
   * 停止录音。
   * 对应 onPressOut / 按钮松开。
   */
  stop() {
    const s = this.session;
    if (!s) return;
    if (s.hasAudio) {
      this.log("stop");
      this.sendFinal(s);
    } else {
      this.log("wait for first frame");
      s.stopWaitTimer = setTimeout(() => {
        if (this.session?.id === s.id && !this.session.hasAudio) {
          this.log("no audio captured");
          this.transport.emit("iflytek:stop", { sessionId: s.id });
          this.destroy();
          this.events.onError?.("\u5F55\u97F3\u592A\u77ED\uFF0C\u8BF7\u7A0D\u5FAE\u591A\u6309\u4F4F\u4E00\u70B9\u518D\u8BF4\u8BDD", s.id);
        }
      }, this.stopWaitTimeout);
    }
  }
  /** 强制终止当前会话（页面卸载 / 引擎切换时调用） */
  abort() {
    const sid = this.session?.id;
    if (sid) {
      this.transport.emit("iflytek:stop", { sessionId: sid });
    }
    this.destroy();
  }
  /** 完全释放资源，解除 transport 监听 */
  dispose() {
    this.abort();
    this.unbindTransport();
  }
  // ─── transport 事件处理 ───
  _onReady(data) {
    const s = this.session;
    if (!s || s.phase !== "connecting") return;
    if (data.sessionId && data.sessionId !== s.id) return;
    this.log("ready");
    if (s.readyTimer) {
      clearTimeout(s.readyTimer);
      s.readyTimer = null;
    }
    s.ready = true;
    this.setPhase("recording", s.id);
    const buf = s.bufferedChunk;
    if (buf) {
      this.log("flush buffered frame", { len: buf.length });
      this.transport.emit("iflytek:audio", {
        sessionId: s.id,
        status: 0,
        audio: buf,
        language: this.language,
        domain: this.domain,
        accent: this.accent
      });
      s.hasAudio = true;
      s.frameStatus = 1;
      s.bufferedChunk = null;
    }
    if (s.stopWaitTimer && s.hasAudio) {
      clearTimeout(s.stopWaitTimer);
      s.stopWaitTimer = null;
      this.sendFinal(s);
    }
  }
  _onResult(data) {
    const s = this.session;
    if (s && data.sessionId && data.sessionId !== s.id) return;
    const text = data.text?.trim() ?? "";
    const isFinal = Boolean(data.isFinal);
    this.log("result", { text: text.slice(0, 30), isFinal });
    if (isFinal) {
      const sid = s?.id ?? data.sessionId;
      this.destroy();
      if (text) {
        this.events.onFinalResult?.(text, sid);
      }
    } else if (text) {
      this.events.onInterimResult?.(text, data.sessionId);
    }
  }
  _onError(data) {
    const s = this.session;
    if (s && data.sessionId && data.sessionId !== s.id) return;
    this.log("error", { message: data.message, sessionId: data.sessionId });
    const sid = s?.id ?? data.sessionId;
    this.destroy();
    this.events.onError?.(data.message || "\u8BAF\u98DE\u8BC6\u522B\u5931\u8D25", sid);
  }
  // ─── 音频数据 ───
  _onAudioData(chunk, sid) {
    const s = this.session;
    if (!s || s.id !== sid) return;
    if (!s.ready) {
      s.bufferedChunk = chunk;
      return;
    }
    this.log("frame", { s: s.frameStatus, len: chunk.length });
    this.transport.emit("iflytek:audio", {
      sessionId: sid,
      status: s.frameStatus,
      audio: chunk,
      language: this.language,
      domain: this.domain,
      accent: this.accent
    });
    s.hasAudio = true;
    s.frameStatus = 1;
    if (s.stopWaitTimer && s.hasAudio) {
      clearTimeout(s.stopWaitTimer);
      s.stopWaitTimer = null;
      this.sendFinal(s);
    }
  }
  // ─── 内部工具 ───
  sendFinal(s) {
    if (s.finalSent) return;
    s.finalSent = true;
    this.setPhase("stopping", s.id);
    s.dataListener?.remove();
    s.dataListener = null;
    try {
      this.recorder.stop();
    } catch {
    }
    this.log("send final frame");
    this.transport.emit("iflytek:audio", { sessionId: s.id, status: 2 });
  }
  destroy() {
    const s = this.session;
    if (!s) return;
    s.dataListener?.remove();
    if (s.readyTimer) clearTimeout(s.readyTimer);
    if (s.stopWaitTimer) clearTimeout(s.stopWaitTimer);
    try {
      this.recorder.stop();
    } catch {
    }
    this.session = null;
    this.setPhase("idle", null);
  }
  setPhase(phase, sid) {
    this.events.onPhaseChange?.(phase, sid);
  }
  bindTransport() {
    if (this.bound) return;
    this.transport.on("iflytek:ready", this.handleReady);
    this.transport.on("iflytek:result", this.handleResult);
    this.transport.on("iflytek:error", this.handleError);
    this.bound = true;
  }
  unbindTransport() {
    if (!this.bound) return;
    this.transport.off("iflytek:ready", this.handleReady);
    this.transport.off("iflytek:result", this.handleResult);
    this.transport.off("iflytek:error", this.handleError);
    this.bound = false;
  }
  log(msg, extra) {
    if (!this.debug) return;
    if (extra) {
      console.log(`[sa2kit/iflytek] ${msg}`, extra);
    } else {
      console.log(`[sa2kit/iflytek] ${msg}`);
    }
  }
};

// src/iflytek/WebAudioRecorder.ts
var WebAudioRecorder = class {
  constructor() {
    this.stream = null;
    this.audioCtx = null;
    this.sourceNode = null;
    this.workletNode = null;
    this.processorNode = null;
    this.callback = null;
    this.sampleRate = 16e3;
    this.bufferSize = 4096;
    this.running = false;
  }
  init(options) {
    this.sampleRate = options.sampleRate;
    this.bufferSize = options.bufferSize;
    this.cleanup();
  }
  on(event, callback) {
    this.callback = callback;
    return {
      remove: () => {
        if (this.callback === callback) {
          this.callback = null;
        }
      }
    };
  }
  start() {
    if (this.running) return;
    this.running = true;
    this.startAsync().catch((err) => {
      console.error("[WebAudioRecorder] start failed:", err);
      this.running = false;
    });
  }
  stop() {
    this.running = false;
    this.cleanup();
  }
  async startAsync() {
    this.stream = await navigator.mediaDevices.getUserMedia({
      audio: {
        sampleRate: { ideal: this.sampleRate },
        channelCount: 1,
        echoCancellation: false,
        noiseSuppression: false,
        autoGainControl: false
      }
    });
    this.audioCtx = new AudioContext({ sampleRate: this.sampleRate });
    this.sourceNode = this.audioCtx.createMediaStreamSource(this.stream);
    const bufSize = this.closestPow2(this.bufferSize);
    this.processorNode = this.audioCtx.createScriptProcessor(bufSize, 1, 1);
    this.processorNode.onaudioprocess = (e) => {
      if (!this.running || !this.callback) return;
      const float32 = e.inputBuffer.getChannelData(0);
      const base64 = this.float32ToBase64PCM16(float32);
      this.callback(base64);
    };
    this.sourceNode.connect(this.processorNode);
    this.processorNode.connect(this.audioCtx.destination);
  }
  cleanup() {
    this.processorNode?.disconnect();
    this.processorNode = null;
    this.workletNode?.disconnect();
    this.workletNode = null;
    this.sourceNode?.disconnect();
    this.sourceNode = null;
    if (this.audioCtx && this.audioCtx.state !== "closed") {
      this.audioCtx.close().catch(() => {
      });
    }
    this.audioCtx = null;
    this.stream?.getTracks().forEach((t) => t.stop());
    this.stream = null;
  }
  float32ToBase64PCM16(float32) {
    const buf = new ArrayBuffer(float32.length * 2);
    const view = new DataView(buf);
    for (let i = 0; i < float32.length; i++) {
      const s = Math.max(-1, Math.min(1, float32[i]));
      view.setInt16(i * 2, s < 0 ? s * 32768 : s * 32767, true);
    }
    const bytes = new Uint8Array(buf);
    let binary = "";
    for (let i = 0; i < bytes.length; i++) {
      binary += String.fromCharCode(bytes[i]);
    }
    return typeof btoa === "function" ? btoa(binary) : Buffer.from(buf).toString("base64");
  }
  closestPow2(n) {
    let v = 256;
    while (v < n && v < 16384) v *= 2;
    return v;
  }
};

export { IflytekSTT, WebAudioRecorder };
//# sourceMappingURL=index.mjs.map
//# sourceMappingURL=index.mjs.map