"""
修复声音库3个问题：
1. Task #355: 试听时缓存音频，只有"重新随机音色"才重新生成
2. Task #356: 试听文案使用声音的sample_text（用户输入的试听文本）
用法：python fix_voice_preview.py
"""
import re

PORTAL = r"D:\AI-Ad-Agent\portal\portal.html"

with open(PORTAL, 'r', encoding='utf-8') as f:
    content = f.read()

# ===== 修复1：在 loadDHVoices() 中保存 sample_text 到全局变量 =====
old_load = r"""    listEl.innerHTML = voices.map(v => `
      <div style="display:flex;align-items:center;gap:12px;padding:10px 12px;border:1px solid #e2e8f0;border-radius:8px;margin-bottom:8px;background:#fff;">
        <span style="font-size:24px;">${v.type === 'clone' ? '🔬' : '🎤'}</span>
        <div style="flex:1;">
          <div style="font-weight:600;">${v.name}</div>
          <div style="font-size:11px;color:var(--text-secondary);">${v.type === 'clone' ? 'Few-shot 克隆' : '参考音频'} · ${v.created_at||''}</div>
        </div>
        <button onclick="previewVoice('${v.id}')" class="btn btn-outline btn-sm">🔊 试听</button>
        <button onclick="reseedVoice('${v.id}')" class="btn btn-outline btn-sm" title="重新随机音色" style="border-color:#c4b5fd;color:#7c3aed;">🎲</button>
        <button onclick="deleteVoice('${v.id}')" class="btn btn-outline btn-sm" style="border-color:#fca5a5;color:#ef4444;">🗑️</button>
      </div>
    `).join('');
    refreshVoiceSelects(voices);"""

new_load = r"""    // v11.8: 保存每个声音的 sample_text 到全局变量（用于试听文案）
    window._voiceSampleTexts = {};
    voices.forEach(v => {
      if (v.sample_text) window._voiceSampleTexts[v.id] = v.sample_text;
    });
    
    listEl.innerHTML = voices.map(v => `
      <div style="display:flex;align-items:center;gap:12px;padding:10px 12px;border:1px solid #e2e8f0;border-radius:8px;margin-bottom:8px;background:#fff;">
        <span style="font-size:24px;">${v.type === 'clone' ? '🔬' : '🎤'}</span>
        <div style="flex:1;">
          <div style="font-weight:600;">${v.name}</div>
          <div style="font-size:11px;color:var(--text-secondary);">${v.type === 'clone' ? 'Few-shot 克隆' : '参考音频'} · ${v.created_at||''}</div>
        </div>
        <button onclick="previewVoice('${v.id}')" class="btn btn-outline btn-sm">🔊 试听</button>
        <button onclick="reseedVoice('${v.id}')" class="btn btn-outline btn-sm" title="重新随机音色" style="border-color:#c4b5fd;color:#7c3aed;">🎲</button>
        <button onclick="deleteVoice('${v.id}')" class="btn btn-outline btn-sm" style="border-color:#fca5a5;color:#ef4444;">🗑️</button>
      </div>
    `).join('');
    refreshVoiceSelects(voices);"""

if old_load in content:
    content = content.replace(old_load, new_load)
    print("[OK] 修复1：loadDHVoices() 已更新，保存 sample_text 到全局变量")
else:
    print("[WARN] 修复1：未找到 loadDHVoices() 中的旧代码")

