/**
 * CapCut 轨道操作
 *
 * 所有 add* 函数 + 转场策略 + 账号配置读取。
 * Agent 修改字幕风格、Ken Burns、转场、特效等只需关注此文件。
 *
 * 音频策略（固化铁律）：
 *   - 音频由 TTS 1.15x 生成，导入 CapCut 时无 speed 字段（不可调速）
 *   - 每个 item 的 segments[] 逐段添加，各段 start 按 startOffset 精确对齐
 */

const path = require('path')
const fs = require('fs')
const { api, US, getConfig } = require('./capcut-api')
const { splitTextIntoSentences, loadAccountConfig: loadAccountConfigFromUtils, getManifestDir } = require('./pipeline-utils')

// ============================================================================
// 账号配置读取
// ============================================================================

function loadAccountConfig(manifest) {
  const account = manifest.account
  if (!account) return {}
  try { return loadAccountConfigFromUtils(account) } catch { return {} }
}

function loadSubtitleStyle(manifest) {
  return loadAccountConfig(manifest).capcut?.subtitleStyle || {}
}

function loadKeywordStyle(manifest) {
  return loadAccountConfig(manifest).capcut?.keywordStyle || {}
}

function loadTransitions(manifest) {
  return loadAccountConfig(manifest).capcut?.transitions || null
}

function applyAnimationProps(cap, style = {}) {
  if (style.inAnimation) cap.in_animation = style.inAnimation
  if (style.outAnimation) cap.out_animation = style.outAnimation
  if (style.inAnimDuration) cap.in_animation_duration = style.inAnimDuration
  if (style.outAnimDuration) cap.out_animation_duration = style.outAnimDuration
}

// ============================================================================
// 转场策略
// ============================================================================

function getTransition(item, index, totalCount, transitionConfig, allItems, timeline) {
  if (!transitionConfig) return { name: '', duration: 0 }

  const defaultT = transitionConfig.default || { name: '闪白', duration: 150000 }
  const strategy = transitionConfig.strategy || 'fixed'

  if (index === 0) return { name: '', duration: 0 }
  if (index >= totalCount - 1) return { name: '', duration: 0 }

  switch (strategy) {
    case 'director': {
      const ref = (item.directorRef || '').toLowerCase()
      const byDirector = transitionConfig.byDirector || {}
      return byDirector[ref] || defaultT
    }

    case 'rhythm': {
      const rules = transitionConfig.byPosition || {}
      if (index >= totalCount - 2) return rules.closing || defaultT
      if (allItems && index > 0) {
        const prev = allItems[index - 1]
        if (item.directorRef && prev.directorRef && item.directorRef !== prev.directorRef) {
          return rules.keypoint || defaultT
        }
        if (item.keyword && !prev.keyword) {
          return rules.keypoint || defaultT
        }
      }
      if (timeline && timeline.length > 0) {
        const elapsed = timeline[index].start
        let lastTransStart = 0
        for (let pi = index - 1; pi >= 1; pi--) {
          const p = allItems[pi]
          const pp = pi > 0 ? allItems[pi - 1] : null
          if (pi >= totalCount - 2) { lastTransStart = timeline[pi].start; break }
          if (pp && p.directorRef && pp.directorRef && p.directorRef !== pp.directorRef) {
            lastTransStart = timeline[pi].start; break
          }
          if (p.keyword && pp && !pp.keyword) { lastTransStart = timeline[pi].start; break }
        }
        if (elapsed - lastTransStart >= 8000000) return rules.body || defaultT
      } else {
        if (index % 3 === 0) return rules.body || defaultT
      }
      return { name: '', duration: 0 }
    }

    case 'fixed':
    default:
      return defaultT
  }
}

// ============================================================================
// 入场动画映射
// ============================================================================

const DIRECTOR_ANIMATIONS = {
  tarantino: '动感放大',
  kitano: '轻微放大',
  fincher: '渐显',
}

function getAnimationForDirector(directorRef, defaultAnimation) {
  if (!directorRef) return defaultAnimation
  return DIRECTOR_ANIMATIONS[directorRef.toLowerCase()] || defaultAnimation
}

// ============================================================================
// Ken Burns 关键帧动画
// ============================================================================

