import 'dart:convert'; import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart'; import 'package:uuid/uuid.dart'; import '../models/models.dart'; class LocalStorage { static const _dbName = 'add_months.db'; static const _tableName = 'user_data'; static const _savedRunsTable = 'saved_runs'; static const _version = 2; static Database? _database; static const _uuid = Uuid(); static Future get database async { if (_database != null) return _database!; _database = await _initDatabase(); return _database!; } static Future _initDatabase() async { final dbPath = await getDatabasesPath(); final path = join(dbPath, _dbName); return await openDatabase( path, version: _version, onCreate: (db, version) async { await db.execute(''' CREATE TABLE $_tableName ( key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at INTEGER NOT NULL ) '''); await db.execute(''' CREATE TABLE $_savedRunsTable ( id TEXT PRIMARY KEY, label TEXT NOT NULL, result TEXT NOT NULL, profile TEXT NOT NULL, behaviors TEXT NOT NULL, created_at INTEGER NOT NULL ) '''); }, onUpgrade: (db, oldVersion, newVersion) async { if (oldVersion < 2) { await db.execute(''' CREATE TABLE $_savedRunsTable ( id TEXT PRIMARY KEY, label TEXT NOT NULL, result TEXT NOT NULL, profile TEXT NOT NULL, behaviors TEXT NOT NULL, created_at INTEGER NOT NULL ) '''); } }, ); } // Generic key-value operations static Future _put(String key, Map value) async { final db = await database; await db.insert( _tableName, { 'key': key, 'value': jsonEncode(value), 'updated_at': DateTime.now().millisecondsSinceEpoch, }, conflictAlgorithm: ConflictAlgorithm.replace, ); } static Future?> _get(String key) async { final db = await database; final results = await db.query( _tableName, where: 'key = ?', whereArgs: [key], ); if (results.isEmpty) return null; return jsonDecode(results.first['value'] as String) as Map; } // Profile operations static Future saveProfile(UserProfile profile) async { await _put('profile', profile.toJson()); } static Future getProfile() async { final json = await _get('profile'); if (json == null) return null; return UserProfile.fromJson(json); } // Behavioral inputs operations static Future saveBehaviors(BehavioralInputs behaviors) async { await _put('behaviors', behaviors.toJson()); } static Future getBehaviors() async { final json = await _get('behaviors'); if (json == null) return null; return BehavioralInputs.fromJson(json); } // Result operations static Future saveResult(CalculationResult result) async { await _put('lastResult', result.toJson()); } static Future getLastResult() async { final json = await _get('lastResult'); if (json == null) return null; return CalculationResult.fromJson(json); } // Check if user has completed setup static Future hasCompletedSetup() async { final profile = await getProfile(); final behaviors = await getBehaviors(); return profile != null && behaviors != null; } // Delete all data (including saved runs) static Future deleteAllData() async { final db = await database; await db.delete(_tableName); await db.delete(_savedRunsTable); } // Get last updated timestamp static Future getLastUpdated() async { final db = await database; final results = await db.query( _tableName, columns: ['updated_at'], orderBy: 'updated_at DESC', limit: 1, ); if (results.isEmpty) return null; return DateTime.fromMillisecondsSinceEpoch( results.first['updated_at'] as int, ); } // ============================================ // Saved Runs operations // ============================================ static Future saveSavedRun({ required String label, required CalculationResult result, required UserProfile profile, required BehavioralInputs behaviors, }) async { final db = await database; final id = _uuid.v4(); final now = DateTime.now(); await db.insert(_savedRunsTable, { 'id': id, 'label': label, 'result': jsonEncode(result.toJson()), 'profile': jsonEncode(profile.toJson()), 'behaviors': jsonEncode(behaviors.toJson()), 'created_at': now.millisecondsSinceEpoch, }); return id; } static Future> getSavedRuns() async { final db = await database; final results = await db.query( _savedRunsTable, orderBy: 'created_at DESC', ); return results.map((row) => SavedRun( id: row['id'] as String, label: row['label'] as String, result: CalculationResult.fromJson( jsonDecode(row['result'] as String) as Map, ), profile: UserProfile.fromJson( jsonDecode(row['profile'] as String) as Map, ), behaviors: BehavioralInputs.fromJson( jsonDecode(row['behaviors'] as String) as Map, ), createdAt: DateTime.fromMillisecondsSinceEpoch(row['created_at'] as int), )).toList(); } static Future getSavedRun(String id) async { final db = await database; final results = await db.query( _savedRunsTable, where: 'id = ?', whereArgs: [id], ); if (results.isEmpty) return null; final row = results.first; return SavedRun( id: row['id'] as String, label: row['label'] as String, result: CalculationResult.fromJson( jsonDecode(row['result'] as String) as Map, ), profile: UserProfile.fromJson( jsonDecode(row['profile'] as String) as Map, ), behaviors: BehavioralInputs.fromJson( jsonDecode(row['behaviors'] as String) as Map, ), createdAt: DateTime.fromMillisecondsSinceEpoch(row['created_at'] as int), ); } static Future updateSavedRunLabel(String id, String newLabel) async { final db = await database; await db.update( _savedRunsTable, {'label': newLabel}, where: 'id = ?', whereArgs: [id], ); } static Future deleteSavedRun(String id) async { final db = await database; await db.delete( _savedRunsTable, where: 'id = ?', whereArgs: [id], ); } static Future deleteAllSavedRuns() async { final db = await database; await db.delete(_savedRunsTable); } static Future getSavedRunsCount() async { final db = await database; final result = await db.rawQuery('SELECT COUNT(*) as count FROM $_savedRunsTable'); return result.first['count'] as int; } // ============================================ // Unit preference operations // ============================================ static Future setUseMetricUnits(bool useMetric) async { await _put('useMetricUnits', {'value': useMetric}); } static Future getUseMetricUnits() async { final json = await _get('useMetricUnits'); if (json == null) return false; // Default to imperial (US) return json['value'] as bool; } }