import 'package:flutter_bloc/flutter_bloc.dart'; import '../../data/models/teacher_models.dart'; import '../../data/repositories/teacher_repository.dart'; /** * ============================================================================== * SAQEL TEACHER STUDIO - MONETIZATION CUBIT * ============================================================================== * * إدارة المحفظة المالية والتسييل وسحوبات كليك (CliQ Payouts): * - حساب عوائد النموذج المزدوج (1,420 مجاني مدعوم مقابل 380 مدفوع خارجي) * - تقسيم الحصص: 55% للمعلم / 15% لمديرية الثقافة / 30% للمنصة * - إرسال واعتماد طلبات السحب عبر كليك. */ class TeacherMonetizationState { final double institutionalStudents; final double studentSubscribers; final double pricePerCourse; final double grossRevenue; final double teacherShare; final double directorateShare; final double platformShare; final double availableBalance; final double totalWithdrawn; final String cliqAlias; final List courses; final List payoutRequests; final bool isSubmittingPayout; final bool isLoading; final bool financialUnavailable; final String financialMessage; const TeacherMonetizationState({ required this.institutionalStudents, required this.studentSubscribers, required this.pricePerCourse, required this.grossRevenue, required this.teacherShare, required this.directorateShare, required this.platformShare, required this.availableBalance, required this.totalWithdrawn, required this.cliqAlias, required this.courses, required this.payoutRequests, required this.isSubmittingPayout, this.isLoading = false, this.financialUnavailable = false, this.financialMessage = '', }); TeacherMonetizationState copyWith({ double? institutionalStudents, double? studentSubscribers, double? pricePerCourse, double? grossRevenue, double? teacherShare, double? directorateShare, double? platformShare, double? availableBalance, double? totalWithdrawn, String? cliqAlias, List? courses, List? payoutRequests, bool? isSubmittingPayout, bool? isLoading, bool? financialUnavailable, String? financialMessage, }) { return TeacherMonetizationState( institutionalStudents: institutionalStudents ?? this.institutionalStudents, studentSubscribers: studentSubscribers ?? this.studentSubscribers, pricePerCourse: pricePerCourse ?? this.pricePerCourse, grossRevenue: grossRevenue ?? this.grossRevenue, teacherShare: teacherShare ?? this.teacherShare, directorateShare: directorateShare ?? this.directorateShare, platformShare: platformShare ?? this.platformShare, availableBalance: availableBalance ?? this.availableBalance, totalWithdrawn: totalWithdrawn ?? this.totalWithdrawn, cliqAlias: cliqAlias ?? this.cliqAlias, courses: courses ?? this.courses, payoutRequests: payoutRequests ?? this.payoutRequests, isSubmittingPayout: isSubmittingPayout ?? this.isSubmittingPayout, isLoading: isLoading ?? this.isLoading, financialUnavailable: financialUnavailable ?? this.financialUnavailable, financialMessage: financialMessage ?? this.financialMessage, ); } } class TeacherMonetizationCubit extends Cubit { final TeacherRepository repository; TeacherMonetizationCubit({required this.repository}) : super(const TeacherMonetizationState( institutionalStudents: 0, studentSubscribers: 0, pricePerCourse: 20.0, grossRevenue: 0.0, teacherShare: 0.0, directorateShare: 0.0, platformShare: 0.0, availableBalance: 0.0, totalWithdrawn: 0.0, cliqAlias: '', courses: [], payoutRequests: [], isSubmittingPayout: false, isLoading: false, )); Future loadMonetization() async { emit(state.copyWith(isLoading: true)); try { final data = await repository.getMonetizationDashboard(); final courses = await repository.getPublishedCourses(); final audience = data['audience_breakdown'] as Map? ?? {}; final inst = audience['institutional_students'] as Map? ?? {}; final mkt = audience['marketplace_students'] as Map? ?? {}; final wallet = data['wallet'] as Map? ?? {}; final unavailable = data['financial_status'] == 'unavailable'; final instCount = (inst['count'] as num?)?.toDouble() ?? 0.0; final paidCount = (mkt['count'] as num?)?.toDouble() ?? 0.0; final split = data['revenue_split'] as Map? ?? {}; // Never infer a wallet from subscriber counts, course prices, or old // percentages. The backend returns null until a provider-backed ledger // and eligible watch-minute policy are live. final gross = (data['gross_revenue_jod'] as num?)?.toDouble() ?? 0.0; final tShare = (split['teacher_amount_jod'] as num?)?.toDouble() ?? 0.0; final dShare = (split['directorate_amount_jod'] as num?)?.toDouble() ?? 0.0; final pShare = (split['platform_amount_jod'] as num?)?.toDouble() ?? 0.0; final avail = (wallet['available_balance_jod'] as num?)?.toDouble() ?? 0.0; final withdrawn = (wallet['total_withdrawn_jod'] as num?)?.toDouble() ?? 0.0; final alias = wallet['cliq_payout_alias']?.toString() ?? ''; List payouts = []; if (wallet['recent_payouts'] is List) { payouts = (wallet['recent_payouts'] as List).map((p) { return TeacherPayoutRequestModel( cliqAlias: p['teacher_cliq_alias']?.toString() ?? alias, amountJod: (p['amount_jod'] as num?)?.toDouble() ?? 0.0, requestedAt: p['created_at']?.toString() ?? 'اليوم', status: p['status']?.toString() ?? 'queued', ); }).toList(); } emit(state.copyWith( institutionalStudents: instCount, studentSubscribers: paidCount, grossRevenue: gross, teacherShare: tShare, directorateShare: dShare, platformShare: pShare, availableBalance: avail, totalWithdrawn: withdrawn, cliqAlias: alias, courses: courses, payoutRequests: payouts, financialUnavailable: unavailable, financialMessage: data['financial_message']?.toString() ?? '', isLoading: false, )); } catch (_) { emit(state.copyWith(isLoading: false)); } } void updateSubscribers(double subscribers) { // Presentation must not fabricate revenue from a manually changed count. emit(state.copyWith(studentSubscribers: subscribers)); } Future submitCliqPayout({ required String cliqAlias, required double amountJod, }) async { emit(state.copyWith(isSubmittingPayout: true)); try { final req = await repository.requestCliqPayout( cliqAlias: cliqAlias, amountJod: amountJod, ); final updatedList = List.from(state.payoutRequests) ..insert(0, req); emit(state.copyWith( payoutRequests: updatedList, isSubmittingPayout: false, )); } catch (_) { emit(state.copyWith(isSubmittingPayout: false)); } } }