const KEN_BURNS_FALLBACK = {
  default: { startScale: 1.0, scaleRate: 0.8, panXRate: 0, panYRate: 0 },
  byDirector: {
    tarantino: { startScale: 1.0, scaleRate: 0.9, panXRate: 0, panYRate: -0.3 },
    kitano:    { startScale: 1.03, scaleRate: 0.5, panXRate: 0.4, panYRate: 0 },
    fincher:   { startScale: 1.0, scaleRate: 0.4, panXRate: 0, panYRate: 0 },
  },
}
const KEN_BURNS_MAX_SCALE = 1.20

function loadKenBurns(manifest) {
  const cfg = loadAccountConfig(manifest).capcut?.kenBurns
  if (cfg && cfg.default) return cfg
  return KEN_BURNS_FALLBACK
}

function getKenBurnsProfile(item, kbConfig) {
  const director = (item.directorRef || '').toLowerCase()
  const byDirector = kbConfig.byDirector || {}
  return byDirector[director] || kbConfig.default
}

async function addKenBurns(draftUrl, segmentIds, items, timeline, manifest) {
  if (!segmentIds || segmentIds.length === 0) {
    console.log('   无 segment IDs，跳过 Ken Burns')
    return
  }

  const kbConfig = loadKenBurns(manifest)
  if (kbConfig.enabled === false) {
    console.log('   Ken Burns 已禁用（account.json kenBurns.enabled=false）')
    return
  }

  const keyframes = []
  for (let i = 0; i < segmentIds.length; i++) {
    const segId = segmentIds[i]
    const item = items[i]
    const tl = timeline[i]
    if (!segId || !tl) continue

    const profile = getKenBurnsProfile(item, kbConfig)
    const durSec = tl.duration / US
    const segDur = tl.duration

    const startScale = profile.startScale || 1.0
    const scaleRate = profile.scaleRate != null ? profile.scaleRate : 0.8
    const endScale = Math.min(startScale + scaleRate * durSec / 100, KEN_BURNS_MAX_SCALE)

    keyframes.push(
      { segment_id: segId, property: 'UNIFORM_SCALE', offset: 0, value: startScale },
      { segment_id: segId, property: 'UNIFORM_SCALE', offset: segDur, value: endScale },
    )

    const panXRate = profile.panXRate || 0
    const panYRate = profile.panYRate || 0
    if (panXRate !== 0) {
      const panX = panXRate * durSec
      keyframes.push(
        { segment_id: segId, property: 'KFTypePositionX', offset: 0, value: 0 },
        { segment_id: segId, property: 'KFTypePositionX', offset: segDur, value: panX },
      )
    }
    if (panYRate !== 0) {
      const panY = panYRate * durSec
      keyframes.push(
        { segment_id: segId, property: 'KFTypePositionY', offset: 0, value: 0 },
        { segment_id: segId, property: 'KFTypePositionY', offset: segDur, value: panY },
      )
    }
  }

  if (keyframes.length === 0) {
    console.log('   无关键帧生成，跳过')
    return
  }

  const res = await api('add_keyframes', {
    draft_url: draftUrl,
    keyframes: JSON.stringify(keyframes),
  })
  console.log(`   已添加 ${res.keyframes_added || keyframes.length} 个 Ken Burns 关键帧 (${segmentIds.length} 段)`)
}

// ============================================================================
// 添加图片
// ============================================================================

async function addImages(draftUrl, items, imgUrls, timeline, width, height, animation = '', transitionConfig = null) {
  const imageInfos = items.map((item, i) => {
    const url = imgUrls[item.file]
    if (!url) throw new Error(`图片 ${item.file} 未上传成功，无法添加`)
    const tl = timeline[i]
    const t = getTransition(item, i, items.length, transitionConfig, items, timeline)
    const info = {
      image_url: url,
      width,
      height,
      start: tl.start,
      end: tl.end,
      duration: tl.duration,
      transition: t.name,
      transition_duration: t.duration,
    }

    const itemAnimation = getAnimationForDirector(item.directorRef, animation)
    if (itemAnimation) {
      const parts = itemAnimation.split('+').map(p => p.trim()).filter(Boolean)
      const loopNames = ['缩放', '缩放 II', '回弹伸缩', '旋转伸缩']
      const loopAnims = parts.filter(p => loopNames.includes(p))
      const inAnims = parts.filter(p => !loopNames.includes(p))
      if (loopAnims.length > 0) info.loop_animation = loopAnims.join('|')
      if (inAnims.length > 0) info.in_animation = inAnims.join('|')
    }

    return info
  })

  console.log(`   一次性添加 ${imageInfos.length} 张图片...`)
  const res = await api('add_images', {
    draft_url: draftUrl,
    image_infos: JSON.stringify(imageInfos),
    alpha: 1, scale_x: 1, scale_y: 1,
    transform_x: 0, transform_y: 0,
  }, 300000)
  const allSegmentIds = res.segment_ids || []

  console.log(`   已添加 ${items.length} 张图片`)
  return allSegmentIds
}

