// 离线缓存 — pieces metadata + audio + chord PNG 都存 IndexedDB。
// 不用 Cache API 是因为 IDB 单条删除可控，大文件 Blob 友好。
//
// 配置（localStorage）：
//   music.cache.enabled    'true' | 'false'  默认 false
//   music.cache.wifiOnly   'true' | 'false'  默认 true
//
// 进度向 window 广播 CustomEvent('music-cache-progress', { detail: { done, total, busy } })

import { ref } from 'vue'
import { listPieces, getPiece, attachmentUrl } from './api.js'

const DB_NAME = 'music-cache'
const DB_VERSION = 1
const STORE_AUDIO = 'audio'      // key: attachment id (number)
const STORE_IMAGE = 'image'      // key: attachment id
const STORE_META = 'meta'        // key: 'pieces' | 'updated_at' ...

let dbPromise = null

function openDb() {
  if (dbPromise) return dbPromise
  dbPromise = new Promise((resolve, reject) => {
    const req = indexedDB.open(DB_NAME, DB_VERSION)
    req.onupgradeneeded = () => {
      const db = req.result
      if (!db.objectStoreNames.contains(STORE_AUDIO)) db.createObjectStore(STORE_AUDIO)
      if (!db.objectStoreNames.contains(STORE_IMAGE)) db.createObjectStore(STORE_IMAGE)
      if (!db.objectStoreNames.contains(STORE_META)) db.createObjectStore(STORE_META)
    }
    req.onsuccess = () => resolve(req.result)
    req.onerror = () => reject(req.error)
  })
  return dbPromise
}

async function idbGet(store, key) {
  const db = await openDb()
  return new Promise((resolve, reject) => {
    const tx = db.transaction(store, 'readonly')
    const req = tx.objectStore(store).get(key)
    req.onsuccess = () => resolve(req.result)
    req.onerror = () => reject(req.error)
  })
}

async function idbPut(store, key, value) {
  const db = await openDb()
  return new Promise((resolve, reject) => {
    const tx = db.transaction(store, 'readwrite')
    tx.objectStore(store).put(value, key)
    tx.oncomplete = () => resolve()
    tx.onerror = () => reject(tx.error)
  })
}

async function idbDelete(store, key) {
  const db = await openDb()
  return new Promise((resolve, reject) => {
    const tx = db.transaction(store, 'readwrite')
    tx.objectStore(store).delete(key)
    tx.oncomplete = () => resolve()
    tx.onerror = () => reject(tx.error)
  })
}

async function idbKeys(store) {
  const db = await openDb()
  return new Promise((resolve, reject) => {
    const tx = db.transaction(store, 'readonly')
    const req = tx.objectStore(store).getAllKeys()
    req.onsuccess = () => resolve(req.result)
    req.onerror = () => reject(req.error)
  })
}

async function idbClearAll() {
  const db = await openDb()
  return new Promise((resolve, reject) => {
    const tx = db.transaction([STORE_AUDIO, STORE_IMAGE, STORE_META], 'readwrite')
    tx.objectStore(STORE_AUDIO).clear()
    tx.objectStore(STORE_IMAGE).clear()
    tx.objectStore(STORE_META).clear()
    tx.oncomplete = () => resolve()
    tx.onerror = () => reject(tx.error)
  })
}

// ---- 配置 ----

export function isCacheEnabled() {
  return localStorage.getItem('music.cache.enabled') === 'true'
}
export function setCacheEnabled(v) {
  localStorage.setItem('music.cache.enabled', v ? 'true' : 'false')
}
export function isWifiOnly() {
  return localStorage.getItem('music.cache.wifiOnly') !== 'false'  // 默认 true
}
export function setWifiOnly(v) {
  localStorage.setItem('music.cache.wifiOnly', v ? 'true' : 'false')
}

// 网络感知：不是 WiFi（蜂窝 / 慢速）就跳过
function isLikelyMetered() {
  const c = navigator.connection || navigator.mozConnection || navigator.webkitConnection
  if (!c) return false  // 不知道就当 OK
  if (c.saveData) return true
  if (c.type === 'cellular') return true
  if (['slow-2g', '2g'].includes(c.effectiveType)) return true
  return false
}

// ---- 媒体取用 ----

const blobUrlCache = new Map()  // attId -> blob URL（避免反复 createObjectURL）

export async function getCachedBlobUrl(store, attId) {
  if (blobUrlCache.has(attId)) return blobUrlCache.get(attId)
  const blob = await idbGet(store, attId)
  if (!blob) return null
  const url = URL.createObjectURL(blob)
  blobUrlCache.set(attId, url)
  return url
}

