package dev.privacyllc.period.navigation

import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CalendarMonth
import androidx.compose.material.icons.filled.Insights
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.Today
import androidx.compose.material.icons.filled.Circle
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.NavHost
import androidx.navigation.NavDestination
import androidx.navigation.compose.composable
import androidx.navigation.compose.navigation
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.rememberNavController
import dev.privacyllc.period.R
import dev.privacyllc.period.designsystem.PeriodTheme
import dev.privacyllc.period.feature.calendar.CalendarScreen
import dev.privacyllc.period.feature.export.ExportHost
import dev.privacyllc.period.feature.export.ExportScreen
import dev.privacyllc.period.feature.insights.InsightsScreen
import dev.privacyllc.period.feature.lock.LockSettingsScreen
import dev.privacyllc.period.feature.onboarding.OnboardingScreen
import dev.privacyllc.period.feature.settings.NotificationSettingsScreen
import dev.privacyllc.period.feature.settings.SettingsScreen
import dev.privacyllc.period.feature.today.TodayScreen

/**
 * The four tabs from docs/planning/PRODUCT_PLAN.md §20.
 *
 * Four and only four. Every proposal to add a fifth is answered by
 * docs/planning/PROJECT_PLAN.md's "deliberately not" list.
 */
enum class PeriodDestination(
    val route: String,
    @param:StringRes val labelRes: Int,
    val icon: ImageVector,
) {
    TODAY("today", R.string.tab_today, Icons.Filled.Today),
    CALENDAR("calendar", R.string.tab_calendar, Icons.Filled.CalendarMonth),
    INSIGHTS("insights", R.string.tab_insights, Icons.Filled.Insights),
    SETTINGS("settings", R.string.tab_settings, Icons.Filled.Settings),
}

/**
 * The root: onboarding, then the app.
 *
 * The decision comes from `UserPreferences.onboardingCompleted` and from
 * nowhere else. A second source — "does any period exist?" — would look
 * equivalent and is not: somebody who deletes all their data has not asked to be
 * onboarded again, and somebody who abandoned onboarding halfway has.
 */
@Composable
fun PeriodRoot(viewModel: RootViewModel = hiltViewModel()) {
    val state by viewModel.state.collectAsStateWithLifecycle()

    // Notification actions no longer arrive here. They are parked in
    // AppLockController and applied by AppLockGate once the session is
    // unlocked, because tapping "Not yet" writes to the health record and that
    // button is reachable from the phone's own lock screen by anybody.

    when (state) {
        RootState.Loading -> Unit  // one frame; a spinner here flashes and reads as jank
        RootState.Onboarding -> OnboardingScreen(onFinished = viewModel::onboardingFinished)
        RootState.Ready -> PeriodApp()
    }
}

