import 'package:shared_preferences/shared_preferences.dart'; import 'package:tm_app/core/constants/pref_keys.dart'; import 'package:tm_app/core/utils/logger.dart'; import 'package:tm_app/core/utils/result.dart'; import 'package:tm_app/data/services/company_ai_service.dart'; import 'package:tm_app/data/services/model/onboarding_response/onboarding_response.dart'; import 'package:tm_app/data/services/model/recommend_response/recommend_response.dart'; /// Outcome of [CompanyAiRepository.maybeStartOnboarding]. enum AiOnboardingOutcome { /// Onboarding was started against the AI service. started, /// Skipped: the company has no documents and no website yet. skippedNoData, /// Skipped: this profile version was already onboarded. skippedUnchanged, /// The onboarding request failed (network/server). Safe to retry later. failed, } /// Coordinates AI onboarding and recommendation calls. /// /// Onboarding is asynchronous on the AI side and only needs to run when the /// company's documents or website change, so this repository fingerprints that /// data in [SharedPreferences] and skips redundant calls — mirroring the /// `onCompanyProfileReady` guidance in the mobile integration guide. class CompanyAiRepository { CompanyAiRepository({ required CompanyAiService companyAiService, required SharedPreferences prefs, }) : _companyAiService = companyAiService, _prefs = prefs; final CompanyAiService _companyAiService; final SharedPreferences _prefs; /// Starts onboarding only when the company has data to index and its /// fingerprint differs from the last successful run. Future maybeStartOnboarding({ required List documentFileIds, required String? website, }) async { final hasWebsite = website != null && website.trim().isNotEmpty; if (documentFileIds.isEmpty && !hasWebsite) { return AiOnboardingOutcome.skippedNoData; } final fingerprint = _fingerprint(documentFileIds, website); if (fingerprint == _prefs.getString(PrefKeys.onboardingFingerprint)) { return AiOnboardingOutcome.skippedUnchanged; } final result = await _companyAiService.startOnboarding(); switch (result) { case Ok(): await _prefs.setString(PrefKeys.onboardingFingerprint, fingerprint); await _prefs.setInt( PrefKeys.onboardingStartedAt, DateTime.now().millisecondsSinceEpoch, ); AppLogger().info('✅ AI onboarding started for fingerprint $fingerprint'); return AiOnboardingOutcome.started; case Error(): // Don't persist the fingerprint so the next profile load retries. AppLogger().logWarning('⚠️ AI onboarding failed: ${result.error}'); return AiOnboardingOutcome.failed; } } Future> getRecommendations() { return _companyAiService.getRecommendations(); } /// Epoch milliseconds of the last started onboarding, or null if none. int? lastOnboardingStartedAt() => _prefs.getInt(PrefKeys.onboardingStartedAt); /// Stable, order-independent fingerprint of the data the AI service indexes. /// Stored verbatim (it is not sensitive) so it compares reliably across app /// launches, unlike [String.hashCode]. String _fingerprint(List documentFileIds, String? website) { final ids = [...documentFileIds]..sort(); return '${ids.join(',')}|${website?.trim() ?? ''}'; } }