#!/usr/bin/env node

/**
 * 同步 CapCut Mate 草稿到本地剪映（独立版）
 *
 * 从 API 获取草稿文件列表 → 下载到本地剪映目录 → 路径重写 → 远程素材本地化 → 注册 + 触发扫描
 * 不依赖 Electron、不依赖 capcut-mate Python 环境。
 *
 * 用法:
 *   node sync-to-jianying.js <draft_url> [--name "草稿名称"]
 *
 * draft_url 格式: http://xxx/openapi/capcut-mate/v1/get_draft?draft_id=xxx
 */

const axios = require('axios')
const path = require('path')
const fs = require('fs')
const { createWriteStream } = require('fs')
const fsp = fs.promises
const { execFile } = require('child_process')

// ============================================================================
// 配置
// ============================================================================

function getConfig() {
  const configPath = path.join(__dirname, '..', '..', 'config.json')
  return JSON.parse(fs.readFileSync(configPath, 'utf-8'))
}

// ============================================================================
// 工具函数
// ============================================================================

function isHttpUrl(value) {
  if (!value || typeof value !== 'string') return false
  try {
    const parsed = new URL(value)
    return parsed.protocol === 'http:' || parsed.protocol === 'https:'
  } catch { return false }
}

function extractDraftId(url) {
  const match = url.match(/draft_id=([^&]+)/)
  return match ? match[1] : null
}

function winPath(p) {
  return p.replace(/\//g, '\\')
}

function getFileExtFromUrl(url, fallback = '.bin') {
  try { return path.extname(new URL(url).pathname) || fallback }
  catch { return fallback }
}

// ============================================================================
// 下载
// ============================================================================

async function downloadStream(url, filePath) {
  await fsp.mkdir(path.dirname(filePath), { recursive: true })
  const res = await axios.get(url, { responseType: 'stream', timeout: 60000 })
  if (res.status !== 200) throw new Error(`HTTP ${res.status}: ${url}`)
  return new Promise((resolve, reject) => {
    const writer = res.data.pipe(createWriteStream(filePath, { flags: 'w' }))
    writer.on('close', resolve)
    writer.on('error', reject)
    res.data.on('error', reject)
  })
}

// ============================================================================
// 路径重写（核心逻辑来自 desktop-client/download.js）
// ============================================================================

function updatePathValue(obj, key, targetDir, draftId) {
  const oldVal = obj[key]
  if (!oldVal || typeof oldVal !== 'string') return
  const idIndex = oldVal.indexOf(draftId)
  if (idIndex === -1) return
  const relativePath = oldVal.substring(idIndex).replace(/\//g, path.sep)
  const cleaned = relativePath.replace(draftId + path.sep, '')
  obj[key] = path.join(targetDir, cleaned)
}

function recursivelyUpdatePaths(obj, targetDir, draftId) {
  if (Array.isArray(obj)) { obj.forEach(item => recursivelyUpdatePaths(item, targetDir, draftId)); return }
  if (obj && typeof obj === 'object') {
    if (obj.path && typeof obj.path === 'string') updatePathValue(obj, 'path', targetDir, draftId)
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) recursivelyUpdatePaths(obj[key], targetDir, draftId)
    }
  }
}

// ============================================================================
// 远程素材本地化（下载 http/https URL 素材到本地）
// ============================================================================

async function localizeRemoteMaterials(materials, draftDir) {
  if (!materials || typeof materials !== 'object') return
  const supportedTypes = ['videos', 'audios']
  const cache = new Map()

  // 收集所有需要下载的素材
  const downloadTasks = []
  for (const matType of supportedTypes) {
    const list = materials[matType]
    if (!Array.isArray(list)) continue
    for (const item of list) {
      if (!item || typeof item !== 'object') continue
      if (!isHttpUrl(item.path)) continue

      const subDir = matType === 'videos'
        ? (item.type === 'photo' ? 'images' : 'videos')
        : matType === 'audios' ? 'audios' : 'misc'
      const ext = getFileExtFromUrl(item.path, matType === 'audios' ? '.mp3' : '.mp4')
      const baseName = (item.material_name || item.name || item.id || Date.now()) + ext
      const localPath = path.join(draftDir, 'assets', subDir, baseName)

      if (!cache.has(item.path)) {
        cache.set(item.path, localPath)
        downloadTasks.push({ item, url: item.path, localPath, baseName })
      }
      item.path = cache.get(item.path)
    }
  }

  if (downloadTasks.length === 0) return

  // 并行下载（最多 8 个并发）
  const CONCURRENCY = 8
  console.log(`  素材本地化: ${downloadTasks.length} 个文件，${CONCURRENCY} 并发...`)
  for (let i = 0; i < downloadTasks.length; i += CONCURRENCY) {
    const batch = downloadTasks.slice(i, i + CONCURRENCY)
    await Promise.all(batch.map(async (task, j) => {
      try {
        await fsp.mkdir(path.dirname(task.localPath), { recursive: true })
        await downloadStream(task.url, task.localPath)
        console.log(`  [${i + j + 1}/${downloadTasks.length}] ${task.baseName} OK`)
      } catch (err) {
        console.error(`  [${i + j + 1}/${downloadTasks.length}] ${task.baseName} FAIL: ${err.message}`)
      }
    }))
  }
}

