package dev.privacyllc.period.di

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStoreFile
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import dev.privacyllc.period.core.data.CycleData
import dev.privacyllc.period.core.data.CycleRepository
import dev.privacyllc.period.core.datastore.UserPreferencesRepository
import dev.privacyllc.period.core.notifications.ReminderScheduler
import dev.privacyllc.period.domain.prediction.PersonalPredictionEngine
import dev.privacyllc.period.domain.prediction.PredictionEngine
import java.time.Clock
import javax.inject.Singleton

/**
 * Where the app learns what a file path is, and the only place it does.
 *
 * Note what is absent: no `PeriodDatabase`, no DAO, no Room import anywhere in
 * this module or anywhere above it. `CycleData.repository` hands back a
 * repository and keeps the storage to itself — see
 * docs/architecture/README.md.
 */
@Module
@InstallIn(SingletonComponent::class)
object DataModule {

    /**
     * Singleton because Room and DataStore both are: two instances over one
     * file is a corruption bug that only shows up under concurrency.
     */
    @Provides
    @Singleton
    fun cycleRepository(
        @ApplicationContext context: Context,
        engine: PredictionEngine,
        clock: Clock,
    ): CycleRepository = CycleData.repository(context, engine, clock)

    @Provides
    @Singleton
    fun preferencesDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
        PreferenceDataStoreFactory.create {
            context.preferencesDataStoreFile("user_preferences")
        }

    @Provides
    @Singleton
    fun userPreferencesRepository(store: DataStore<Preferences>) = UserPreferencesRepository(store)

    /**
     * The engine, and the one line that decides which one the product ships.
     *
     * Everything else depends on [PredictionEngine] rather than an
     * implementation, which is what made this swap a single line and what makes
     * the next one a single line too.
     *
     * `BaselinePredictionEngine` stays in the tree. It stopped being the product
     * and became the control: `EngineComparisonTest` scores both over the §51
     * fixtures every build, so "the new engine is better" is a number rather
     * than an opinion. At the swap it was **mean absolute error 0.67 against
     * 1.00, and the window contained the actual start 9 times out of 9 against
     * 7** — the second mattering more, because a window that misses is a broken
     * promise rather than a tight forecast.
     */
    @Provides
    @Singleton
    fun predictionEngine(): PredictionEngine = PersonalPredictionEngine()

    /** Injected rather than read from the environment, so §50's date edge cases stay testable. */
    @Provides
    @Singleton
    fun clock(): Clock = Clock.systemDefaultZone()

    @Provides
    @Singleton
    fun reminderScheduler(@ApplicationContext context: Context): ReminderScheduler =
        ReminderScheduler(context)
}