// 短路：内存里已有 blob URL → 同步返回；未启用 cache → 直接网络 URL 不查 IDB；
// 只有启用 cache 且内存没 cache 命中才掏 IDB
export function getAudioUrl(attId) {
  if (blobUrlCache.has(attId)) return blobUrlCache.get(attId)
  if (!isCacheEnabled()) return attachmentUrl(attId)
  // 启用了但内存没缓存：网络立返，后台尝试 IDB 命中后下次会用上
  warmCachedBlob(STORE_AUDIO, attId)
  return attachmentUrl(attId)
}

export function getImageUrl(attId) {
  if (blobUrlCache.has(attId)) return blobUrlCache.get(attId)
  if (!isCacheEnabled()) return attachmentUrl(attId)
  warmCachedBlob(STORE_IMAGE, attId)
  return attachmentUrl(attId)
}

function warmCachedBlob(store, attId) {
  idbGet(store, attId).then((blob) => {
    if (blob && !blobUrlCache.has(attId)) {
      blobUrlCache.set(attId, URL.createObjectURL(blob))
    }
  }).catch(() => {})
}

// ---- 状态 ----

export const cacheStats = ref({ audioCount: 0, imageCount: 0, busy: false, done: 0, total: 0 })

async function refreshStats() {
  const [a, i] = await Promise.all([idbKeys(STORE_AUDIO), idbKeys(STORE_IMAGE)])
  cacheStats.value.audioCount = a.length
  cacheStats.value.imageCount = i.length
}

function emitProgress() {
  window.dispatchEvent(new CustomEvent('music-cache-progress', { detail: { ...cacheStats.value } }))
}

// ---- 下载 worker ----

let workerRunning = false
let workerAbort = null

async function downloadOne(url, store, key) {
  const r = await fetch(url, { cache: 'reload' })
  if (!r.ok) throw new Error(`${r.status}`)
  const blob = await r.blob()
  await idbPut(store, key, blob)
}

export async function startCacheWorker() {
  if (workerRunning) return
  if (!isCacheEnabled()) return
  if (isWifiOnly() && isLikelyMetered()) {
    console.log('[cache] skip: on metered connection')
    return
  }
  // 主动申请永久存储
  if (navigator.storage && navigator.storage.persist) {
    try { await navigator.storage.persist() } catch {}
  }

  workerRunning = true
  cacheStats.value.busy = true
  workerAbort = new AbortController()
  try {
    // 1) 拉全部 pieces metadata
    const pieces = await listPieces()
    await idbPut(STORE_META, 'pieces', pieces)
    await idbPut(STORE_META, 'updated_at', Date.now())

    // 2) 算出所有要下载的 (store, id) 列表
    const haveAudio = new Set(await idbKeys(STORE_AUDIO))
    const haveImage = new Set(await idbKeys(STORE_IMAGE))
    const targets = []
    for (const p of pieces) {
      const detail = await getPiece(p.id)
      for (const a of detail.attachments || []) {
        if (a.kind === 'audio' && !haveAudio.has(a.id)) {
          targets.push({ store: STORE_AUDIO, id: a.id })
        } else if (a.kind === 'image' && !haveImage.has(a.id)) {
          targets.push({ store: STORE_IMAGE, id: a.id })
        }
      }
      if (workerAbort.signal.aborted) break
    }

    cacheStats.value.total = targets.length
    cacheStats.value.done = 0
    emitProgress()

    // 3) 串行下载（concurrency=2）
    const concurrency = 2
    const queue = [...targets]
    async function worker() {
      while (queue.length && !workerAbort.signal.aborted) {
        if (isWifiOnly() && isLikelyMetered()) break
        const t = queue.shift()
        try {
          await downloadOne(attachmentUrl(t.id), t.store, t.id)
        } catch (e) {
          console.warn('[cache] dl failed', t, e)
        }
        cacheStats.value.done++
        emitProgress()
        await new Promise((r) => setTimeout(r, 80))
      }
    }
    await Promise.all(Array.from({ length: concurrency }, () => worker()))
  } catch (e) {
    console.warn('[cache] worker error', e)
  } finally {
    workerRunning = false
    cacheStats.value.busy = false
    workerAbort = null
    await refreshStats()
    emitProgress()
  }
}

export function abortCacheWorker() {
  if (workerAbort) workerAbort.abort()
}

export async function clearCache() {
  abortCacheWorker()
  for (const u of blobUrlCache.values()) URL.revokeObjectURL(u)
  blobUrlCache.clear()
  await idbClearAll()
  await refreshStats()
  emitProgress()
}

// ---- 存储用量 ----

export async function estimateUsage() {
  if (navigator.storage && navigator.storage.estimate) {
    const e = await navigator.storage.estimate()
    return { usage: e.usage || 0, quota: e.quota || 0 }
  }
  return { usage: 0, quota: 0 }
}

// ---- 启动入口 ----

export async function initCache() {
  await refreshStats()
  if (isCacheEnabled()) {
    // 不阻塞主线程，延后 3s 让 app 先 render
    setTimeout(() => startCacheWorker(), 3000)
  }
}
