import 'package:flutter/foundation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../../core/constants.dart'; part 'payment_draft_provider.g.dart'; /// Session mode the customer is paying for. Mirrors backend `SessionMode` /// (chat | call). Phase 4 introduces `call` as a future option — pricing config /// supplies the call tier list, but no functional voice feature is built yet. enum PaymentMode { chat('chat'), call('call'); final String value; const PaymentMode(this.value); static PaymentMode fromString(String? v) => values.firstWhere((e) => e.value == v, orElse: () => PaymentMode.chat); } /// Draft state shared across the multi-screen payment flow: /// discount-paywall / method-pick / duration-pick / method / waiting / expired. /// /// The state is deliberately minimal — the *source of truth* for amount and /// duration is always the backend (server-validated tier or first-session /// discount). The draft only carries the customer's in-flight intent across /// the screen graph. class PaymentDraft { final PaymentMode mode; final String? durationId; final int? durationMinutes; final int? priceIDR; final String? paymentId; final bool isFirstSessionDiscount; /// When set, this payment is for a "Curhat lagi" (returning-targeted) flow: /// downstream of payment confirm, `searching_screen` will fire the targeted /// chat request against this specific mitra rather than the general blast. /// Set by `BestieHistoryListScreen` BEFORE pushing `/payment/entry`. final String? targetedMitraId; /// Optional display name for the targeted mitra — surfaced on the targeted /// waiting overlay ("lagi nungguin {name}") and any future returning-flow UI /// that wants to greet the customer with the right bestie. final String? targetedMitraName; /// Topic-sensitivity choice made before entering the payment flow. Carried /// through to the eventual chat-request API call. Defaults to `regular`. final TopicSensitivity topicSensitivity; const PaymentDraft({ this.mode = PaymentMode.chat, this.durationId, this.durationMinutes, this.priceIDR, this.paymentId, this.isFirstSessionDiscount = false, this.targetedMitraId, this.targetedMitraName, this.topicSensitivity = TopicSensitivity.regular, }); PaymentDraft copyWith({ PaymentMode? mode, String? durationId, int? durationMinutes, int? priceIDR, String? paymentId, bool? isFirstSessionDiscount, String? targetedMitraId, String? targetedMitraName, TopicSensitivity? topicSensitivity, }) { return PaymentDraft( mode: mode ?? this.mode, durationId: durationId ?? this.durationId, durationMinutes: durationMinutes ?? this.durationMinutes, priceIDR: priceIDR ?? this.priceIDR, paymentId: paymentId ?? this.paymentId, isFirstSessionDiscount: isFirstSessionDiscount ?? this.isFirstSessionDiscount, targetedMitraId: targetedMitraId ?? this.targetedMitraId, targetedMitraName: targetedMitraName ?? this.targetedMitraName, topicSensitivity: topicSensitivity ?? this.topicSensitivity, ); } } @Riverpod(keepAlive: true) class PaymentDraftNotifier extends _$PaymentDraftNotifier { @override PaymentDraft build() => const PaymentDraft(); void setMode(PaymentMode mode) { // Switching mode resets the previously-picked tier — chat and call // tier lists are independent. state = state.copyWith( mode: mode, durationId: null, durationMinutes: null, priceIDR: null, ); } void setTier({ required String durationId, required int durationMinutes, required int priceIDR, }) { state = state.copyWith( durationId: durationId, durationMinutes: durationMinutes, priceIDR: priceIDR, ); } void setDiscountPlan({ required int durationMinutes, required int priceIDR, }) { state = state.copyWith( mode: PaymentMode.chat, durationMinutes: durationMinutes, priceIDR: priceIDR, isFirstSessionDiscount: true, ); } void setPaymentId(String paymentId) { state = state.copyWith(paymentId: paymentId); } /// Mark this draft as a targeted "Curhat lagi" flow against a specific /// mitra. Must be called BEFORE pushing `/payment/entry` from the /// bestie-history list — the entry screen calls [resetExceptTarget] to /// clear stale tier/payment state while preserving the targeting set here. void setTargetedMitra({required String mitraId, String? mitraName}) { state = state.copyWith( targetedMitraId: mitraId, targetedMitraName: mitraName, ); } /// Set the topic-sensitivity choice for the upcoming chat request. void setTopicSensitivity(TopicSensitivity topicSensitivity) { state = state.copyWith(topicSensitivity: topicSensitivity); } /// Full reset — clears EVERYTHING including targeted-mitra intent. /// Use this when starting a fresh BLAST flow (e.g. "bestie baru" branch or /// the no-history Home CTA). If you want to preserve a targeted-mitra /// selection made just before entering the payment flow (set via /// [setTargetedMitra]), use [resetExceptTarget] instead. void reset() { debugPrint('[PaymentDraft] reset() — clearing entire draft including targeted-mitra intent'); state = const PaymentDraft(); } /// Reset everything EXCEPT the targeted-mitra fields. Used by the payment /// entry screen so a fresh dive into the multi-screen flow clears any stale /// tier/payment state while preserving the just-picked targeted mitra. If /// the draft has no targeted mitra set, this behaves identically to /// [reset]. void resetExceptTarget() { debugPrint( '[PaymentDraft] resetExceptTarget() — preserving targetedMitraId=${state.targetedMitraId}', ); state = PaymentDraft( targetedMitraId: state.targetedMitraId, targetedMitraName: state.targetedMitraName, topicSensitivity: state.topicSensitivity, ); } }