# ===== 修复2：修改 previewVoice() 函数 - 添加缓存 + 使用 sample_text =====
old_preview = r"""async function previewVoice(voiceId) {
  // v11.5: Backend uses stored sample_text from voice_info, overriding this default
  const sampleText = '大家好，我是您的专属数字人主播，欢迎来到直播间！';
  // v7.5: 使用 AudioContext 解码播放，支持 base64 内联音频（避免二次请求超时）
  const ctx = ensureAudioContext();
  if (ctx.state === 'suspended') {
    try { await ctx.resume(); } catch(e) {}
  }
  try {
    showToast('生成试听音频…', 'info');
    const resp = await fetch(`${DH_API}/voice/preview`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` },
      body: JSON.stringify({ voice_id: voiceId, text: sampleText }),
      signal: AbortSignal.timeout(120000)  // v7.5: 2分钟超时，够GPT-SoVITS合成
    });
    const data = await resp.json();
    
    // v8.3: 优先处理 base64 内联音频（避免二次HTTP请求超时）
    // 修复: bytes.buffer 可能比实际数据大，使用 slice(0, bytes.byteLength) 确保 decodeAudioData 正确
    if (data.audio_base64) {
      const binaryStr = atob(data.audio_base64);
      const bytes = new Uint8Array(binaryStr.length);
      for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
      showToast('正在解码播放…', 'info');
      try {
        const audioBuffer = await ctx.decodeAudioData(bytes.buffer.slice(0, bytes.byteLength));
        const source = ctx.createBufferSource();
        source.buffer = audioBuffer;
        source.connect(ctx.destination);
        source.start();
        source.onended = () => showToast('试听完成', 'info');
        showToast('正在播放…', 'info');
      } catch (decodeErr) {
        console.error('Audio decode error:', decodeErr);
        showToast('音频解码失败: ' + decodeErr.message, 'error');
      }
    }
    // 兼容旧版 audio_url 方式
    else if (data.audio_url) {
      let audioUrl = data.audio_url.startsWith('/') ? `${PORTAL_URL}${data.audio_url}` : data.audio_url;
      audioUrl += (audioUrl.includes('?') ? '&' : '?') + `token=${encodeURIComponent(authToken)}`;
      showToast('正在加载音频…', 'info');
      const audioResp = await fetch(audioUrl, { signal: AbortSignal.timeout(30000) });
      if (!audioResp.ok) throw new Error('音频加载失败: HTTP ' + audioResp.status);
      const arrayBuffer = await audioResp.arrayBuffer();
      const audioBuffer = await ctx.decodeAudioData(arrayBuffer);
      const source = ctx.createBufferSource();
      source.buffer = audioBuffer;
      source.connect(ctx.destination);
      source.start();
      source.onended = () => showToast('试听完成', 'info');
      showToast('正在播放…', 'info');
    }
    else if (data.success && data.audio_base64) {
      // 兼容 success 包装格式
      const binaryStr = atob(data.audio_base64);
      const bytes = new Uint8Array(binaryStr.length);
      for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
      try {
        const audioBuffer = await ctx.decodeAudioData(bytes.buffer.slice(0, bytes.byteLength));
        const source = ctx.createBufferSource();
        source.buffer = audioBuffer;
        source.connect(ctx.destination);
        source.start();
        source.onended = () => showToast('试听完成', 'info');
        showToast('正在播放…', 'info');
      } catch (decodeErr) {
        console.error('Audio decode error:', decodeErr);
        showToast('音频解码失败: ' + decodeErr.message, 'error');
      }
    }
    else showToast(data.error || '生成失败', 'error');
  } catch(e) { showToast('试听失败: ' + e.message, 'error'); }
}"""

