package com.jttools.datacontrol.remote import android.content.Context import com.google.firebase.FirebaseApp import com.google.firebase.FirebaseOptions import com.google.firebase.auth.FirebaseAuth import com.google.firebase.firestore.FirebaseFirestore import com.jttools.datacontrol.BuildConfig import com.jttools.datacontrol.log.AppLog import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.tasks.await import javax.inject.Inject import javax.inject.Singleton /** État du lien Firebase : [generation] change à chaque (ré)initialisation, pour relancer les écoutes. */ data class GatewayState(val generation: Int = 0, val enabled: Boolean = false, val projectId: String? = null) /** * Accès à Firebase (Auth anonyme + Firestore). La configuration se saisit dans l'application * (Réglages → Contrôle parental) et peut changer à tout moment ; à défaut, on retombe sur * `firebase.properties` (compilation). Sans configuration, [enabled] est faux et tout reste inerte. */ @Singleton class FirebaseGateway @Inject constructor( @ApplicationContext private val context: Context ) { private val _state = MutableStateFlow(GatewayState()) val state: StateFlow = _state.asStateFlow() val enabled: Boolean get() = _state.value.enabled @Volatile private var app: FirebaseApp? = null private var currentKey: String? = null private fun buildConfigFallback(): FirebaseConfig? = if (BuildConfig.FIREBASE_API_KEY.isNotBlank() && BuildConfig.FIREBASE_APP_ID.isNotBlank() && BuildConfig.FIREBASE_PROJECT_ID.isNotBlank() ) FirebaseConfig(BuildConfig.FIREBASE_API_KEY, BuildConfig.FIREBASE_APP_ID, BuildConfig.FIREBASE_PROJECT_ID) else null /** Applique la configuration saisie [saved] (ou, à défaut, celle de compilation). Sans effet si elle n'a pas changé. */ @Synchronized fun apply(saved: FirebaseConfig?) { val cfg = saved ?: buildConfigFallback() val key = cfg?.let { "${it.apiKey}|${it.appId}|${it.projectId}" } if (key == currentKey && (key == null || app != null)) return app?.let { runCatching { it.delete() } } app = null currentKey = key if (cfg != null) { app = runCatching { FirebaseApp.initializeApp( context, FirebaseOptions.Builder() .setApiKey(cfg.apiKey) .setApplicationId(cfg.appId) .setProjectId(cfg.projectId) .build() ) }.onFailure { AppLog.e("Firebase", "Initialisation impossible", it) }.getOrNull() } val ok = app != null AppLog.i("Firebase", if (ok) "Configuré (projet ${cfg?.projectId})" else "Non configuré") _state.value = GatewayState(_state.value.generation + 1, ok, cfg?.projectId?.takeIf { ok }) } val db: FirebaseFirestore? get() = app?.let { FirebaseFirestore.getInstance(it) } private val auth: FirebaseAuth? get() = app?.let { FirebaseAuth.getInstance(it) } /** Identifiant de l'appareil côté Firebase (connexion anonyme, persistante). Null si indisponible. */ suspend fun ensureSignedIn(): String? { val a = auth ?: return null a.currentUser?.let { return it.uid } return runCatching { a.signInAnonymously().await().user?.uid } .onFailure { AppLog.w("Firebase", "Connexion anonyme impossible : ${it.message}") } .getOrNull() } }