// ============================================================================
// 添加视频
// ============================================================================

async function addVideos(draftUrl, inputDir, items, timeline, width, height, transitionConfig = null) {
  const videoInfos = items.map((item, i) => {
    const tl = timeline[i]
    const t = getTransition(item, i, items.length, transitionConfig, items, timeline)
    return {
      video_url: item.videoUrl || (item.video ? path.resolve(inputDir, item.video) : null) || item.url || path.resolve(inputDir, item.file),
      width,
      height,
      start: tl.start,
      end: tl.end,
      duration: tl.duration,
      mask: '',
      transition: t.name,
      transition_duration: t.duration,
      volume: item.volume || 1,
    }
  })

  try {
    const res = await api('add_videos', {
      draft_url: draftUrl,
      video_infos: JSON.stringify(videoInfos),
      alpha: 1, scale_x: 1, scale_y: 1,
      transform_x: 0, transform_y: 0,
      scene_timelines: [],
    })
    console.log(`   已添加 ${items.length} 个视频片段（全量）`)
    return res.segment_ids || []
  } catch (err) {
    if (!err.message.includes('504') && !err.message.includes('timeout')) throw err
    console.log(`   全量提交超时，降级为分批添加...`)
  }

  const BATCH_SIZE = 3
  const allSegmentIds = []
  for (let i = 0; i < videoInfos.length; i += BATCH_SIZE) {
    const batch = videoInfos.slice(i, i + BATCH_SIZE)
    const batchNum = Math.floor(i / BATCH_SIZE) + 1
    const totalBatches = Math.ceil(videoInfos.length / BATCH_SIZE)
    console.log(`   分批 [${batchNum}/${totalBatches}] 添加 ${batch.length} 个片段...`)
    const res = await api('add_videos', {
      draft_url: draftUrl,
      video_infos: JSON.stringify(batch),
      alpha: 1, scale_x: 1, scale_y: 1,
      transform_x: 0, transform_y: 0,
      scene_timelines: [],
    })
    if (res.segment_ids) allSegmentIds.push(...res.segment_ids)
  }

  console.log(`   已添加 ${items.length} 个视频片段（分批）`)
  return allSegmentIds
}

// ============================================================================
// 将 segment 写入视频轨道时间线（slot）
// 背景：add_videos 只负责把视频加入素材库，不自动上时间线。
//       此函数在 add_videos 成功后调用，将每个 segment_id 写入第一个 video track。
// ============================================================================

async function addSlots(draftUrl, items, timeline) {
  // 获取当前云端草稿的 draft_content，获取第一个 video track 的 id
  let draftData
  try {
    draftData = (await api('get_draft', { draft_url: draftUrl })).data || {}
  } catch (err) {
    console.log('   get_draft 不可用，切换本地写入模式')
    return addSlotsLocally(draftUrl, items, timeline)
  }

  const tracks = draftData.tracks || []
  const videoTrack = tracks.find(t => t.type === 'video')
  if (!videoTrack) {
    console.log('   未找到 video track，跳过 slot 写入')
    return
  }

  // 构造 slot 数据（复用 buildSlot）
  const slots = []
  for (let i = 0; i < items.length; i++) {
    const segId = items[i].segmentId || items[i]._segmentId
    if (!segId) continue
    slots.push(buildSlot(segId, videoTrack.id, i, timeline[i]))
  }

  if (slots.length === 0) {
    console.log('   无有效 slot 数据，跳过')
    return
  }

  // 通过 add_slots API 写入
  try {
    await api('add_slots', {
      draft_url: draftUrl,
      slots: JSON.stringify(slots),
    })
    console.log(`   已写入 ${slots.length} 个 slot 到视频轨道`)
  } catch (err) {
    console.log(`   add_slots API 不可用: ${err.message}，降级为本地写入`)
    await addSlotsLocally(draftUrl, items, timeline, videoTrack.id)
  }
}