@Composable
fun PeriodApp() {
    val navController = rememberNavController()
    val backStackEntry by navController.currentBackStackEntryAsState()
    val currentDestination = backStackEntry?.destination

    val inSettings = currentDestination.isIn(PeriodDestination.SETTINGS)

    Scaffold(
        // The export result belongs above everything, including a sub-page's own
        // bar. It used to be a sibling emitted BEFORE the Scaffold — and
        // PeriodTheme's Surface is a Box, where later siblings draw on top of
        // earlier ones, so an opaque Scaffold was painted over it. It has
        // almost certainly never been seen; the export's on-device checks were
        // never run either.
        topBar = { ExportHost() },
        bottomBar = {
            NavigationBar {
                PeriodDestination.entries.forEach { destination ->
                    val selected = currentDestination.isIn(destination)
                    NavigationBarItem(
                        selected = selected,
                        onClick = {
                            if (selected) {
                                // Re-tapping the tab you are already on returns
                                // it to its root. For three tabs that is a
                                // no-op; for Settings it is the way out of a
                                // child, and the thing a user reaches for when
                                // a screen has no other exit.
                                navController.popBackStack(destination.rootRoute, inclusive = false)
                            } else {
                                navController.navigate(destination.route) {
                                    popUpTo(navController.graph.findStartDestination().id) {
                                        // Today, Calendar and Insights keep their
                                        // place across a tab switch. Settings must
                                        // not: its children are one-shot tasks, and
                                        // a saved stack is restored on the way back
                                        // in — which is how tapping Settings from
                                        // inside App lock landed on App lock again,
                                        // for the rest of the process. Nothing is
                                        // saved, so there is nothing to restore.
                                        saveState = !inSettings
                                    }
                                    launchSingleTop = true
                                    restoreState = true
                                }
                            }
                        },
                        icon = { Icon(destination.icon, contentDescription = null) },
                        // maxLines and ellipsis, because at the largest
                        // accessibility font scale these labels do not fit and
                        // Compose's default is to wrap them mid-word: the tab
                        // bar reads "Calenda / r" and "Setting / s". Found by
                        // setting font_scale to 2.0 on a device, which nothing
                        // in the build does. Ellipsis degrades to "Calend…",
                        // which is still a word somebody can recognise, and the
                        // icon above it carries the meaning anyway.
                        label = {
                            Text(
                                stringResource(destination.labelRes),
                                maxLines = 1,
                                overflow = TextOverflow.Ellipsis,
                            )
                        },
                    )
                }
            }
        },
    ) { innerPadding ->
        NavHost(
            navController = navController,
            startDestination = PeriodDestination.TODAY.route,
            modifier = Modifier.padding(innerPadding),
        ) {
            composable(PeriodDestination.TODAY.route) { TodayScreen() }
            composable(PeriodDestination.CALENDAR.route) { CalendarScreen() }
            composable(PeriodDestination.INSIGHTS.route) { InsightsScreen() }

            // Settings is a nested graph, not a single destination.
            //
            // Two things fall out of that, and both were broken without it. The
            // tab's route now names the graph, so every child is inside the
            // tab's hierarchy and the tab renders as selected while you are on
            // App lock — before, no tab looked selected on any settings child,
            // and the user tapped an unselected Settings tab to escape. And the
            // graph gives the re-tap above a root to pop back to.
            navigation(route = PeriodDestination.SETTINGS.route, startDestination = SETTINGS_ROOT) {
                composable(SETTINGS_ROOT) {
                    SettingsScreen(
                        onOpenNotifications = { navController.navigate(SETTINGS_NOTIFICATIONS) },
                        onOpenAppLock = { navController.navigate(SETTINGS_LOCK) },
                        onOpenExport = { navController.navigate(SETTINGS_EXPORT) },
                    )
                }
                composable(SETTINGS_NOTIFICATIONS) {
                    NotificationSettingsScreen(onNavigateBack = { navController.popBackStack() })
                }
                composable(SETTINGS_LOCK) {
                    LockSettingsScreen(onNavigateBack = { navController.popBackStack() })
                }
                composable(SETTINGS_EXPORT) {
                    ExportScreen(onNavigateBack = { navController.popBackStack() })
                }
            }
        }
    }
}

/**
 * Skeleton only. Each of these is replaced by its real screen in Batch 03, and
 * saying "not built yet" on the screen is deliberate — a convincing mock is how
 * a screen comes to be believed finished.
 */
@Composable
private fun PlaceholderScreen(title: String) {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Icon(
            Icons.Filled.Circle,
            contentDescription = null,
            tint = MaterialTheme.colorScheme.primaryContainer,
        )
        Text(
            text = title,
            style = MaterialTheme.typography.headlineMedium,
            color = MaterialTheme.colorScheme.onBackground,
        )
        Text(
            text = "Not built yet",
            style = MaterialTheme.typography.bodyMedium,
            color = MaterialTheme.colorScheme.onSurfaceVariant,
            textAlign = TextAlign.Center,
        )
    }
}

@Preview(showBackground = true)
@Composable
private fun PlaceholderPreview() {
    PeriodTheme { PlaceholderScreen("Calendar") }
}

/**
 * The export destination is written here, not on the export screen.
 *
 * Everything inside `PeriodApp` composes only after the gate has unlocked, so
 * this is the earliest point at which writing is allowed — and the export
 * screen itself may be long gone, because returning from the save dialog can
 * re-lock the app and the user unlocks onto Today.
 */
/**
 * The §36 tree itself. Distinct from the Settings *graph*, whose route is the
 * tab's — one names where the tab points, the other what it shows first.
 */
private const val SETTINGS_ROOT = "settings/root"

/** Reminder settings, a child of the Settings tab rather than a fifth tab. */
private const val SETTINGS_NOTIFICATIONS = "settings/notifications"

/** §36 puts the app lock above Delete My Data: protect first, destroy second. */
private const val SETTINGS_LOCK = "settings/lock"

/** Export, above Delete in §36's order: the offer of a copy comes before the erase. */
private const val SETTINGS_EXPORT = "settings/export"

/** True while [tab] is anywhere in this destination's hierarchy, children included. */
private fun NavDestination?.isIn(tab: PeriodDestination) =
    this?.hierarchy?.any { it.route == tab.route } == true

/**
 * Where re-selecting a tab returns to.
 *
 * Only Settings has children, so only Settings distinguishes the two; for the
 * others the graph route and the screen route are the same thing.
 */
private val PeriodDestination.rootRoute
    get() = if (this == PeriodDestination.SETTINGS) SETTINGS_ROOT else route