new_preview = r"""// v11.8: 试听音频缓存（只有"重新随机音色"才重新生成）
window._voicePreviewCache = window._voicePreviewCache || new Map();

async function previewVoice(voiceId, forceRegenerate = false) {
  // v11.8: 使用声音信息中保存的 sample_text（用户克隆时输入的试听文本）
  const sampleText = (window._voiceSampleTexts && window._voiceSampleTexts[voiceId])
    || '大家好，我是您的专属数字人主播，欢迎来到直播间！';
  
  // v11.8: 检查缓存，如果命中且不需要重新生成，直接播放
  if (!forceRegenerate && window._voicePreviewCache.has(voiceId)) {
    const cached = window._voicePreviewCache.get(voiceId);
    const ctx = ensureAudioContext();
    if (ctx.state === 'suspended') { try { await ctx.resume(); } catch(e) {} }
    const source = ctx.createBufferSource();
    source.buffer = cached.audioBuffer;
    source.connect(ctx.destination);
    source.start();
    source.onended = () => showToast('试听完成（缓存）', 'info');
    showToast('正在播放（缓存）…', 'info');
    return;
  }
  
  // v7.5: 使用 AudioContext 解码播放，支持 base64 内联音频（避免二次请求超时）
  const ctx = ensureAudioContext();
  if (ctx.state === 'suspended') {
    try { await ctx.resume(); } catch(e) {}
  }
  try {
    showToast('生成试听音频…', 'info');
    const resp = await fetch(`${DH_API}/voice/preview`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` },
      body: JSON.stringify({ voice_id: voiceId, text: sampleText }),
      signal: AbortSignal.timeout(120000)  // v7.5: 2分钟超时，够GPT-SoVITS合成
    });
    const data = await resp.json();
    
    // v8.3: 优先处理 base64 内联音频（避免二次HTTP请求超时）
    // 修复: bytes.buffer 可能比实际数据大，使用 slice(0, bytes.byteLength) 确保 decodeAudioData 正确
    let audioBuffer = null;
    if (data.audio_base64) {
      const binaryStr = atob(data.audio_base64);
      const bytes = new Uint8Array(binaryStr.length);
      for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
      showToast('正在解码播放…', 'info');
      try {
        audioBuffer = await ctx.decodeAudioData(bytes.buffer.slice(0, bytes.byteLength));
      } catch (decodeErr) {
        console.error('Audio decode error:', decodeErr);
        showToast('音频解码失败: ' + decodeErr.message, 'error');
        return;
      }
    }
    // 兼容旧版 audio_url 方式
    else if (data.audio_url) {
      let audioUrl = data.audio_url.startsWith('/') ? `${PORTAL_URL}${data.audio_url}` : data.audio_url;
      audioUrl += (audioUrl.includes('?') ? '&' : '?') + `token=${encodeURIComponent(authToken)}`;
      showToast('正在加载音频…', 'info');
      const audioResp = await fetch(audioUrl, { signal: AbortSignal.timeout(30000) });
      if (!audioResp.ok) throw new Error('音频加载失败: HTTP ' + audioResp.status);
      const arrayBuffer = await audioResp.arrayBuffer();
      audioBuffer = await ctx.decodeAudioData(arrayBuffer);
    }
    else if (data.success && data.audio_base64) {
      // 兼容 success 包装格式
      const binaryStr = atob(data.audio_base64);
      const bytes = new Uint8Array(binaryStr.length);
      for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
      try {
        audioBuffer = await ctx.decodeAudioData(bytes.buffer.slice(0, bytes.byteLength));
      } catch (decodeErr) {
        console.error('Audio decode error:', decodeErr);
        showToast('音频解码失败: ' + decodeErr.message, 'error');
        return;
      }
    }
    else { showToast(data.error || '生成失败', 'error'); return; }
    
    // v11.8: 缓存音频数据
    window._voicePreviewCache.set(voiceId, { audioBuffer, timestamp: Date.now() });
    
    // 播放音频
    const source = ctx.createBufferSource();
    source.buffer = audioBuffer;
    source.connect(ctx.destination);
    source.start();
    source.onended = () => showToast('试听完成', 'info');
    showToast('正在播放…', 'info');
  } catch(e) { showToast('试听失败: ' + e.message, 'error'); }
}"""

if old_preview in content:
    content = content.replace(old_preview, new_preview)
    print("[OK] 修复2：previewVoice() 已更新，添加缓存 + 使用 sample_text")
else:
    print("[WARN] 修复2：未找到 previewVoice() 函数，尝试模糊匹配...")
    # 尝试查找函数定义
    if 'async function previewVoice' in content:
        print("[INFO] previewVoice() 函数存在，但内容不匹配，可能需要手动修复")
    else:
        print("[ERROR] previewVoice() 函数不存在！")

# ===== 修复3：修改 reseedVoice() 函数 - 清除缓存并重新生成 =====
old_reseed = r"""async function reseedVoice(voiceId) {
  if (!confirm('重新随机音色后，该声音的每次合成结果将发生变化。确定继续吗？')) return;
  try {
    const resp = await fetch(`${DH_API}/voice/reseed`, {"""

new_reseed = r"""async function reseedVoice(voiceId) {
  if (!confirm('重新随机音色后，该声音的每次合成结果将发生变化。确定继续吗？')) return;
  // v11.8: 清除缓存，强制重新生成
  if (window._voicePreviewCache && window._voicePreviewCache.has(voiceId)) {
    window._voicePreviewCache.delete(voiceId);
  }
  try {
    const resp = await fetch(`${DH_API}/voice/reseed`, {"""

if old_reseed in content:
    content = content.replace(old_reseed, new_reseed)
    print("[OK] 修复3：reseedVoice() 已更新，清除缓存")
else:
    print("[WARN] 修复3：未找到 reseedVoice() 函数开头，尝试模糊匹配...")
    if 'async function reseedVoice' in content:
        print("[INFO] reseedVoice() 函数存在，但内容不匹配")
    else:
        print("[ERROR] reseedVoice() 函数不存在！")

# 保存修改
with open(PORTAL, 'w', encoding='utf-8') as f:
    f.write(content)

print("\n✅ 所有修复已完成！")
print("\n修复内容：")
print("1. loadDHVoices() 保存 sample_text 到 window._voiceSampleTexts")
print("2. previewVoice() 添加音频缓存 + 使用 sample_text")
print("3. reseedVoice() 清除缓存，强制重新生成")
print("\n下一步：")
print("1. 重启数字人服务（加载 v11.8）")
print("2. 刷新浏览器（Ctrl+F5）测试")