// 直接写入本地 draft_content.json 的 slot
// options.draftId: 可选，直接指定 draftId（优先使用），否则从 draftUrl 提取
async function addSlotsLocally(draftUrl, items, timeline, trackId, options = {}) {
  // 优先使用 options.draftId，否则从 draftUrl 提取
  let draftId = options.draftId || null
  if (!draftId) {
    try {
      draftId = new URL(draftUrl).searchParams.get('draft_id')
    } catch {
      console.log('   无法解析 draftUrl，跳过本地 slot 写入')
      return
    }
  }

  if (!draftId) {
    console.log('   无法提取 draft_id，跳过本地 slot 写入')
    return
  }

  const jianyingPath = getConfig().jianyingDraftPath
  const draftPath = path.join(jianyingPath, draftId, 'draft_content.json')
  if (!fs.existsSync(draftPath)) {
    console.log(`   本地草稿不存在: ${draftPath}，跳过 slot 写入`)
    return
  }

  let draft
  try {
    draft = JSON.parse(fs.readFileSync(draftPath, 'utf-8'))
  } catch {
    console.log('   draft_content.json 读取失败，跳过')
    return
  }

  // 找到第一个 video track
  const videoTrack = trackId
    ? draft.tracks.find(t => t.id === trackId)
    : draft.tracks.find(t => t.type === 'video')

  if (!videoTrack) {
    console.log('   未找到 video track，跳过')
    return
  }

  const slots = []
  for (let i = 0; i < items.length; i++) {
    const item = items[i]
    const tl = timeline[i]
    const segId = item.segmentId || item._segmentId
    if (!segId) {
      // 尝试从 materials.videos 匹配
      const fname = item.video ? path.basename(item.video) : ''
      const matVideo = (draft.materials.videos || []).find(v => {
        const matFname = path.basename(v.path || '')
        return fname && matFname.includes(fname.replace('videos/', ''))
      })
      if (matVideo) {
        items[i]._segmentId = matVideo.id
        slots.push(buildSlot(matVideo.id, videoTrack.id, i, tl))
      }
    } else {
      slots.push(buildSlot(segId, videoTrack.id, i, tl))
    }
  }

  if (slots.length > 0) {
    videoTrack.slots = slots
    draft.duration = timeline.length > 0 ? timeline[timeline.length - 1].end : 0
    fs.writeFileSync(draftPath, JSON.stringify(draft, null, 2), 'utf-8')
    console.log(`   已本地写入 ${slots.length} 个 slot 到视频轨道`)

    // 触发剪映扫描
    triggerDirScan(path.dirname(draftPath))
  }
}

function buildSlot(segId, trackId, index, tl) {
  return {
    id: generateUUID(),
    material_id: segId,
    track_id: trackId,
    render_index: index,
    type: 'video',
    common_property: {
      start_time: tl.start,
      source_timerange: { start: 0, duration: tl.duration },
      target_timerange: { start: tl.start, duration: tl.duration },
      is_avatar: false,
      audio_fade: { fade_in_duration: 0, fade_out_duration: 0 },
      volume: 1.0,
    },
  }
}

function generateUUID() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
    const r = Math.random() * 16 | 0
    return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16).toUpperCase()
  })
}

// ============================================================================
// 本地轨道合并 — 将同一类型的多个轨道合并为一个
// 背景：远端 add_audios / add_captions 每次调用都会创建新轨道。
//       批量添加失败降级为逐条调用时，会导致每条 segment 独占一个轨道。
//       此函数在 sync 后读取本地 draft_content.json，合并同类型轨道。
// ============================================================================

