package dev.privacyllc.period.core.notifications

import dev.privacyllc.period.core.datastore.NotificationPrivacy

/**
 * The six notification types §29 gives independent toggles to.
 *
 * Named for what they are *for* rather than when they fire, because the timing
 * is `ReminderRules`' problem and the wording is this file's.
 */
enum class ReminderKind {
    PERIOD_APPROACHING,
    PERIOD_EXPECTED_TODAY,
    DID_IT_START,
    PERIOD_END_CHECK_IN,
    FERTILE_WINDOW_APPROACHING,
    OVULATION_ESTIMATED,
}

/** What Android does with the notification on a locked screen. */
enum class LockScreenVisibility {
    /** Shown in full. Only ever used for [NotificationPrivacy.DIRECT]. */
    PUBLIC,

    /** The public version is shown instead, and it must be supplied. */
    PRIVATE,
}

/**
 * One notification, in two versions.
 *
 * The split is the whole feature. [publicTitle] and [publicBody] are what a
 * person standing next to the phone can read; [privateTitle] and [privateBody]
 * are what the owner sees once it is unlocked.
 *
 * Both are always produced, even in [NotificationPrivacy.DIRECT] where they are
 * identical — because a nullable public version is one forgotten branch away
 * from Android falling back to showing the private text, which is the exact
 * failure this type exists to prevent.
 */
data class NotificationText(
    val publicTitle: String,
    val publicBody: String?,
    val privateTitle: String,
    val privateBody: String,
    val visibility: LockScreenVisibility,
)

/**
 * §28's three modes, as a pure function.
 *
 * Pure on purpose. This is the one surface in the app whose mistakes are visible
 * to somebody who is not the user, and a pure function is the only version of it
 * that can be exhaustively tested without an emulator — so the test asserts, for
 * every kind and both private modes, that no health word survives to the lock
 * screen.
 *
 * ## What the words are chosen against
 *
 * Not just "does it name a period". A notification that says *"Checking in about
 * your cycle"* leaks in a shoulder-glance exactly as much as one that says
 * period, and one that arrives every 28 days leaks by its rhythm no matter what
 * it says. Discreet copy is therefore deliberately dull and deliberately
 * unspecific — "Quick check-in" could be anything, which is the point.
 */
object NotificationCopy {

    /** Words that must never reach a lock screen except in [NotificationPrivacy.DIRECT]. */
    val SENSITIVE_WORDS = listOf(
        "period", "cycle", "fertile", "fertility", "ovulation", "ovulating",
        "menstrual", "bleeding", "flow", "pms",
    )

    fun textFor(
        kind: ReminderKind,
        privacy: NotificationPrivacy,
        daysUntil: Int? = null,
    ): NotificationText {
        val private = privateTextFor(kind, daysUntil)

        return when (privacy) {
            // §28's default. Something is happening; nothing says what.
            NotificationPrivacy.DISCREET -> NotificationText(
                publicTitle = discreetTitle(kind),
                publicBody = discreetBody(kind),
                privateTitle = private.first,
                privateBody = private.second,
                visibility = LockScreenVisibility.PRIVATE,
            )

            // §28: no health information. Not "vague health information" — none.
            // A body is omitted entirely rather than made bland, because a body
            // is a second chance to leak and this mode exists for people who
            // cannot afford one.
            NotificationPrivacy.MAXIMUM_PRIVACY -> NotificationText(
                publicTitle = "Reminder",
                publicBody = null,
                privateTitle = private.first,
                privateBody = private.second,
                visibility = LockScreenVisibility.PRIVATE,
            )

            // The only mode where the two versions are the same, and the only
            // one the user has to choose deliberately.
            NotificationPrivacy.DIRECT -> NotificationText(
                publicTitle = private.first,
                publicBody = private.second,
                privateTitle = private.first,
                privateBody = private.second,
                visibility = LockScreenVisibility.PUBLIC,
            )
        }
    }

    /**
     * Action labels, which are visible text on a lock screen too.
     *
     * §31 is explicit about this and it is easy to miss: the notification body
     * can be perfectly discreet while a button underneath it says "Started my
     * period". These are chosen to mean nothing out of context.
     */
    fun actionLabels(kind: ReminderKind, privacy: NotificationPrivacy): List<String> =
        when (kind) {
            ReminderKind.DID_IT_START, ReminderKind.PERIOD_EXPECTED_TODAY ->
                if (privacy == NotificationPrivacy.DIRECT) listOf("Started", "Not yet")
                else listOf("Yes", "Not yet")

            ReminderKind.PERIOD_END_CHECK_IN ->
                if (privacy == NotificationPrivacy.DIRECT) listOf("Ended", "Still going")
                else listOf("Done", "Not yet")

            else -> emptyList()
        }

    private fun discreetTitle(kind: ReminderKind) = when (kind) {
        ReminderKind.PERIOD_APPROACHING -> "A heads-up"
        ReminderKind.PERIOD_EXPECTED_TODAY -> "Quick check-in"
        ReminderKind.DID_IT_START -> "Checking in"
        ReminderKind.PERIOD_END_CHECK_IN -> "Quick check-in"
        ReminderKind.FERTILE_WINDOW_APPROACHING -> "A heads-up"
        ReminderKind.OVULATION_ESTIMATED -> "A heads-up"
    }

    private fun discreetBody(kind: ReminderKind) = when (kind) {
        ReminderKind.PERIOD_APPROACHING -> "Something may be coming up later this week."
        ReminderKind.PERIOD_EXPECTED_TODAY -> "Something may be coming up."
        ReminderKind.DID_IT_START -> "Tap when you have a moment."
        ReminderKind.PERIOD_END_CHECK_IN -> "Tap when you have a moment."
        ReminderKind.FERTILE_WINDOW_APPROACHING -> "Something to note this week."
        ReminderKind.OVULATION_ESTIMATED -> "Something to note today."
    }

    /** Title and body for the unlocked device. This is where the app can be plain. */
    private fun privateTextFor(kind: ReminderKind, daysUntil: Int?): Pair<String, String> =
        when (kind) {
            ReminderKind.PERIOD_APPROACHING -> "Period approaching" to
                when {
                    daysUntil == null -> "Your period is likely soon."
                    daysUntil <= 1 -> "Your period is likely tomorrow."
                    else -> "Your period is likely in about $daysUntil days."
                }

            ReminderKind.PERIOD_EXPECTED_TODAY -> "Did your period start?" to
                "Your period may start today."

            ReminderKind.DID_IT_START -> "Did your period start?" to
                "It has been a couple of days since your forecast."

            ReminderKind.PERIOD_END_CHECK_IN -> "Is your period over?" to
                "Let us know so the estimate stays accurate."

            // "Estimated" is load-bearing in both of these — §17.
            ReminderKind.FERTILE_WINDOW_APPROACHING -> "Estimated fertile window" to
                when {
                    daysUntil == null -> "Your estimated fertile window is coming up."
                    daysUntil <= 1 -> "Your estimated fertile window starts tomorrow."
                    else -> "Your estimated fertile window starts in about $daysUntil days."
                }

            ReminderKind.OVULATION_ESTIMATED -> "Estimated ovulation" to
                "Today is your estimated ovulation day."
        }
}
