import 'dart:convert'; import 'package:immich_mobile/utils/semver.dart'; sealed class ValueCodec { const ValueCodec(); String encode(T value); T decode(String raw); static final Map> _primitives = { ..._register(PrimitiveCodec.integer), ..._register(PrimitiveCodec.real), ..._register(PrimitiveCodec.boolean), ..._register(PrimitiveCodec.string), ..._register(const DateTimeCodec()), }; static Map> _register(ValueCodec codec) => { T: codec, // Reifies the nullable type T so it can be used as a key in the _primitives map _typeOf(): codec, }; static Type _typeOf() => T; static ValueCodec forType(Type runtimeType) { final codec = _primitives[runtimeType]; if (codec == null) { throw StateError('No primitive codec for $runtimeType. Provide an explicit codec when defining the key.'); } return codec as ValueCodec; } } final class EnumCodec extends ValueCodec { final List values; const EnumCodec(this.values); @override String encode(T value) => value.name; @override T decode(String raw) => values.firstWhere((v) => v.name == raw); } final class DateTimeCodec extends ValueCodec { const DateTimeCodec(); @override String encode(DateTime value) => value.toIso8601String(); @override DateTime decode(String raw) => DateTime.parse(raw); } final class SemVerCodec extends ValueCodec { const SemVerCodec(); @override String encode(SemVer value) => value.toString(); @override SemVer decode(String raw) => SemVer.fromString(raw); } final class MapCodec extends ValueCodec> { final ValueCodec _keyCodec; final ValueCodec _valueCodec; const MapCodec(this._keyCodec, this._valueCodec); @override String encode(Map value) { final entries = {}; value.forEach((k, v) => entries[_keyCodec.encode(k)] = _valueCodec.encode(v)); return jsonEncode(entries); } @override Map decode(String raw) { try { final decoded = jsonDecode(raw); if (decoded is! Map) { return {}; } final result = {}; for (final entry in decoded.entries) { final rawKey = entry.key; final rawValue = entry.value; if (rawKey is! String || rawValue is! String) { continue; } final k = _keyCodec.decode(rawKey); final v = _valueCodec.decode(rawValue); result[k] = v; } return result; } on FormatException { return {}; } } } final class ListCodec extends ValueCodec> { final ValueCodec _elementCodec; const ListCodec(this._elementCodec); @override String encode(List value) => jsonEncode(value.map(_elementCodec.encode).toList()); @override List decode(String raw) { try { final decoded = jsonDecode(raw); if (decoded is! List) { return []; } final result = []; for (final item in decoded) { if (item is! String) { return []; } final element = _elementCodec.decode(item); result.add(element); } return result; } on FormatException { return []; } } } final class PrimitiveCodec extends ValueCodec { final T Function(String) _parse; const PrimitiveCodec._(this._parse); @override String encode(T value) => value.toString(); @override T decode(String raw) => _parse(raw); static const integer = PrimitiveCodec._(int.parse); static const real = PrimitiveCodec._(double.parse); static const boolean = PrimitiveCodec._(bool.parse); static const string = PrimitiveCodec._(_identity); static String _identity(String s) => s; }