function consolidateTracks(draftId) {
  const jianyingPath = getConfig().jianyingDraftPath
  const draftPath = path.join(jianyingPath, draftId, 'draft_content.json')
  if (!fs.existsSync(draftPath)) {
    console.log('   本地草稿不存在，跳过轨道合并')
    return
  }

  let draft
  try {
    draft = JSON.parse(fs.readFileSync(draftPath, 'utf-8'))
  } catch {
    console.log('   draft_content.json 读取失败，跳过轨道合并')
    return
  }

  const tracks = draft.tracks || []
  for (const trackType of ['audio', 'text']) {
    const sameTypeTracks = tracks.filter(t => t.type === trackType && t.segments && t.segments.length > 0)
    if (sameTypeTracks.length <= 1) continue

    // 以第一个轨道为主，将其他轨道的 segments 合并进来
    const primary = sameTypeTracks[0]
    for (let i = 1; i < sameTypeTracks.length; i++) {
      primary.segments.push(...sameTypeTracks[i].segments)
      sameTypeTracks[i].segments = []
    }

    // 去重：同 start 只保留第一个（批量失败→逐条降级时首段会重复）
    const seen = new Set()
    const deduped = []
    for (const seg of primary.segments) {
      const key = seg.target_timerange?.start ?? seg.start_time ?? null
      if (key == null || seen.has(key)) continue
      seen.add(key)
      deduped.push(seg)
    }
    const dupCount = primary.segments.length - deduped.length
    primary.segments = deduped

    // 按 start 排序
    primary.segments.sort((a, b) => {
      const aStart = a.target_timerange?.start ?? a.start_time ?? 0
      const bStart = b.target_timerange?.start ?? b.start_time ?? 0
      return aStart - bStart
    })

    console.log(`   已合并 ${sameTypeTracks.length} 条 ${trackType} 轨道 → 1 条 (${primary.segments.length} 段)${dupCount > 0 ? ` 去重 ${dupCount} 段` : ''}`)
  }

  // 移除空轨道（segments 为空的非 video 轨道）
  draft.tracks = tracks.filter(t => {
    if (t.type === 'video') return true
    if (t.type === 'effect' || t.type === 'filter') return true
    return t.segments && t.segments.length > 0
  })

  fs.writeFileSync(draftPath, JSON.stringify(draft, null, 2), 'utf-8')
  triggerDirScan(path.dirname(draftPath))
}

const { execFile } = require('child_process')

function triggerDirScan(dir) {
  const tmp = dir + '.slot_tmp'
  if (process.platform === 'darwin') {
    execFile('rsync', ['-a', dir + '/', tmp], (err) => {
      try { fs.rmSync(tmp, { recursive: true, force: true }) } catch {}
    })
  }
}

// ============================================================================
// 添加 TTS 配音
// ============================================================================

async function addVoiceover(draftUrl, inputDir, items, timeline, audioUrls = {}) {
  // 优先使用 segments[] 逐段添加（精确对齐）
  // 无 segments 时降级为旧的整段方式
  const segmentsFlat = []

  for (let i = 0; i < items.length; i++) {
    const item = items[i]
    const tl = timeline[i]
    if (!item.audio) continue

    if (item.segments && item.segments.length > 0) {
      // 使用 segments 精确添加，end 裁剪到 slot 边界防止跨场景重叠
      for (const seg of item.segments) {
        if (!seg.audio || seg.error) continue
        const audioUrl = seg.audio.startsWith('http')
          ? seg.audio
          : (audioUrls[seg.audio] || path.resolve(inputDir, seg.audio))
        const segStartUs = tl.start + Math.round(seg.startOffset * US)
        const segEndUs = Math.min(segStartUs + Math.round(seg.duration * US), tl.end)
        const actualDurUs = segEndUs - segStartUs
        segmentsFlat.push({
          audio_url: audioUrl,
          start: segStartUs,
          end: segEndUs,
          duration: actualDurUs,
          volume: 1.0,
        })
      }
    } else {
      // 降级：整段添加，end 裁剪到 slot 边界
      const audioUrl = item.audio.startsWith('http')
        ? item.audio
        : (audioUrls[item.audio] || path.resolve(inputDir, item.audio))
      const audioDurUs = item.audioDuration ? item.audioDuration * US : tl.duration
      const endUs = Math.min(tl.start + audioDurUs, tl.end)
      const actualDurUs = endUs - tl.start
      segmentsFlat.push({
        audio_url: audioUrl,
        start: tl.start,
        end: endUs,
        duration: actualDurUs,
        volume: 1.0,
      })
    }
  }

  if (segmentsFlat.length === 0) {
    console.log('   无 TTS 音频文件，跳过')
    return
  }

  // 批量添加音频（同一轨道），失败时逐个兜底
  let addedCount = 0
  try {
    await api('add_audios', {
      draft_url: draftUrl,
      audio_infos: JSON.stringify(segmentsFlat),
    })
    addedCount = segmentsFlat.length
  } catch (err) {
    console.log(`   批量添加音频失败 (${err.message.slice(0, 60)})，逐个添加...`)
    for (const audioInfo of segmentsFlat) {
      try {
        await api('add_audios', {
          draft_url: draftUrl,
          audio_infos: JSON.stringify([audioInfo]),
        })
        addedCount++
      } catch (e2) {
        console.error(`   音频添加失败: ${e2.message.slice(0, 80)}`)
      }
    }
  }
  const ossCount = segmentsFlat.filter(a => a.audio_url.startsWith('http')).length
  console.log(`   已添加 ${addedCount}/${segmentsFlat.length} 段 TTS 配音 (${ossCount} 段 OSS)`)
}

