/* ═══════════════════════════════════════════════════════
   Kling Motion — Client-Side IndexedDB Storage
   ═══════════════════════════════════════════════════════
   Stores video Blobs in browser IndexedDB with auto-expiration.
   Replaces server-side disk persistence when LOCAL_PERSIST_VIDEOS=false.

   - DB: kling_motion_videos
   - Store: results  (key: assetId, value: { blob, contentType, savedAt, expiresAt, metadata })
   - Default TTL: 24 hours (configurable via setRetentionMs)
   - Auto-cleanup: on init + every 5 minutes
   - Capacity: depends on browser (~50MB-1GB+ per origin)
*/

(function (global) {
  "use strict";

  const DB_NAME = "kling_motion_videos";
  const DB_VERSION = 1;
  const STORE_NAME = "results";
  const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
  const CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // every 5 min
  const MAX_ITEMS = 50; // hard cap to prevent quota exhaustion

  let dbPromise = null;
  let cleanupTimer = null;
  let _retentionMs = DEFAULT_TTL_MS;

  // ── DB init ────────────────────────────────────────────
  function openDB() {
    if (dbPromise) return dbPromise;
    dbPromise = new Promise((resolve, reject) => {
      if (!global.indexedDB) {
        reject(new Error("IndexedDB tidak tersedia di browser ini"));
        return;
      }
      const req = global.indexedDB.open(DB_NAME, DB_VERSION);
      req.onupgradeneeded = (e) => {
        const db = e.target.result;
        if (!db.objectStoreNames.contains(STORE_NAME)) {
          const store = db.createObjectStore(STORE_NAME, { keyPath: "assetId" });
          store.createIndex("expiresAt", "expiresAt", { unique: false });
        }
      };
      req.onsuccess = () => resolve(req.result);
      req.onerror = () => reject(req.error);
    });
    return dbPromise;
  }

  async function txStore(mode) {
    const db = await openDB();
    return db.transaction(STORE_NAME, mode).objectStore(STORE_NAME);
  }

  function reqToPromise(req) {
    return new Promise((resolve, reject) => {
      req.onsuccess = () => resolve(req.result);
      req.onerror = () => reject(req.error);
    });
  }

  // ── Public API ─────────────────────────────────────────
  function setRetentionMs(ms) {
    _retentionMs = Math.max(60_000, Number(ms) || DEFAULT_TTL_MS);
  }

  /**
   * Save a Blob to IndexedDB under assetId.
   * @param {string} assetId
   * @param {Blob} blob
   * @param {object} [metadata]
   * @returns {Promise<{assetId, savedAt, expiresAt, sizeBytes, contentType}>}
   */
  async function put(assetId, blob, metadata = {}) {
    if (!assetId) throw new Error("assetId required");
    if (!(blob instanceof Blob)) throw new Error("blob required (Blob instance)");
    const savedAt = Date.now();
    const expiresAt = savedAt + _retentionMs;
    const contentType = blob.type || "video/mp4";
    const record = {
      assetId,
      blob,
      contentType,
      savedAt,
      expiresAt,
      sizeBytes: blob.size,
      metadata: metadata || {},
    };
    const store = await txStore("readwrite");
    await reqToPromise(store.put(record));

    // Enforce MAX_ITEMS cap — drop oldest if exceeded
    await enforceCap();

    return { assetId, savedAt, expiresAt, sizeBytes: blob.size, contentType };
  }

  /**
   * Get a Blob from IndexedDB.
   * @param {string} assetId
   * @returns {Promise<{blob, contentType, savedAt, expiresAt, metadata} | null>}
   */
  async function get(assetId) {
    if (!assetId) return null;
    const store = await txStore("readonly");
    const rec = await reqToPromise(store.get(assetId));
    if (!rec) return null;
    // Auto-expire on read
    if (rec.expiresAt <= Date.now()) {
      await del(assetId);
      return null;
    }
    return rec;
  }

  /**
   * Get a Blob as object URL (caller must revoke when done).
   * @param {string} assetId
   * @returns {Promise<string | null>}
   */
  async function getObjectURL(assetId) {
    const rec = await get(assetId);
    if (!rec) return null;
    return URL.createObjectURL(rec.blob);
  }

  /**
   * Delete a record.
   * @param {string} assetId
   */
  async function del(assetId) {
    if (!assetId) return;
    const store = await txStore("readwrite");
    await reqToPromise(store.delete(assetId));
  }

  /**
   * List all non-expired records (without blob payload).
   * @returns {Promise<Array<{assetId, contentType, savedAt, expiresAt, sizeBytes, metadata}>>}
   */
  async function list() {
    const store = await txStore("readonly");
    const all = await reqToPromise(store.getAll());
    const now = Date.now();
    const items = [];
    for (const r of all) {
      if (r.expiresAt <= now) continue;
      items.push({
        assetId: r.assetId,
        contentType: r.contentType,
        savedAt: r.savedAt,
        expiresAt: r.expiresAt,
        sizeBytes: r.sizeBytes,
        metadata: r.metadata || {},
      });
    }
    items.sort((a, b) => b.savedAt - a.savedAt);
    return items;
  }

  /**
   * Purge all expired records. Returns the number deleted + bytes freed.
   */
  async function cleanup() {
    const store = await txStore("readwrite");
    const all = await reqToPromise(store.getAll());
    const now = Date.now();
    let deleted = 0;
    let freed = 0;
    for (const r of all) {
      if (r.expiresAt <= now) {
        await reqToPromise(store.delete(r.assetId));
        deleted++;
        freed += r.sizeBytes || 0;
      }
    }
    return { deleted, freedBytes: freed };
  }

  /**
   * Hard cap — keep only the most recent MAX_ITEMS records.
   */
  async function enforceCap() {
    const store = await txStore("readwrite");
    const all = await reqToPromise(store.getAll());
    if (all.length <= MAX_ITEMS) return { trimmed: 0 };
    all.sort((a, b) => b.savedAt - a.savedAt);
    const toDelete = all.slice(MAX_ITEMS);
    for (const r of toDelete) {
      await reqToPromise(store.delete(r.assetId));
    }
    return { trimmed: toDelete.length };
  }

  /**
   * Storage usage estimate (in bytes).
   */
  async function getStorageEstimate() {
    if (navigator.storage && navigator.storage.estimate) {
      const est = await navigator.storage.estimate();
      return {
        usage: est.usage || 0,
        quota: est.quota || 0,
        percent: est.quota ? ((est.usage / est.quota) * 100).toFixed(1) : 0,
      };
    }
    return { usage: 0, quota: 0, percent: 0 };
  }

  /**
   * Total size of all stored blobs (in bytes).
   */
  async function getTotalSize() {
    const store = await txStore("readonly");
    const all = await reqToPromise(store.getAll());
    return all.reduce((sum, r) => sum + (r.sizeBytes || 0), 0);
  }

  /**
   * Start the periodic cleanup timer.
   * Safe to call multiple times — only one timer runs.
   */
  function startAutoCleanup() {
    if (cleanupTimer) return;
    cleanupTimer = setInterval(async () => {
      try {
        const { deleted, freedBytes } = await cleanup();
        if (deleted > 0) {
          console.log(`[ClientStorage] Auto-cleanup: hapus ${deleted} video expired (${(freedBytes / 1024 / 1024).toFixed(2)} MB)`);
        }
      } catch (e) {
        console.warn("[ClientStorage] Auto-cleanup error:", e);
      }
    }, CLEANUP_INTERVAL_MS);
  }

  function stopAutoCleanup() {
    if (cleanupTimer) {
      clearInterval(cleanupTimer);
      cleanupTimer = null;
    }
  }

  /**
   * One-shot init: open DB, run cleanup, start timer.
   * Call on page load.
   */
  async function init() {
    try {
      await openDB();
      const { deleted, freedBytes } = await cleanup();
      if (deleted > 0) {
        console.log(`[ClientStorage] Init cleanup: hapus ${deleted} video expired (${(freedBytes / 1024 / 1024).toFixed(2)} MB)`);
      }
      startAutoCleanup();
      return { ok: true, cleaned: deleted, freedBytes };
    } catch (e) {
      console.error("[ClientStorage] Init failed:", e);
      return { ok: false, error: e.message };
    }
  }

  // ── Export ─────────────────────────────────────────────
  global.ClientStorage = {
    init,
    put,
    get,
    getObjectURL,
    del,
    list,
    cleanup,
    enforceCap,
    getStorageEstimate,
    getTotalSize,
    setRetentionMs,
    startAutoCleanup,
    stopAutoCleanup,
    DEFAULT_TTL_MS,
    MAX_ITEMS,
  };
})(typeof window !== "undefined" ? window : globalThis);