// ============================================================================
// 注册到 root_meta_info.json
// ============================================================================

function registerDraft(draftId, draftName, totalDurationUs) {
  const { jianyingDraftPath } = getConfig()
  const rootMetaPath = path.join(jianyingDraftPath, 'root_meta_info.json')
  const draftDir = path.join(jianyingDraftPath, draftId)

  if (!fs.existsSync(rootMetaPath)) {
    fs.writeFileSync(rootMetaPath, JSON.stringify({ all_draft_store: [] }, null, 4), 'utf-8')
  }
  const rootMeta = JSON.parse(fs.readFileSync(rootMetaPath, 'utf-8'))
  if (rootMeta.all_draft_store.some(d => d.draft_fold_path === winPath(draftDir))) {
    console.log('  已注册，跳过')
    return
  }

  const now = Date.now() * 1000
  rootMeta.all_draft_store.unshift({
    cloud_draft_cover: false, cloud_draft_sync: false,
    draft_cloud_last_action_download: false, draft_cloud_purchase_info: '',
    draft_cloud_template_id: '', draft_cloud_tutorial_info: '',
    draft_cloud_videocut_purchase_info: '',
    draft_cover: winPath(path.join(draftDir, 'draft_cover.jpg')),
    draft_fold_path: winPath(draftDir),
    draft_id: draftId, draft_is_ai_shorts: false,
    draft_is_cloud_temp_draft: false, draft_is_invisible: false,
    draft_is_web_article_video: false,
    draft_json_file: winPath(path.join(draftDir, 'draft_content.json')),
    draft_name: draftName || draftId, draft_new_version: '',
    draft_root_path: winPath(jianyingDraftPath),
    draft_timeline_materials_size: 0, draft_type: '',
    draft_web_article_video_enter_from: '',
    streaming_edit_draft_ready: true,
    tm_draft_cloud_completed: '', tm_draft_cloud_entry_id: -1,
    tm_draft_cloud_modified: 0, tm_draft_cloud_parent_entry_id: -1,
    tm_draft_cloud_space_id: -1, tm_draft_cloud_user_id: -1,
    tm_draft_create: now, tm_draft_modified: now, tm_draft_removed: 0,
    tm_duration: totalDurationUs || 0,
  })

  fs.writeFileSync(rootMetaPath, JSON.stringify(rootMeta, null, 4), 'utf-8')
  console.log(`  已注册: ${draftName || draftId}`)
}

// ============================================================================
// 触发剪映目录扫描（robocopy 技巧）
// ============================================================================

function triggerDirectoryScan(targetDir) {
  if (!fs.existsSync(targetDir)) return
  const tmpDir = targetDir + '.tmp'

  if (process.platform === 'win32') {
    execFile('robocopy', [targetDir, tmpDir, '/E', '/COPY:DAT', '/R:1', '/W:1', '/NP', '/NJH', '/NJS'],
      { windowsHide: true }, (err) => {
        const code = err ? err.code : 0
        if (code >= 8) console.log(`  扫描触发失败 (code ${code})`)
        else console.log('  已触发剪映扫描')
        try { fs.rmSync(tmpDir, { recursive: true, force: true }) } catch {}
      })
  } else if (process.platform === 'darwin') {
    execFile('rsync', ['-a', targetDir + '/', tmpDir], (err) => {
      if (!err) console.log('  已触发剪映扫描')
      try { fs.rmSync(tmpDir, { recursive: true, force: true }) } catch {}
    })
  }
}

