import { t as h } from "./defineProperty-Dt7ri80W-76aeaed7.js";
function T(e, t) {
  if (typeof e != "string" || e.length === 0) return null;
  try {
    const r = typeof t.apiEndpoint == "string" && /^https?:\/\//i.test(t.apiEndpoint) ? t.apiEndpoint : void 0, i = r ? new URL(e, r) : new URL(e);
    return i.protocol !== "http:" && i.protocol !== "https:" ? null : i;
  } catch {
    return null;
  }
}
function A(e, t) {
  const r = Array.isArray(t.assetOrigins) ? t.assetOrigins.filter((i) => typeof i == "string" && i.length > 0).map((i) => {
    try {
      return new URL(i).origin;
    } catch {
      return i;
    }
  }) : [];
  if (r.length === 0) {
    if (typeof t.apiEndpoint == "string" && /^https?:\/\//i.test(t.apiEndpoint)) try {
      return new URL(t.apiEndpoint).origin === e.origin;
    } catch {
      return !1;
    }
    return typeof window < "u" && typeof window.location?.origin == "string" && window.location.origin === e.origin;
  }
  return r.includes(e.origin);
}
function R(e) {
  const t = {};
  for (const [r, i] of Object.entries(e)) {
    const o = r.toLowerCase();
    o === "authorization" || o === "proxy-authorization" || o === "cookie" || o.startsWith("x-auth-") || (t[r] = i);
  }
  return t;
}
var I = (e) => {
  const t = (e.providerOptions && typeof e.providerOptions == "object" ? e.providerOptions : {}).__pieTelemetry;
  return typeof t == "function" ? t : void 0;
}, E = {
  pie: 3e3,
  custom: 3e3
}, P = /* @__PURE__ */ new Set(["polly", "google"]), U = (e) => {
  if (!e || b(e) !== "pie") return !1;
  const t = (e.provider || "polly").toLowerCase();
  return P.has(t);
}, S = (e) => e.replace(/\/+$/, ""), z = (e) => {
  const t = S(e.apiEndpoint), r = (e.provider || "").toLowerCase();
  return r === "polly" || r === "google" ? `${t}/${r}/voices` : `${t}/voices`;
}, b = (e) => e.transportMode === "custom" ? "custom" : e.transportMode === "pie" ? "pie" : e.provider === "custom" ? "custom" : "pie", k = (e, t) => e.endpointMode ? e.endpointMode : t === "custom" ? "rootPost" : "synthesizePath", M = (e, t) => e.endpointValidationMode ? e.endpointValidationMode : t === "custom" ? "none" : "voices", O = (e) => {
  const t = e.providerOptions || {};
  if (typeof t.speedRate == "string") return t.speedRate;
  const r = Number(e.rate ?? 1);
  return !Number.isFinite(r) || r <= 0.95 ? "slow" : r >= 1.5 ? "fast" : "medium";
}, C = (e) => {
  const t = [];
  let r = 0;
  const i = e.split(`
`).map((o) => o.trim()).filter(Boolean);
  for (const o of i) try {
    const s = JSON.parse(o), a = typeof s.type == "string" ? s.type : "word", n = typeof s.time == "number" && Number.isFinite(s.time) ? s.time : 0, l = typeof s.value == "string" ? s.value : "", d = typeof s.start == "number" && Number.isFinite(s.start) ? s.start : null, u = typeof s.end == "number" && Number.isFinite(s.end) ? s.end : null, g = d ?? r, c = u ?? g + Math.max(1, l.length || String(s.value || "").length);
    r = Math.max(c + 1, r), t.push({
      time: n,
      type: a,
      start: g,
      end: c,
      value: l
    });
  } catch {
  }
  return t;
}, L = (e) => {
  if (!Array.isArray(e)) return [];
  const t = [];
  let r = 0;
  for (const i of e) {
    if (!i || typeof i != "object") continue;
    const o = typeof i.type == "string" ? i.type : "word", s = typeof i.time == "number" && Number.isFinite(i.time) ? i.time : 0, a = typeof i.value == "string" ? i.value : "", n = typeof i.start == "number" && Number.isFinite(i.start) ? i.start : null, l = typeof i.end == "number" && Number.isFinite(i.end) ? i.end : null, d = n ?? r, u = l ?? d + Math.max(1, a.length || 1);
    r = Math.max(r, u + 1), t.push({
      time: s,
      type: o,
      start: d,
      end: u,
      value: a
    });
  }
  return t.sort((i, o) => i.time !== o.time ? i.time - o.time : i.start !== o.start ? i.start - o.start : i.end - o.end);
}, B = {
  pie: {
    id: "pie",
    resolveSynthesisUrl: (e) => {
      const t = k(e, "pie"), r = S(e.apiEndpoint);
      return t === "rootPost" ? r : `${r}/synthesize`;
    },
    buildRequestBody: (e, t) => {
      const r = t.providerOptions || {}, i = typeof t.engine == "string" ? t.engine : typeof r.engine == "string" ? r.engine : void 0, o = typeof r.sampleRate == "number" && Number.isFinite(r.sampleRate) ? r.sampleRate : void 0, s = r.format === "mp3" || r.format === "ogg" || r.format === "pcm" ? r.format : void 0, a = Array.isArray(r.speechMarkTypes) ? r.speechMarkTypes.filter((n) => n === "word" || n === "sentence" || n === "ssml") : void 0;
      return {
        text: e,
        provider: t.provider || "polly",
        voice: t.voice,
        language: t.language,
        rate: t.rate,
        engine: i,
        sampleRate: o,
        format: s,
        speechMarkTypes: a,
        includeSpeechMarks: !0
      };
    },
    parseResponse: async (e) => {
      const t = await e.json();
      return {
        audio: {
          kind: "base64",
          data: t.audio,
          contentType: t.contentType
        },
        speechMarks: Array.isArray(t.speechMarks) ? t.speechMarks : []
      };
    }
  },
  custom: {
    id: "custom",
    resolveSynthesisUrl: (e) => {
      const t = k(e, "custom"), r = S(e.apiEndpoint);
      return t === "synthesizePath" ? `${r}/synthesize` : r;
    },
    buildRequestBody: (e, t) => {
      const r = t.providerOptions || {}, i = typeof r.lang_id == "string" ? r.lang_id : t.language || "en-US", o = typeof r.cache == "boolean" ? r.cache : !0;
      return {
        text: e,
        speedRate: O(t),
        lang_id: i,
        cache: o
      };
    },
    parseResponse: async (e, t, r, i) => {
      const o = await e.json(), s = {};
      if (t.includeAuthOnAssetFetch) for (const [d, u] of Object.entries(r)) d.toLowerCase() === "authorization" && (s[d] = u);
      let a = [];
      const n = L(o.speechMarks);
      if (n.length > 0) a = n;
      else if (typeof o.word == "string" && o.word.length > 0) {
        const d = T(o.word, t);
        if (d !== null) {
          const u = A(d, t) ? s : R(s), g = await fetch(d.toString(), {
            headers: u,
            signal: i,
            redirect: "error"
          });
          g.ok && (a = C(await g.text()));
        }
      }
      const l = T(o.audioContent, t);
      if (l === null) throw new Error("TTS server returned an invalid audio URL");
      return {
        audio: {
          kind: "url",
          url: l.toString()
        },
        speechMarks: a
      };
    }
  }
}, x = class {
  constructor(e, t) {
    h(this, "config", void 0), h(this, "adapter", void 0), h(this, "currentAudio", null), h(this, "pausedState", !1), h(this, "wordTimings", []), h(this, "highlightInterval", null), h(this, "intentionallyStopped", !1), h(this, "activeSynthesisController", null), h(this, "synthesisRunId", 0), h(this, "telemetryReporter", void 0), h(this, "onWordBoundary", void 0), this.config = e, this.adapter = t, this.telemetryReporter = I(e);
  }
  async emitTelemetry(e, t) {
    try {
      await this.telemetryReporter?.(e, t);
    } catch (r) {
      console.warn("[ServerTTSProvider] telemetry callback failed:", r);
    }
  }
  async speak(e) {
    this.stop(), this.intentionallyStopped = !1;
    const t = ++this.synthesisRunId, r = new AbortController();
    this.activeSynthesisController = r;
    const { audioUrl: i, wordTimings: o } = await this.synthesizeSpeech(e, r.signal, t);
    if (t !== this.synthesisRunId) {
      URL.revokeObjectURL(i);
      return;
    }
    return this.wordTimings = o, new Promise((s, a) => {
      const n = new Audio(i);
      this.currentAudio = n, this.config.volume !== void 0 && (n.volume = Math.max(0, Math.min(1, this.config.volume))), n.onplay = () => {
        this.pausedState = !1, this.onWordBoundary && this.wordTimings.length > 0 && this.startWordHighlighting();
      }, n.onended = () => {
        this.stopWordHighlighting(), URL.revokeObjectURL(i), this.currentAudio = null, this.wordTimings = [], s();
      }, n.onerror = (l) => {
        this.stopWordHighlighting(), URL.revokeObjectURL(i), this.currentAudio = null, this.wordTimings = [], this.intentionallyStopped ? s() : a(/* @__PURE__ */ new Error("Failed to play audio from server"));
      }, n.onpause = () => {
        this.stopWordHighlighting(), this.pausedState = !0;
      }, n.play().catch(a);
    });
  }
  async synthesizeSpeech(e, t, r) {
    const i = Date.now();
    await this.emitTelemetry("pie-tool-backend-call-start", {
      toolId: "tts",
      backend: this.config.provider || "server",
      operation: "synthesize-speech"
    });
    const o = {
      "Content-Type": "application/json",
      ...this.config.headers
    };
    this.config.authToken && (o.Authorization = `Bearer ${this.config.authToken}`);
    const s = this.adapter.resolveSynthesisUrl(this.config), a = this.adapter.buildRequestBody(e, this.config), n = await (async () => {
      try {
        return await fetch(s, {
          method: "POST",
          headers: o,
          body: JSON.stringify(a),
          signal: t
        });
      } catch (c) {
        throw await this.emitTelemetry("pie-tool-backend-call-error", {
          toolId: "tts",
          backend: this.config.provider || "server",
          operation: "synthesize-speech",
          duration: Date.now() - i,
          errorType: "TTSBackendNetworkError",
          message: c instanceof Error ? c.message : String(c)
        }), c;
      }
    })();
    if (!n.ok) {
      const c = await n.text().catch(() => "");
      let p = {};
      if (c) try {
        p = JSON.parse(c);
      } catch (f) {
        console.warn("[ServerTTSProvider] non-OK response body was not JSON", {
          status: n.status,
          url: s,
          rawBodyPreview: c.slice(0, 500),
          parseError: f instanceof Error ? f.message : String(f)
        });
      }
      const y = typeof p.message == "string" && p.message || (typeof p.error?.message == "string" ? p.error.message : void 0) || `Server returned ${n.status}`;
      await this.emitTelemetry("pie-tool-backend-call-error", {
        toolId: "tts",
        backend: this.config.provider || "server",
        operation: "synthesize-speech",
        duration: Date.now() - i,
        statusCode: n.status,
        errorType: "TTSBackendRequestError",
        message: y
      });
      const m = e.replace(/\s+/g, " ").trim().slice(0, 120);
      throw console.error("[ServerTTSProvider] synthesize request failed", {
        status: n.status,
        url: s,
        backend: this.config.provider || "server",
        message: y,
        responseBody: p,
        textLength: e.length,
        textPreview: m.length < e.length ? `${m}…` : m
      }), new Error(y);
    }
    const l = await this.adapter.parseResponse(n, this.config, o, t);
    if (r !== this.synthesisRunId || t.aborted) throw new Error("Synthesis superseded by a newer request");
    let d;
    if (l.audio.kind === "base64") d = this.base64ToBlob(l.audio.data, l.audio.contentType);
    else {
      const c = l.audio.url, p = Date.now();
      await this.emitTelemetry("pie-tool-backend-call-start", {
        toolId: "tts",
        backend: this.config.provider || "server",
        operation: "fetch-synthesized-audio-asset"
      });
      const y = {};
      this.config.includeAuthOnAssetFetch && this.config.authToken && (y.Authorization = `Bearer ${this.config.authToken}`);
      const m = T(c, this.config);
      if (m === null) throw await this.emitTelemetry("pie-tool-backend-call-error", {
        toolId: "tts",
        backend: this.config.provider || "server",
        operation: "fetch-synthesized-audio-asset",
        duration: Date.now() - p,
        errorType: "TTSAssetInvalidUrl",
        message: "TTS asset URL rejected (non-http(s) or malformed)"
      }), /* @__PURE__ */ new Error("TTS asset URL rejected (non-http(s) or malformed)");
      const f = A(m, this.config) ? y : R(y), v = await (async () => {
        try {
          return await fetch(m.toString(), {
            headers: f,
            signal: t,
            redirect: "error"
          });
        } catch (w) {
          throw await this.emitTelemetry("pie-tool-backend-call-error", {
            toolId: "tts",
            backend: this.config.provider || "server",
            operation: "fetch-synthesized-audio-asset",
            duration: Date.now() - p,
            errorType: "TTSAssetNetworkError",
            message: w instanceof Error ? w.message : String(w)
          }), w;
        }
      })();
      if (!v.ok) throw await this.emitTelemetry("pie-tool-backend-call-error", {
        toolId: "tts",
        backend: this.config.provider || "server",
        operation: "fetch-synthesized-audio-asset",
        duration: Date.now() - p,
        statusCode: v.status,
        errorType: "TTSAssetFetchError",
        message: `Failed to download synthesized audio (${v.status})`
      }), /* @__PURE__ */ new Error(`Failed to download synthesized audio (${v.status})`);
      d = await v.blob(), await this.emitTelemetry("pie-tool-backend-call-success", {
        toolId: "tts",
        backend: this.config.provider || "server",
        operation: "fetch-synthesized-audio-asset",
        duration: Date.now() - p
      });
    }
    const u = URL.createObjectURL(d), g = this.parseSpeechMarks(l.speechMarks);
    return await this.emitTelemetry("pie-tool-backend-call-success", {
      toolId: "tts",
      backend: this.config.provider || "server",
      operation: "synthesize-speech",
      duration: Date.now() - i
    }), {
      audioUrl: u,
      wordTimings: g
    };
  }
  base64ToBlob(e, t) {
    const r = atob(e), i = new Array(r.length);
    for (let s = 0; s < r.length; s++) i[s] = r.charCodeAt(s);
    const o = new Uint8Array(i);
    return new Blob([o], { type: t });
  }
  parseSpeechMarks(e) {
    return e.filter((t) => t.type === "word").map((t, r) => ({
      time: t.time,
      wordIndex: r,
      charIndex: t.start,
      length: t.end - t.start,
      word: t.value
    }));
  }
  startWordHighlighting() {
    if (this.stopWordHighlighting(), !this.currentAudio || !this.onWordBoundary || this.wordTimings.length === 0) {
      console.log("[ServerTTSProvider] Cannot start highlighting:", {
        hasAudio: !!this.currentAudio,
        hasCallback: !!this.onWordBoundary,
        wordTimingsCount: this.wordTimings.length
      });
      return;
    }
    console.log("[ServerTTSProvider] Starting word highlighting with", this.wordTimings.length, "word timings"), console.log("[ServerTTSProvider] Playback rate:", this.currentAudio.playbackRate), console.log("[ServerTTSProvider] First 3 timings:", this.wordTimings.slice(0, 3));
    let e = -1;
    this.highlightInterval = window.setInterval(() => {
      if (!this.currentAudio) {
        this.stopWordHighlighting();
        return;
      }
      const t = this.currentAudio.currentTime * 1e3;
      for (let r = 0; r < this.wordTimings.length; r++) {
        const i = this.wordTimings[r];
        if (t >= i.time && r > e) {
          this.onWordBoundary && (console.log("[ServerTTSProvider] Highlighting word at charIndex:", i.charIndex, "length:", i.length, "time:", i.time, "currentTime:", t), this.onWordBoundary(i.word, i.charIndex, i.length)), e = r;
          break;
        }
      }
    }, 50);
  }
  stopWordHighlighting() {
    this.highlightInterval !== null && (clearInterval(this.highlightInterval), this.highlightInterval = null);
  }
  pause() {
    this.currentAudio && !this.pausedState && (this.currentAudio.pause(), this.stopWordHighlighting(), this.pausedState = !0);
  }
  resume() {
    this.currentAudio && this.pausedState && (this.currentAudio.play(), this.pausedState = !1, this.onWordBoundary && this.wordTimings.length > 0 && this.startWordHighlighting());
  }
  stop() {
    this.synthesisRunId += 1, this.activeSynthesisController && (this.activeSynthesisController.abort(), this.activeSynthesisController = null), this.stopWordHighlighting(), this.currentAudio && (this.intentionallyStopped = !0, this.currentAudio.pause(), this.currentAudio.src && URL.revokeObjectURL(this.currentAudio.src), this.currentAudio.src = "", this.currentAudio = null), this.pausedState = !1, this.wordTimings = [];
  }
  isPlaying() {
    return this.currentAudio !== null && !this.pausedState;
  }
  isPaused() {
    return this.pausedState;
  }
  updateSettings(e) {
    e.rate !== void 0 && (this.config.rate = e.rate), e.pitch !== void 0 && (this.config.pitch = e.pitch), e.voice !== void 0 && (this.config.voice = e.voice);
  }
}, N = class {
  constructor() {
    h(this, "providerId", "server-tts"), h(this, "providerName", "Server TTS"), h(this, "version", "1.0.0"), h(this, "config", null), h(this, "adapter", null), h(this, "telemetryReporter", void 0);
  }
  async emitTelemetry(e, t) {
    try {
      await this.telemetryReporter?.(e, t);
    } catch (r) {
      console.warn("[ServerTTSProvider] telemetry callback failed:", r);
    }
  }
  async initialize(e) {
    const t = e;
    if (!t.apiEndpoint) throw new Error("apiEndpoint is required for ServerTTSProvider");
    this.config = t, this.telemetryReporter = I(t);
    const r = b(t);
    if (this.adapter = B[r], t.validateEndpoint) {
      const i = Date.now();
      if (await this.emitTelemetry("pie-tool-backend-call-start", {
        toolId: "tts",
        backend: t.provider || "server",
        operation: "validate-endpoint"
      }), !await this.testAPIAvailability()) throw await this.emitTelemetry("pie-tool-backend-call-error", {
        toolId: "tts",
        backend: t.provider || "server",
        operation: "validate-endpoint",
        duration: Date.now() - i,
        errorType: "TTSEndpointValidationError",
        message: `Server TTS API not available at ${t.apiEndpoint}`
      }), /* @__PURE__ */ new Error(`Server TTS API not available at ${t.apiEndpoint}`);
      await this.emitTelemetry("pie-tool-backend-call-success", {
        toolId: "tts",
        backend: t.provider || "server",
        operation: "validate-endpoint",
        duration: Date.now() - i
      });
    }
    return new x(t, this.adapter);
  }
  async testAPIAvailability() {
    if (!this.config || !this.adapter) return !1;
    try {
      const e = { ...this.config.headers };
      this.config.authToken && (e.Authorization = `Bearer ${this.config.authToken}`);
      const t = new AbortController(), r = setTimeout(() => t.abort(), 5e3), i = M(this.config, this.adapter.id);
      if (i === "none") return clearTimeout(r), !0;
      const o = i === "voices" ? z(this.config) : this.adapter.resolveSynthesisUrl(this.config), s = i === "voices" ? "GET" : "OPTIONS";
      try {
        const a = await fetch(o, {
          method: s,
          headers: e,
          signal: t.signal
        });
        return clearTimeout(r), a.ok || a.status === 405;
      } catch {
        return clearTimeout(r), !1;
      }
    } catch {
      return !1;
    }
  }
  supportsFeature(e) {
    switch (e) {
      case "pause":
      case "resume":
      case "wordBoundary":
      case "voiceSelection":
      case "rateControl":
        return !0;
      case "pitchControl":
        return !1;
      default:
        return !1;
    }
  }
  getCapabilities() {
    const e = this.config ? b(this.config) : "pie";
    return {
      supportsPause: !0,
      supportsResume: !0,
      supportsWordBoundary: !0,
      supportsVoiceSelection: !0,
      supportsRateControl: !0,
      supportsPitchControl: !1,
      supportsSSML: U(this.config),
      maxTextLength: E[e]
    };
  }
  destroy() {
    this.config = null, this.adapter = null, this.telemetryReporter = void 0;
  }
};
export {
  N as ServerTTSProvider
};
