import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; import '../modelos/preset_ecualizador.dart'; class ConfiguracionEcualizador { const ConfiguracionEcualizador({ required this.principal, required this.porEmisora, this.activo = true, this.eqMultiDeviceEnabled = false, this.presetsDispositivo = const {}, this.presetsMatriz = const {}, this.nombresDispositivos = const {}, }); final PresetEcualizador principal; final Map porEmisora; final bool activo; /// Feature toggle: when false all multi-device logic is bypassed. final bool eqMultiDeviceEnabled; /// Per-device presets: deviceId → PresetEcualizador. final Map presetsDispositivo; /// Matrix presets: "stationUuid:deviceId" → PresetEcualizador. final Map presetsMatriz; /// Custom display names for devices: deviceId → custom name. final Map nombresDispositivos; } class ServicioEcualizador { ServicioEcualizador({SharedPreferences? prefs}) : _prefs = prefs; static const _keyPresetPrincipal = 'eq_preset_principal_v1'; static const _keyPresetsPorEmisora = 'eq_presets_por_emisora_v1'; static const _keyActivo = 'eq_activo_v1'; static const _keyMultiDeviceEnabled = 'eq_multi_device_enabled_v1'; static const _keyPresetsPorDispositivo = 'eq_preset_por_dispositivo_v1'; static const _keyPresetsMatriz = 'eq_presets_matriz_v1'; static const _keyNombresDispositivos = 'eq_nombres_dispositivos_v1'; /// bt-device-identity ADR-5: guard flag for the one-time placeholder purge. static const _keyPlaceholderPurgaHecha = 'eq_placeholder_purge_done_v1'; /// bt-device-identity ADR-3: the exact OS placeholder MAC, in the /// `bt_a2dp:`-prefixed id shape used across all device-keyed SP maps. static const _placeholderMacLiteral = 'bt_a2dp:02:00:00:00:00:00'; final SharedPreferences? _prefs; /// Injected startup instance (S3-R4); getInstance() is only a fallback. Future _resolverPrefs() async => _prefs ?? SharedPreferences.getInstance(); Future cargar() async { await migrarClavesPlaceholder(); final prefs = await _resolverPrefs(); final principal = _leerPresetPrincipal(prefs); final porEmisora = _leerPresetsPorEmisora(prefs); final presetsDispositivo = _leerMapa(prefs, _keyPresetsPorDispositivo); final presetsMatriz = _leerMapa(prefs, _keyPresetsMatriz); final nombresDispositivos = _leerMapaStrings(prefs, _keyNombresDispositivos); return ConfiguracionEcualizador( principal: principal, porEmisora: porEmisora, activo: prefs.getBool(_keyActivo) ?? true, eqMultiDeviceEnabled: prefs.getBool(_keyMultiDeviceEnabled) ?? false, presetsDispositivo: presetsDispositivo, presetsMatriz: presetsMatriz, nombresDispositivos: nombresDispositivos, ); } /// One-time guarded migration (bt-device-identity ADR-5): purges entries /// keyed by the exact OS placeholder MAC ([_placeholderMacLiteral]) from /// all three device-keyed SP maps. Idempotent — short-circuits via /// [_keyPlaceholderPurgaHecha] after the first successful run. Every other /// entry (including near-miss keys) is preserved byte-for-byte. Future migrarClavesPlaceholder() async { final prefs = await _resolverPrefs(); if (prefs.getBool(_keyPlaceholderPurgaHecha) ?? false) return; final presetsPorDispositivo = _leerMapa(prefs, _keyPresetsPorDispositivo); if (presetsPorDispositivo.remove(_placeholderMacLiteral) != null) { await _guardarMapa( prefs, _keyPresetsPorDispositivo, presetsPorDispositivo, ); } // Matrix keys are "stationUuid:deviceId"; split on the FIRST colon only // (station UUIDs are RFC4122 and contain no colons — multi-device-eq // ADR-3), since deviceId itself may contain colons (e.g. a MAC-based id). final presetsMatriz = _leerMapa(prefs, _keyPresetsMatriz); final clavesMatrizAPurgar = presetsMatriz.keys.where((clave) { final separador = clave.indexOf(':'); if (separador == -1) return false; final deviceIdSegmento = clave.substring(separador + 1); return deviceIdSegmento == _placeholderMacLiteral; }).toList(); if (clavesMatrizAPurgar.isNotEmpty) { for (final clave in clavesMatrizAPurgar) { presetsMatriz.remove(clave); } await _guardarMapa(prefs, _keyPresetsMatriz, presetsMatriz); } final nombresDispositivos = _leerMapaStrings( prefs, _keyNombresDispositivos, ); if (nombresDispositivos.remove(_placeholderMacLiteral) != null) { await _guardarMapaStrings( prefs, _keyNombresDispositivos, nombresDispositivos, ); } await prefs.setBool(_keyPlaceholderPurgaHecha, true); } Future guardarPrincipal(PresetEcualizador preset) async { final prefs = await _resolverPrefs(); await prefs.setString(_keyPresetPrincipal, jsonEncode(preset.toJson())); } Future guardarPorEmisora(String uuid, PresetEcualizador preset) async { final prefs = await _resolverPrefs(); final mapa = _leerPresetsPorEmisora(prefs); mapa[uuid] = preset; await _guardarPresetsPorEmisora(prefs, mapa); } Future guardarActivo(bool activo) async { final prefs = await _resolverPrefs(); await prefs.setBool(_keyActivo, activo); } Future eliminarPorEmisora(String uuid) async { final prefs = await _resolverPrefs(); final mapa = _leerPresetsPorEmisora(prefs); mapa.remove(uuid); await _guardarPresetsPorEmisora(prefs, mapa); } Future guardarConfiguracion(ConfiguracionEcualizador config) async { final prefs = await _resolverPrefs(); await prefs.setString( _keyPresetPrincipal, jsonEncode(config.principal.toJson()), ); await _guardarPresetsPorEmisora(prefs, config.porEmisora); await prefs.setBool(_keyActivo, config.activo); await prefs.setBool(_keyMultiDeviceEnabled, config.eqMultiDeviceEnabled); await _guardarMapa( prefs, _keyPresetsPorDispositivo, config.presetsDispositivo, ); await _guardarMapa(prefs, _keyPresetsMatriz, config.presetsMatriz); await _guardarMapaStrings( prefs, _keyNombresDispositivos, config.nombresDispositivos, ); } /// Persists the multi-device feature toggle. Future guardarToggleMultiDispositivo(bool habilitado) async { final prefs = await _resolverPrefs(); await prefs.setBool(_keyMultiDeviceEnabled, habilitado); } /// Saves a per-device preset. Future guardarPresetDispositivo( String deviceId, PresetEcualizador preset, ) async { final prefs = await _resolverPrefs(); final mapa = _leerMapa(prefs, _keyPresetsPorDispositivo); mapa[deviceId] = preset; await _guardarMapa(prefs, _keyPresetsPorDispositivo, mapa); } /// Saves a matrix (stationUuid:deviceId) preset entry. Future guardarPresetMatriz( String clave, PresetEcualizador preset, ) async { final prefs = await _resolverPrefs(); final mapa = _leerMapa(prefs, _keyPresetsMatriz); mapa[clave] = preset; await _guardarMapa(prefs, _keyPresetsMatriz, mapa); } /// Removes a per-device preset entry. Future eliminarPresetDispositivo(String deviceId) async { final prefs = await _resolverPrefs(); final mapa = _leerMapa(prefs, _keyPresetsPorDispositivo); mapa.remove(deviceId); await _guardarMapa(prefs, _keyPresetsPorDispositivo, mapa); } /// Removes a matrix preset entry. Future eliminarPresetMatriz(String clave) async { final prefs = await _resolverPrefs(); final mapa = _leerMapa(prefs, _keyPresetsMatriz); mapa.remove(clave); await _guardarMapa(prefs, _keyPresetsMatriz, mapa); } /// Returns the persisted device custom names map. Future> cargarNombresDispositivos() async { final prefs = await _resolverPrefs(); return _leerMapaStrings(prefs, _keyNombresDispositivos); } /// Persists the device custom names map. Future guardarNombresDispositivos(Map nombres) async { final prefs = await _resolverPrefs(); await _guardarMapaStrings(prefs, _keyNombresDispositivos, nombres); } /// Reads a `Map` from a SharedPreferences JSON key. Map _leerMapa( SharedPreferences prefs, String key, ) { final raw = prefs.getString(key); if (raw == null || raw.isEmpty) return {}; try { final data = Map.from(jsonDecode(raw) as Map); return data.map( (k, v) => MapEntry( k, PresetEcualizador.desdeJson(Map.from(v as Map)), ), ); } catch (_) { return {}; } } Future _guardarMapa( SharedPreferences prefs, String key, Map mapa, ) async { final serializado = mapa.map((k, v) => MapEntry(k, v.toJson())); await prefs.setString(key, jsonEncode(serializado)); } /// Reads a `Map` from a SharedPreferences JSON key. Map _leerMapaStrings(SharedPreferences prefs, String key) { final raw = prefs.getString(key); if (raw == null || raw.isEmpty) return {}; try { final data = Map.from(jsonDecode(raw) as Map); return data.map((k, v) => MapEntry(k, v as String)); } catch (_) { return {}; } } Future _guardarMapaStrings( SharedPreferences prefs, String key, Map mapa, ) async { await prefs.setString(key, jsonEncode(mapa)); } PresetEcualizador _leerPresetPrincipal(SharedPreferences prefs) { final raw = prefs.getString(_keyPresetPrincipal); if (raw == null || raw.isEmpty) { return PresetEcualizador.flat; } try { return PresetEcualizador.desdeJson( Map.from(jsonDecode(raw) as Map), ); } catch (_) { return PresetEcualizador.flat; } } Map _leerPresetsPorEmisora( SharedPreferences prefs, ) { final raw = prefs.getString(_keyPresetsPorEmisora); if (raw == null || raw.isEmpty) { return {}; } try { final data = Map.from(jsonDecode(raw) as Map); return data.map( (uuid, preset) => MapEntry( uuid, PresetEcualizador.desdeJson(Map.from(preset as Map)), ), ); } catch (_) { return {}; } } Future _guardarPresetsPorEmisora( SharedPreferences prefs, Map mapa, ) async { final serializado = mapa.map( (uuid, preset) => MapEntry(uuid, preset.toJson()), ); await prefs.setString(_keyPresetsPorEmisora, jsonEncode(serializado)); } }