import 'dart:io'; import 'package:path/path.dart'; import 'package:sqflite/sqflite.dart'; import '../modelos/emisora.dart'; /// Servicio de persistencia de emisoras favoritas con SQLite. /// /// - Inicialización lazy: la BD se abre en el primer acceso. /// - Migration-ready: versión 2 añade campos de la Radio Browser API. class ServicioFavoritos { static const _dbName = 'pluriwave.db'; static const _dbVersion = 2; Database? _db; Future get _database async { _db ??= await _initDb(); return _db!; } Future _initDb() async { final dbPath = await getDatabasesPath(); await Directory(dbPath).create(recursive: true); final path = join(dbPath, _dbName); return openDatabase( path, version: _dbVersion, onCreate: _onCreate, onUpgrade: _onUpgrade, onOpen: _asegurarEsquema, ); } Future _onCreate(Database db, int version) async { await db.execute(''' CREATE TABLE favoritos ( id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT NOT NULL UNIQUE, nombre TEXT NOT NULL, url TEXT NOT NULL, favicon TEXT, pais TEXT, codigo_pais TEXT, idioma TEXT, tags TEXT, codec TEXT, bitrate INTEGER, votes INTEGER NOT NULL DEFAULT 0, clickcount INTEGER NOT NULL DEFAULT 0, orden INTEGER NOT NULL DEFAULT 0 ) '''); } Future _onUpgrade(Database db, int oldVersion, int newVersion) async { await _asegurarEsquema(db); } Future _asegurarEsquema(Database db) async { final tablas = await db.rawQuery( "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'favoritos'", ); if (tablas.isEmpty) { await _onCreate(db, _dbVersion); return; } final columnas = await _columnas(db, 'favoritos'); Future addColumn(String nombre, String sql) async { if (!columnas.contains(nombre)) { await db.execute('ALTER TABLE favoritos ADD COLUMN $nombre $sql'); columnas.add(nombre); } } // Migración defensiva: algunas instalaciones antiguas pueden venir de // esquemas intermedios. No asumimos qué columna existe: la verificamos. await addColumn('favicon', 'TEXT'); await addColumn('pais', 'TEXT'); await addColumn('codigo_pais', 'TEXT'); await addColumn('idioma', 'TEXT'); await addColumn('tags', 'TEXT'); await addColumn('codec', 'TEXT'); await addColumn('bitrate', 'INTEGER'); await addColumn('votes', 'INTEGER NOT NULL DEFAULT 0'); await addColumn('clickcount', 'INTEGER NOT NULL DEFAULT 0'); await addColumn('orden', 'INTEGER NOT NULL DEFAULT 0'); } Future> _columnas(Database db, String tabla) async { final info = await db.rawQuery('PRAGMA table_info($tabla)'); return info.map((row) => row['name'] as String).toSet(); } /// Devuelve todas las emisoras favoritas ordenadas por [orden]. Future> obtenerTodos() async { final db = await _database; final rows = await db.query('favoritos', orderBy: 'orden ASC'); return rows.map(Emisora.fromMap).toList(); } /// Añade una emisora a favoritos. Si ya existe (mismo uuid), la actualiza. Future agregar(Emisora emisora) async { final db = await _database; // Calcular el siguiente orden final maxOrden = Sqflite.firstIntValue( await db.rawQuery('SELECT MAX(orden) FROM favoritos'), ) ?? -1; final nuevaEmisora = emisora.copyWith(orden: maxOrden + 1); await db.insert( 'favoritos', nuevaEmisora.toMap(), conflictAlgorithm: ConflictAlgorithm.replace, ); } /// Elimina una emisora de favoritos por [uuid]. Future eliminar(String uuid) async { final db = await _database; await db.delete('favoritos', where: 'uuid = ?', whereArgs: [uuid]); } /// Devuelve true si la emisora con [uuid] está en favoritos. Future esFavorito(String uuid) async { final db = await _database; final count = Sqflite.firstIntValue( await db.rawQuery( 'SELECT COUNT(*) FROM favoritos WHERE uuid = ?', [uuid], ), ); return (count ?? 0) > 0; } /// Alterna el estado de favorito de una emisora. Future toggleFavorito(Emisora emisora) async { if (await esFavorito(emisora.uuid)) { await eliminar(emisora.uuid); return false; } else { await agregar(emisora); return true; } } /// Actualiza el orden de un favorito. Future reordenar(String uuid, int nuevoOrden) async { final db = await _database; await db.transaction((txn) async { final rows = await txn.query( 'favoritos', columns: ['uuid'], orderBy: 'orden ASC, id ASC', ); final uuids = rows.map((r) => r['uuid'] as String).toList(); final oldIndex = uuids.indexOf(uuid); if (oldIndex == -1 || uuids.isEmpty) return; final targetIndex = nuevoOrden.clamp(0, uuids.length - 1); final moved = uuids.removeAt(oldIndex); uuids.insert(targetIndex, moved); for (var i = 0; i < uuids.length; i++) { await txn.update( 'favoritos', {'orden': i}, where: 'uuid = ?', whereArgs: [uuids[i]], ); } }); } }