package dev.privacyllc.period.core.database.entity

import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import java.time.Instant
import java.time.LocalDate

/**
 * A confirmed period.
 *
 * `startDate` is UNIQUE. Two records claiming the same start are not two
 * periods, they are one period entered twice — and a duplicate start produces a
 * zero-length cycle, which is the shape most likely to make a forecast
 * nonsensical rather than merely wrong. `domain/cycle` already de-duplicates
 * defensively; this stops the row existing in the first place.
 */
@Entity(
    tableName = "period_records",
    indices = [Index(value = ["startDate"], unique = true)],
)
data class PeriodRecordEntity(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,
    val startDate: LocalDate,
    val endDate: LocalDate?,
    val createdAt: Instant,
    val updatedAt: Instant,
    /** Stored as the enum name. Edits are recorded, never silent — §10. */
    val source: String,
    val isConfirmed: Boolean,
)

/**
 * Spotting, in its own table.
 *
 * Separate storage is the structural half of the rule in PRODUCT_PLAN.md §25:
 * spotting must never start or reset a cycle. A `type` column on
 * `period_records` would have made that a query nobody remembers to filter.
 */
@Entity(
    tableName = "spotting_records",
    indices = [Index(value = ["date"], unique = true)],
)
data class SpottingRecordEntity(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,
    val date: LocalDate,
    val createdAt: Instant,
)

/**
 * A forecast, snapshotted before the outcome was known.
 *
 * Never updated in place except to record the outcome (`actualStartDate`,
 * `absoluteErrorDays`) once it is known. That is the entire basis of the
 * accuracy feature in §16 — a prediction rewritten after the fact can only ever
 * report that the app was right.
 */
@Entity(
    tableName = "prediction_records",
    indices = [Index(value = ["generatedAt"]), Index(value = ["actualStartDate"])],
)
data class PredictionRecordEntity(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,
    val generatedAt: Instant,
    val basedOnLastConfirmedPeriodId: Long?,
    val predictedStartDate: LocalDate,
    val predictedWindowStart: LocalDate,
    val predictedWindowEnd: LocalDate,
    val estimatedOvulationDate: LocalDate?,
    val fertileWindowStart: LocalDate?,
    val fertileWindowEnd: LocalDate?,
    val confidenceScore: Double,
    val confidenceLabel: String,
    /** Which engine produced it. An accuracy comparison across versions is meaningless without this. */
    val modelVersion: String,
    val actualStartDate: LocalDate?,
    val absoluteErrorDays: Int?,
)

/**
 * The user said the period had not started by [date].
 *
 * A censoring observation — §13. Stored rather than applied-and-forgotten,
 * because the next recalculation has to condition on every one of them, and
 * because "we asked and she said not yet" is a fact about the history.
 */
@Entity(
    tableName = "not_yet_observations",
    indices = [Index(value = ["date"], unique = true)],
)
data class NotYetObservationEntity(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,
    val date: LocalDate,
    val predictionId: Long?,
    val createdAt: Instant,
)