// ============================================================================
// 添加 BGM
// ============================================================================

async function addBGM(draftUrl, bgmUrl, totalDurationUs) {
  let audioDuration = totalDurationUs
  try {
    const durRes = await api('get_audio_duration', { mp3_url: bgmUrl })
    if (durRes.duration) audioDuration = durRes.duration
  } catch (_) {}

  const fadeIn = 500000
  const fadeOut = 1000000

  await api('add_audios', {
    draft_url: draftUrl,
    audio_infos: JSON.stringify([{
      audio_url: bgmUrl,
      duration: audioDuration,
      end: Math.min(audioDuration, totalDurationUs),
      start: 0,
      volume: 0.15,
      fade_in_duration: fadeIn,
      fade_out_duration: fadeOut,
    }]),
  })
  console.log(`   已添加 BGM (${(audioDuration / US).toFixed(1)}s, fade 0.5s/1s)`)
}

// ============================================================================
// 添加字幕
// ============================================================================

async function addSubtitles(draftUrl, items, timeline, style = {}, split = false) {
  const captions = []

  const animStyle = {
    inAnimation: style.inAnimation || '',
    outAnimation: style.outAnimation || '',
    inAnimDuration: style.inAnimationDuration || null,
    outAnimDuration: style.outAnimationDuration || null,
  }

  for (let i = 0; i < items.length; i++) {
    const item = items[i]
    const text = item.script || item.text || item.caption || ''
    if (!text) continue

    const tl = timeline[i]

    if (split && item.segments && item.segments.length > 0) {
      // 精确字幕模式，end 裁剪到 slot 边界防止跨场景重叠
      for (const seg of item.segments) {
        if (seg.error || !seg.text) continue
        const segStartUs = tl.start + Math.round(seg.startOffset * US)
        const segEndUs = Math.min(segStartUs + Math.round(seg.duration * US), tl.end)

        const cap = {
          start: segStartUs,
          end: segEndUs,
          text: seg.text,
        }

        applyAnimationProps(cap, animStyle)
        captions.push(cap)
      }
    } else if (split) {
      // 降级：按字符比例分配（无 segments 时）
      const sentences = splitTextIntoSentences(text)
      if (sentences.length === 0) continue

      const totalDuration = tl.end - tl.start
      const totalChars = sentences.reduce((sum, s) => sum + s.length, 0)
      let currentTime = tl.start

      sentences.forEach((sentence, idx) => {
        const charRatio = sentence.length / totalChars
        let duration = Math.round(totalDuration * charRatio)

        if (idx === sentences.length - 1) {
          duration = tl.end - currentTime
        }

        duration = Math.max(duration, 500000)

        const cap = {
          start: currentTime,
          end: currentTime + duration,
          text: sentence,
        }

        applyAnimationProps(cap, animStyle)
        captions.push(cap)
        currentTime += duration
      })
    } else {
      const cap = {
        start: tl.start,
        end: tl.end,
        text,
      }

      applyAnimationProps(cap, animStyle)
      captions.push(cap)
    }
  }

  if (captions.length === 0) {
    console.log('   无字幕内容，跳过')
    return
  }

  const commonStyle = {
    font: style.font || null,
    font_size: style.fontSize || 15,
    text_color: style.color || '#ffffff',
    alignment: 1,
    bold: style.bold || false,
    italic: false,
    underline: false,
    has_shadow: style.hasShadow || false,
    shadow_info: style.shadowAlpha ? {
      shadow_alpha: style.shadowAlpha,
      shadow_color: style.shadowColor || '#000000',
      shadow_diffuse: 15,
      shadow_distance: 5,
      shadow_angle: -45,
    } : undefined,
    letter_spacing: style.letterSpacing || 0,
    line_spacing: style.lineSpacing || 0,
    alpha: style.alpha || 1,
    scale_x: 1, scale_y: 1,
    transform_x: 0,
    transform_y: style.transformY || 0,
    style_text: 0,
  }

  // 批量添加字幕（同一轨道），失败时逐条兜底
  let addedCount = 0
  try {
    await api('add_captions', {
      draft_url: draftUrl,
      captions: JSON.stringify(captions),
      ...commonStyle,
    })
    addedCount = captions.length
  } catch (err) {
    console.log(`   批量添加字幕失败 (${err.message.slice(0, 60)})，逐条添加...`)
    for (const cap of captions) {
      try {
        await api('add_captions', {
          draft_url: draftUrl,
          captions: JSON.stringify([cap]),
          ...commonStyle,
        })
        addedCount++
      } catch (e2) {
        console.error(`   字幕添加失败: ${e2.message.slice(0, 80)}`)
      }
    }
  }
  console.log(`   已添加 ${addedCount}/${captions.length} 条字幕${split ? ' (分句模式)' : ''} (字体: ${style.font || '默认'}, 动画: ${animStyle.inAnimation || '无'} → ${animStyle.outAnimation || '无'})`)
}

