/**
 * Cache-Aside + Write-Through storage para Alpha.
 *
 * ─── Patrón ─────────────────────────────────────────────────────
 *
 *   L1 (rápido)      → Map en memoria        ~0ms
 *   L2 (persistente) → Dexie IndexedDB       ~5-50ms
 *   L3 (fallback)    → localStorage           ~1ms
 *
 * ─── Lectura (Cache-Aside) ─────────────────────────────────────
 *
 *   get(key):
 *     1. L1 hit → return (instantáneo)
 *     2. L2 hit → poblar L1 → return
 *     3. L3 hit → poblar L2 + L1 → return
 *     4. Miss  → return null
 *
 * ─── Escritura (Write-Through) ─────────────────────────────────
 *
 *   set(key, value):
 *     1. Escribir L1 (instantáneo)
 *     2. Escribir L2 (Dexie, async await)
 *     3. Escribir L3 (localStorage, sync)
 *     Si L2 falla → el dato sigue en L1 + L3 (consistencia eventual)
 *
 * ─── ¿Por qué este patrón? ─────────────────────────────────────
 *
 *   - Cache-Aside: control explícito de qué se cachea y cuándo
 *   - Write-Through: el cache siempre refleja la fuente de verdad
 *   - L3 (localStorage) actúa como quorum: si IndexedDB falla,
 *     los datos no se pierden
 *   - Para RUMBO: mismo patrón, cambia L2 de Dexie a Turso/libSQL
 *     y L3 pasa a ser un archivo JSON en ~/.rumbo/cache.json
 *
 * ─── Uso ───────────────────────────────────────────────────────
 *
 *   import { storage } from '@/services/storage'
 *   await storage.init()
 *   storage.get('key')         → string | null  (sync)
 *   await storage.set('k', v)  → void           (async)
 *   await storage.remove('k')  → void           (async)
 *   storage.getJSON<T>('k')    → T | null       (sync)
 *   await storage.setJSON()    → void           (async)
 */

import db from '@/services/db'

class AppStorage {
  /** L1: cache en memoria */
  private cache = new Map<string, { value: string; ttl: number | null }>()
  private loaded = false
  private initPromise: Promise<void> | null = null

  // ─── Init ──────────────────────────────────────────────────

  async init() {
    if (this.initPromise) return this.initPromise
    this.initPromise = this._load()
    return this.initPromise
  }

  private async _load() {
    const all = await db.settings.toArray()
    for (const entry of all) {
      this.cache.set(entry.key, { value: entry.value, ttl: null })
    }
    this.loaded = true
    console.log(`[Storage] Init OK — ${all.length} entries en L1`)
  }

  isReady() { return this.loaded }

  // ─── Lectura: Cache-Aside (L1 → L2 → L3) ────────────────

  get(key: string): string | null {
    // 1. L1 hit
    const entry = this.cache.get(key)
    if (entry) {
      if (entry.ttl !== null && entry.ttl < Date.now()) {
        this.cache.delete(key)
      } else {
        return entry.value
      }
    }

    // 2. L2 (Dexie)
    // Nota: Dexie.get() es async. Para mantener get() sync,
    // delegamos la carga asíncrona a init(). Si no está en L1
    // después de init, cae a L3.
    // En la práctica, init() carga TODO al arranque, así que
    // L1 siempre está poblado para keys existentes.

    // 3. L3 (localStorage) — fallback + migración
    const fallback = localStorage.getItem(key)
    if (fallback !== null) {
      this.cache.set(key, { value: fallback, ttl: null })
      // Write-Through hacia L2 (fire-and-forget)
      db.settings.put({ key, value: fallback }).catch(() => {})
      return fallback
    }

    return null
  }

  getJSON<T>(key: string): T | null {
    const raw = this.get(key)
    if (!raw) return null
    try { return JSON.parse(raw) as T } catch { return null }
  }

  // ─── Escritura: Write-Through (L1 + L2 + L3) ─────────────

  async set(key: string, value: string) {
    // 1. L1 (instantáneo)
    this.cache.set(key, { value, ttl: null })

    // 2. L2 + L3 en paralelo
    await Promise.all([
      db.settings.put({ key, value }).catch(e => {
        console.error(`[Storage] L2 error writing "${key}":`, e)
      }),
      Promise.resolve().then(() => {
        localStorage.setItem(key, value)
      }),
    ])
  }

  async setJSON(key: string, value: unknown) {
    await this.set(key, JSON.stringify(value))
  }

  async remove(key: string) {
    this.cache.delete(key)

    await Promise.all([
      db.settings.delete(key).catch(e => {
        console.error(`[Storage] L2 error deleting "${key}":`, e)
      }),
      Promise.resolve().then(() => {
        localStorage.removeItem(key)
      }),
    ])
  }

  // ─── Utilidades ───────────────────────────────────────────

  /** Expone el tamaño del cache L1 (debug) */
  get cacheSize() { return this.cache.size }
}

export const storage = new AppStorage()
