import 'divination_params.dart'; import 'divination_result.dart'; class PointsBalanceData { const PointsBalanceData({ required this.balance, required this.frozenBalance, required this.availableBalance, required this.runCost, required this.canRun, }); factory PointsBalanceData.fromJson(Map json) { return PointsBalanceData( balance: _requiredInt(json, 'balance'), frozenBalance: _requiredInt(json, 'frozenBalance'), availableBalance: _requiredInt(json, 'availableBalance'), runCost: _requiredInt(json, 'runCost'), canRun: _requiredBool(json, 'canRun'), ); } final int balance; final int frozenBalance; final int availableBalance; final int runCost; final bool canRun; } class RunAcceptedData { const RunAcceptedData({ required this.taskId, required this.threadId, required this.runId, required this.created, }); factory RunAcceptedData.fromJson(Map json) { return RunAcceptedData( taskId: _requiredString(json, 'taskId'), threadId: _requiredString(json, 'threadId'), runId: _requiredString(json, 'runId'), created: _requiredBool(json, 'created'), ); } final String taskId; final String threadId; final String runId; final bool created; } class DivinationRunAggregate { const DivinationRunAggregate({ this.threadId, required this.derived, required this.signLevel, required this.conclusion, required this.focusPoints, required this.advice, required this.keywords, required this.answer, }); final DerivedDivinationData derived; final String? threadId; final String signLevel; final List conclusion; final List focusPoints; final List advice; final List keywords; final String answer; DivinationResultData toViewData(DivinationParams params) { return DivinationResultData( params: params, threadId: threadId, binaryCode: derived.binaryCode, changedBinaryCode: derived.changedBinaryCode, guaName: derived.guaName, targetGuaName: derived.targetGuaName, upperName: derived.upperName, lowerName: derived.lowerName, signType: signLevel, keywords: keywords.join('、'), focusPoints: focusPoints, conclusion: _asBullet(conclusion), analysis: answer, suggestion: _asBullet(advice), ganzhi: GanzhiData( yearGanZhi: derived.ganzhi.yearGanZhi, monthGanZhi: derived.ganzhi.monthGanZhi, dayGanZhi: derived.ganzhi.dayGanZhi, timeGanZhi: derived.ganzhi.timeGanZhi, yearKongWang: derived.ganzhi.yearKongWang, monthKongWang: derived.ganzhi.monthKongWang, dayKongWang: derived.ganzhi.dayKongWang, timeKongWang: derived.ganzhi.timeKongWang, yueJian: derived.ganzhi.yueJian, riChen: derived.ganzhi.riChen, yuePo: derived.ganzhi.yuePo, riChong: derived.ganzhi.riChong, ), wuXingStatus: derived.wuXingStatuses, yaoLines: derived.yaoInfoList .map((line) => line.toViewModel()) .toList(growable: false), targetYaoLines: derived.targetYaoInfoList .map((line) => line.toViewModel()) .toList(growable: false), ); } String _asBullet(List lines) { if (lines.isEmpty) { return ''; } return List.generate( lines.length, (i) => '${i + 1}. ${lines[i]}', growable: false, ).join('\n'); } } class DerivedDivinationData { const DerivedDivinationData({ required this.question, required this.questionType, required this.divinationMethod, required this.divinationTime, required this.binaryCode, required this.changedBinaryCode, required this.guaName, this.guaNameHant = '', required this.upperName, required this.lowerName, required this.targetGuaName, this.targetGuaNameHant = '', required this.worldPosition, required this.responsePosition, required this.hasChangingYao, required this.ganzhi, required this.wuXingStatuses, required this.yaoInfoList, required this.targetYaoInfoList, }); factory DerivedDivinationData.fromJson(Map json) { final wuXingRaw = _requiredMap(json, 'wuXingStatuses'); return DerivedDivinationData( question: _requiredString(json, 'question'), questionType: _requiredString(json, 'questionType'), divinationMethod: _requiredString(json, 'divinationMethod'), divinationTime: _requiredString(json, 'divinationTime'), binaryCode: _requiredString(json, 'binaryCode'), changedBinaryCode: _requiredString(json, 'changedBinaryCode'), guaName: _requiredString(json, 'guaName'), guaNameHant: json['guaNameHant'] as String? ?? '', upperName: _requiredString(json, 'upperName'), lowerName: _requiredString(json, 'lowerName'), targetGuaName: _requiredString(json, 'targetGuaName'), targetGuaNameHant: json['targetGuaNameHant'] as String? ?? '', worldPosition: _requiredInt(json, 'worldPosition'), responsePosition: _requiredInt(json, 'responsePosition'), hasChangingYao: _requiredBool(json, 'hasChangingYao'), ganzhi: GanzhiBackend.fromJson(_requiredMap(json, 'ganzhi')), wuXingStatuses: wuXingRaw.map( (key, value) => MapEntry(key, value.toString()), ), yaoInfoList: _parseYaoList(json['yaoInfoList']), targetYaoInfoList: _parseYaoList(json['targetYaoInfoList']), ); } final String question; final String questionType; final String divinationMethod; final String divinationTime; final String binaryCode; final String changedBinaryCode; final String guaName; final String guaNameHant; final String upperName; final String lowerName; final String targetGuaName; final String targetGuaNameHant; final int worldPosition; final int responsePosition; final bool hasChangingYao; final GanzhiBackend ganzhi; final Map wuXingStatuses; final List yaoInfoList; final List targetYaoInfoList; static List _parseYaoList(Object? raw) { final list = raw as List?; if (list == null) { throw const FormatException( 'Missing required list: yaoInfoList/targetYaoInfoList', ); } return list .map((item) { if (item is! Map) { throw const FormatException('Invalid yao line item'); } return YaoBackendLine.fromJson(item); }) .toList(growable: false); } } class GanzhiBackend { const GanzhiBackend({ required this.yearGanZhi, required this.monthGanZhi, required this.dayGanZhi, required this.timeGanZhi, required this.yearKongWang, required this.monthKongWang, required this.dayKongWang, required this.timeKongWang, required this.yueJian, required this.riChen, required this.yuePo, required this.riChong, }); factory GanzhiBackend.fromJson(Map json) { return GanzhiBackend( yearGanZhi: _requiredString(json, 'yearGanZhi'), monthGanZhi: _requiredString(json, 'monthGanZhi'), dayGanZhi: _requiredString(json, 'dayGanZhi'), timeGanZhi: _requiredString(json, 'timeGanZhi'), yearKongWang: _requiredString(json, 'yearKongWang'), monthKongWang: _requiredString(json, 'monthKongWang'), dayKongWang: _requiredString(json, 'dayKongWang'), timeKongWang: _requiredString(json, 'timeKongWang'), yueJian: _requiredString(json, 'yueJian'), riChen: _requiredString(json, 'riChen'), yuePo: _requiredString(json, 'yuePo'), riChong: _requiredString(json, 'riChong'), ); } final String yearGanZhi; final String monthGanZhi; final String dayGanZhi; final String timeGanZhi; final String yearKongWang; final String monthKongWang; final String dayKongWang; final String timeKongWang; final String yueJian; final String riChen; final String yuePo; final String riChong; } class YaoBackendLine { const YaoBackendLine({ required this.position, required this.spiritName, this.spiritNameHant = '', required this.relationName, this.relationNameHant = '', required this.tiganName, required this.elementName, required this.isYang, required this.isChanging, required this.specialMark, }); factory YaoBackendLine.fromJson(Map json) { return YaoBackendLine( position: _requiredInt(json, 'position'), spiritName: _requiredString(json, 'spiritName'), spiritNameHant: json['spiritNameHant'] as String? ?? '', relationName: _requiredString(json, 'relationName'), relationNameHant: json['relationNameHant'] as String? ?? '', tiganName: _requiredString(json, 'tiganName'), elementName: _requiredString(json, 'elementName'), isYang: _requiredBool(json, 'isYang'), isChanging: _requiredBool(json, 'isChanging'), specialMark: _requiredStringAllowEmpty(json, 'specialMark'), ); } final int position; final String spiritName; final String spiritNameHant; final String relationName; final String relationNameHant; final String tiganName; final String elementName; final bool isYang; final bool isChanging; final String specialMark; YaoLineData toViewModel() { final type = switch ((isYang, isChanging)) { (true, false) => YaoType.youngYang, (false, false) => YaoType.youngYin, (true, true) => YaoType.oldYang, (false, true) => YaoType.oldYin, }; return YaoLineData( index: position - 1, spirit: spiritName, relation: relationName, branch: tiganName, element: elementName, type: type, mark: specialMark, ); } } String _requiredStringAllowEmpty(Map json, String key) { if (!json.containsKey(key)) { throw FormatException('Invalid or missing field: $key'); } final value = json[key]; if (value is! String) { throw FormatException('Invalid or missing field: $key'); } return value; } String _requiredString(Map json, String key) { final value = json[key]; if (value is! String || value.isEmpty) { throw FormatException('Invalid or missing field: $key'); } return value; } int _requiredInt(Map json, String key) { final value = json[key]; if (value is! num) { throw FormatException('Invalid or missing field: $key'); } return value.toInt(); } bool _requiredBool(Map json, String key) { final value = json[key]; if (value is! bool) { throw FormatException('Invalid or missing field: $key'); } return value; } Map _requiredMap(Map json, String key) { final value = json[key]; if (value is! Map) { throw FormatException('Invalid or missing field: $key'); } return value; }