// ============================================================================
// 主流程
// ============================================================================

async function syncDraft(draftUrl, options = {}) {
  const config = getConfig()
  const draftId = extractDraftId(draftUrl)
  if (!draftId) throw new Error('无法从 URL 提取 draft_id')

  const jianyingDraftPath = config.jianyingDraftPath
  const draftDir = path.join(jianyingDraftPath, draftId)

  console.log(`\n同步草稿到本地剪映`)
  console.log(`  draft_id: ${draftId}`)
  console.log(`  目标目录: ${draftDir}\n`)

  // 1. 获取文件列表
  console.log('[1/4] 获取文件列表...')
  const res = await axios.get(draftUrl, { timeout: 30000 })
  if (res.data.code !== undefined && res.data.code !== 0) {
    throw new Error(`API 错误: ${res.data.message}`)
  }
  const fileUrls = res.data.files || []
  console.log(`  获取 ${fileUrls.length} 个文件\n`)

  if (fileUrls.length === 0) {
    console.log('  无文件，跳过')
    return
  }

  // 2. 下载文件
  console.log('[2/4] 下载文件...')
  let success = 0, failed = 0
  for (let i = 0; i < fileUrls.length; i++) {
    const fileUrl = fileUrls[i]
    try {
      // 解析本地路径
      const urlObj = new URL(fileUrl)
      const idIndex = urlObj.pathname.indexOf(draftId)
      if (idIndex === -1) { failed++; continue }
      const relativePath = urlObj.pathname.substring(idIndex).replace(/\//g, path.sep)
      const cleaned = relativePath.replace(draftId + path.sep, '')
      const filePath = path.join(draftDir, cleaned)
      await fsp.mkdir(path.dirname(filePath), { recursive: true })

      const fileName = path.basename(filePath)
      if (fileUrl.endsWith('.json')) {
        // JSON 文件：下载 → 路径重写 → 素材本地化 → 写入
        const jsonRes = await axios.get(fileUrl, { timeout: 30000 })
        const jsonData = jsonRes.data

        if (jsonData?.materials) {
          recursivelyUpdatePaths(jsonData.materials, draftDir, draftId)
          await localizeRemoteMaterials(jsonData.materials, draftDir)
        }

        await fsp.writeFile(filePath, JSON.stringify(jsonData, null, 2), 'utf-8')
      } else {
        // 二进制文件：流式下载
        await downloadStream(fileUrl, filePath)
      }

      console.log(`  [${i + 1}/${fileUrls.length}] OK: ${fileName}`)
      success++
    } catch (err) {
      console.error(`  [${i + 1}/${fileUrls.length}] FAIL: ${path.basename(fileUrl)} - ${err.message}`)
      failed++
    }
  }
  console.log(`  下载完成: ${success}/${fileUrls.length}${failed ? `, 失败 ${failed}` : ''}\n`)

  // 3. 注册到剪映
  console.log('[3/4] 注册到剪映...')
  registerDraft(draftId, options.name)
  console.log('')

  // 4. 触发扫描
  console.log('[4/4] 触发剪映扫描...')
  triggerDirectoryScan(draftDir)

  console.log(`\n同步完成! 打开剪映即可看到草稿。\n`)
}

// ============================================================================
// CLI
// ============================================================================

function parseArgs(argv) {
  const args = {}
  for (let i = 0; i < argv.length; i++) {
    if (argv[i].startsWith('--')) {
      const key = argv[i].slice(2)
      const val = argv[i + 1]
      if (val && !val.startsWith('--')) { args[key] = val; i++ }
      else args[key] = true
    } else {
      if (!args.draftUrl) args.draftUrl = argv[i]
    }
  }
  return args
}

async function main() {
  const args = parseArgs(process.argv.slice(2))
  if (!args.draftUrl) {
    console.log('用法: node sync-to-jianying.js <draft_url> [--name "草稿名称"]')
    console.log('')
    console.log('draft_url: http://xxx/openapi/capcut-mate/v1/get_draft?draft_id=xxx')
    process.exit(0)
  }
  await syncDraft(args.draftUrl, { name: args.name })
}

module.exports = { syncDraft, registerDraft, triggerDirectoryScan }

if (require.main === module) {
  main().catch(err => {
    console.error(`\n错误: ${err.message}`)
    process.exit(1)
  })
}
