package io.unom.punktfunk

import android.content.res.Configuration
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.hazeSource

/**
 * Open-source licenses: punktfunk's own license (MIT OR Apache-2.0) plus the third-party software
 * notices, read from the bundled `THIRD-PARTY-NOTICES.txt` asset (generated by
 * scripts/gen-third-party-notices.sh). Reached from [SettingsScreen]; Back returns there.
 *
 * This is the TOUCH entry point; [ConsoleLicensesScreen] shows the same notices on the console's
 * field, where they need a scroll route a controller can actually drive.
 */
@Composable
fun LicensesScreen(onBack: () -> Unit) {
    BackHandler(onBack = onBack)

    Column(Modifier.fillMaxSize()) {
        // Pinned header with a visible Back affordance (Back-button/gesture still work via BackHandler).
        Row(
            modifier = Modifier.fillMaxWidth().padding(start = 4.dp, end = 12.dp, top = 8.dp, bottom = 4.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            IconButton(onClick = onBack) {
                Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
            }
            Text("Open-source licenses", style = MaterialTheme.typography.headlineSmall)
        }
        LicensesBody(
            scroll = rememberScrollState(),
            contentPadding = PaddingValues(start = 20.dp, end = 20.dp, bottom = 24.dp),
        )
    }
}

/**
 * The notices on the console's field. The reason this exists as its own screen rather than the touch
 * one dropped into the shell is the SCROLL: the body is a wall of text with exactly one focusable
 * node (the touch screen's back arrow), and Compose scrolls a container only to keep a FOCUSED child
 * visible — so a controller could reach the first screenful of `THIRD-PARTY-NOTICES.txt` and not one
 * line further. Here up/down steps and the shoulders page, driving the scroll state directly.
 *
 * B closes, as everywhere else; there is nothing on this screen to confirm, so A is not advertised.
 */
@Composable
fun ConsoleLicensesScreen(onBack: () -> Unit, navActive: Boolean = true) {
    BackHandler(onBack = onBack)
    val landscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
    val hazeState = remember { HazeState() }
    val scroll = rememberScrollState()
    val scrollBy = rememberConsoleScroller(scroll)
    val padIsGamepad = (LocalContext.current as? MainActivity)?.lastPadIsGamepad ?: true

    GamepadNavEffect2D(
        active = navActive,
        onDirection = { dir ->
            when (dir) {
                NavDir.UP -> scrollBy(-1, false)
                NavDir.DOWN -> scrollBy(1, false)
                // Left/right are deliberately inert: there is nothing beside this text, and paging
                // sideways off a D-pad would be a second, undocumented way to do the shoulders' job.
                NavDir.LEFT, NavDir.RIGHT -> {}
            }
        },
        onActivate = {},
        onShoulder = { delta -> scrollBy(delta, true) },
    )

    Box(Modifier.fillMaxSize()) {
        Box(Modifier.fillMaxSize().hazeSource(hazeState)) {
            // Calm: this is a screen to read, and a drifting field behind small monospace text is
            // the one place the aurora would be actively unhelpful. Full-bleed under the cutout —
            // only the content takes the safe area.
            GamepadFormBackground(Modifier.fillMaxSize())
            // Inked from the palette: the notices carry no colour of their own, so outside a Surface
            // they would render in Material's default BLACK content colour over the aurora.
            ConsoleInkedTheme {
                Column(Modifier.fillMaxSize().consoleSafeArea()) {
                    LicensesBody(
                        scroll = scroll,
                        contentPadding = PaddingValues(
                            start = ConsoleEdgeInset,
                            end = ConsoleEdgeInset,
                            bottom = ConsoleLegendClearance,
                        ),
                    ) {
                        ConsoleHeader("Open-source licenses", horizontalInset = false)
                    }
                }
            }
        }
        Box(
            Modifier
                .align(Alignment.BottomStart)
                .consoleLegendInsets(landscape)
                .padding(ConsoleLegendInset),
        ) {
            GamepadHintBar(
                listOfNotNull(
                    GamepadHint('↕', PadGlyph.Arrow, "Scroll"),
                    // A TV remote has no shoulders — its route is the D-pad, one step at a time.
                    GamepadHint('⇄', PadGlyph.Arrow, "Page").takeIf { padIsGamepad },
                    PadGlyph.hint('B', "Close", onClick = onBack),
                ),
                hazeState = hazeState,
            )
        }
    }
}

/**
 * The notices themselves, shared by both interfaces — the licenses are a legal obligation, so the
 * two routes must show the same text rather than two copies that can drift. [heading] is empty for
 * the touch screen, which pins its own title row above the scroll.
 */
@Composable
private fun LicensesBody(
    scroll: ScrollState,
    contentPadding: PaddingValues,
    heading: @Composable () -> Unit = {},
) {
    val context = LocalContext.current
    val notices = remember {
        runCatching {
            context.assets.open("THIRD-PARTY-NOTICES.txt").bufferedReader().use { it.readText() }
        }.getOrDefault("Third-party notices unavailable.")
    }
    // The bundled brand typeface (Geist Sans) ships under the SIL Open Font License 1.1. The OFL
    // requires the license travel with the font, so surface it here (mirrors the Apple client).
    val fontLicense = remember {
        runCatching {
            context.assets.open("GEIST-OFL.txt").bufferedReader().use { it.readText() }
        }.getOrNull()
    }
    val version = remember {
        runCatching {
            @Suppress("DEPRECATION")
            context.packageManager.getPackageInfo(context.packageName, 0).versionName
        }.getOrNull()
    }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .verticalScroll(scroll)
            .padding(contentPadding),
        verticalArrangement = Arrangement.spacedBy(16.dp),
    ) {
        heading()
        if (version != null) {
            Text(
                "Punktfunk $version",
                style = MaterialTheme.typography.bodyMedium,
                color = MaterialTheme.colorScheme.onSurfaceVariant,
            )
        }
        Text(
            "Punktfunk is licensed under MIT OR Apache-2.0, at your option. It uses the open-source " +
                "components below, each under its own license.",
            style = MaterialTheme.typography.bodyMedium,
        )
        Text(
            notices,
            style = MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace),
        )
        if (fontLicense != null) {
            Text("Bundled font", style = MaterialTheme.typography.titleMedium)
            Text(
                "The Geist typeface is licensed under the SIL Open Font License 1.1.",
                style = MaterialTheme.typography.bodyMedium,
            )
            Text(
                fontLicense,
                style = MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace),
            )
        }
    }
}