// ============================================================================
// 添加关键字氛围词
// ============================================================================

async function addKeywordOverlays(draftUrl, items, timeline, style = {}) {
  const keywordItems = items.filter(item => item.keyword)
  if (keywordItems.length === 0) {
    console.log('   无关键字，跳过')
    return
  }

  const captions = []
  for (let i = 0; i < items.length; i++) {
    const item = items[i]
    if (!item.keyword) continue
    const tl = timeline[i]

    const cap = {
      start: tl.start,
      end: tl.end,
      text: item.keyword,
    }
    applyAnimationProps(cap, style)

    captions.push(cap)
  }

  if (captions.length === 0) return

  await api('add_captions', {
    draft_url: draftUrl,
    captions: JSON.stringify(captions),
    font: style.font || null,
    font_size: style.fontSize || 60,
    text_color: style.color || '#FFFFFF',
    alignment: 1,
    bold: style.bold || false,
    has_shadow: style.hasShadow || false,
    shadow_info: style.shadowAlpha ? {
      shadow_alpha: style.shadowAlpha,
      shadow_color: style.shadowColor || '#000000',
      shadow_diffuse: 15,
      shadow_distance: 5,
      shadow_angle: -45,
    } : undefined,
    alpha: style.alpha || 1,
    scale_x: 1, scale_y: 1,
    transform_x: 0,
    transform_y: style.transformY || 0,
    text_effect: style.textEffect || null,
  })
  console.log(`   已添加 ${captions.length} 个关键字氛围词 (效果: ${style.textEffect || '无'})`)
}

// ============================================================================
// 添加特效
// ============================================================================

async function addEffects(draftUrl, effectsStr, totalDurationUs) {
  const effectNames = effectsStr.split(',').map(s => s.trim()).filter(Boolean)
  const effectInfos = effectNames.map(name => ({
    effect_title: name,
    start: 0,
    end: totalDurationUs,
  }))

  await api('add_effects', {
    draft_url: draftUrl,
    effect_infos: JSON.stringify(effectInfos),
  })

  console.log(`   已添加: ${effectNames.join(', ')}`)
}

// ============================================================================
// 添加滤镜
// ============================================================================

async function addFilter(draftUrl, filterStr, totalDurationUs) {
  const [name, intensity] = filterStr.split(':')
  await api('add_filters', {
    draft_url: draftUrl,
    filter_infos: JSON.stringify([{
      filter_title: (name || '').trim(),
      start: 0,
      end: totalDurationUs,
      intensity: parseFloat(intensity) || 50,
    }]),
  })
  console.log(`   已添加: ${(name || '').trim()} 强度 ${intensity || 50}`)
}

module.exports = {
  loadAccountConfig,
  loadSubtitleStyle,
  loadKeywordStyle,
  loadKenBurns,
  loadTransitions,
  getTransition,
  addImages,
  addVideos,
  addKenBurns,
  addVoiceover,
  addBGM,
  addSubtitles,
  addKeywordOverlays,
  addSlots,
  addSlotsLocally,
  consolidateTracks,
  addEffects,
  addFilter,
}
