export const DEFAULT_EDGE_VOICES = {
  "zh-CN": "zh-CN-XiaoxiaoNeural",
  "en-US": "en-US-AriaNeural",
} as const;

export function rateToEdgePercent(rate: number): string {
  const bounded = Math.min(3, Math.max(0.5, Number.isFinite(rate) ? rate : 1));
  const percent = Math.round((bounded - 1) * 100);
  return `${percent >= 0 ? "+" : ""}${percent}%`;
}

export class EdgeTtsUnavailableError extends Error {
  readonly code = "edge_tts_unavailable";
  readonly fallbackAllowed = true;

  constructor(message = "Edge 在线语音暂时不可用，已切换到浏览器朗读。") {
    super(message);
    this.name = "EdgeTtsUnavailableError";
  }
}

interface EdgeModule {
  EdgeTTS: new (
    text: string,
    voice?: string,
    options?: { rate?: string; volume?: string; pitch?: string },
  ) => { synthesize(): Promise<{ audio: Blob }> };
}

export function createEdgeBrowserSynthesizer(
  load: () => Promise<EdgeModule> = () => import("edge-tts-universal/browser"),
  wait: (milliseconds: number) => Promise<void> = (milliseconds) =>
    new Promise((resolve) => globalThis.setTimeout(resolve, milliseconds)),
) {
  return async (input: { text: string; voice: string; rate: number }): Promise<Blob> => {
    let lastCause: unknown;
    for (let attempt = 0; attempt < 3; attempt += 1) {
      try {
        const { EdgeTTS } = await load();
        const result = await new EdgeTTS(input.text, input.voice, {
          rate: rateToEdgePercent(input.rate),
          volume: "+0%",
          pitch: "+0Hz",
        }).synthesize();
        if (!result.audio.size) throw new Error("没有收到音频");
        return result.audio.type === "audio/mpeg"
          ? result.audio
          : new Blob([await result.audio.arrayBuffer()], { type: "audio/mpeg" });
      } catch (cause) {
        lastCause = cause;
        if (attempt < 2) await wait(attempt === 0 ? 250 : 750);
      }
    }
    const detail = lastCause instanceof Error ? lastCause.message : "未知错误";
    throw new EdgeTtsUnavailableError(`Edge 在线语音暂时不可用（${detail}）。`);
  };
}

export const synthesizeWithEdge = createEdgeBrowserSynthesizer();
