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