import 'dart:convert'; /// 성별 (호칭을 정할 때만 쓴다). 모르면 [unknown]. enum Gender { male, female, unknown; static Gender parse(String? s) => Gender.values.where((g) => g.name == s).firstOrNull ?? Gender.unknown; } /// 가계도의 한 사람. 출생 연도를 알면 형·동생, 큰아버지·작은아버지를 가른다. class KinPerson { const KinPerson(this.id, {this.gender = Gender.unknown, this.birthYear}); final String id; final Gender gender; final int? birthYear; KinPerson copyWith({Gender? gender, int? birthYear}) => KinPerson(id, gender: gender ?? this.gender, birthYear: birthYear ?? this.birthYear); Map toJson() => {'id': id, 'g': gender.name, if (birthYear != null) 'y': birthYear}; factory KinPerson.fromJson(Map j) => KinPerson(j['id'] as String, gender: Gender.parse(j['g'] as String?), birthYear: j['y'] as int?); } /// 가계도: 사람, 부모-자식, 배우자만 안다. 나는 [meId] 로 들어간다. 계산은 relation.dart 가 한다. class KinGraph { KinGraph(); static const meId = 'me'; final Map _people = {}; final Map> _parents = {}; // 자식 → 부모들 final Map> _children = {}; // 부모 → 자식들 final Map> _spouses = {}; Iterable get people => _people.values; KinPerson? person(String id) => _people[id]; bool has(String id) => _people.containsKey(id); Set parentsOf(String id) => _parents[id] ?? const {}; Set childrenOf(String id) => _children[id] ?? const {}; Set spousesOf(String id) => _spouses[id] ?? const {}; void put(KinPerson p) => _people[p.id] = p; /// [parent] 가 [child] 의 부모. 자기 자신이나 이미 후손인 사람을 부모로 두는 순환은 거절(false). bool addParent(String child, String parent) { if (child == parent || !has(child) || !has(parent)) return false; if (_isAncestor(child, parent)) return false; // child 가 parent 의 조상이면 순환 if (parentsOf(child).length >= 2 && !parentsOf(child).contains(parent)) return false; // 부모는 최대 둘 (_parents[child] ??= {}).add(parent); (_children[parent] ??= {}).add(child); return true; } bool addSpouse(String a, String b) { if (a == b || !has(a) || !has(b)) return false; (_spouses[a] ??= {}).add(b); (_spouses[b] ??= {}).add(a); return true; } void removePerson(String id) { for (final p in parentsOf(id).toList()) { _children[p]?.remove(id); } for (final c in childrenOf(id).toList()) { _parents[c]?.remove(id); } for (final s in spousesOf(id).toList()) { _spouses[s]?.remove(id); } _parents.remove(id); _children.remove(id); _spouses.remove(id); _people.remove(id); } void removeParent(String child, String parent) { _parents[child]?.remove(parent); _children[parent]?.remove(child); } void removeSpouse(String a, String b) { _spouses[a]?.remove(b); _spouses[b]?.remove(a); } bool _isAncestor(String maybeAncestor, String of) { final seen = {}; final stack = [of]; while (stack.isNotEmpty) { final x = stack.removeLast(); if (!seen.add(x)) continue; for (final p in parentsOf(x)) { if (p == maybeAncestor) return true; stack.add(p); } } return false; } String toJson() => jsonEncode({ 'people': [for (final p in _people.values) p.toJson()], 'parent': [ for (final e in _parents.entries) for (final p in e.value) [e.key, p], ], 'spouse': [ for (final e in _spouses.entries) for (final s in e.value) if (e.key.compareTo(s) < 0) [e.key, s], ], }); factory KinGraph.fromJson(String? text) { final g = KinGraph(); if (text == null || text.isEmpty) return g; try { final j = (jsonDecode(text) as Map).cast(); for (final p in (j['people'] as List).cast()) { g.put(KinPerson.fromJson(p.cast())); } for (final e in (j['parent'] as List).cast()) { g.addParent(e[0] as String, e[1] as String); } for (final e in (j['spouse'] as List).cast()) { g.addSpouse(e[0] as String, e[1] as String); } } catch (_) { return KinGraph(); // 깨진 데이터는 빈 가계도 } return g; } }