package solutions.tretter.githugandroid

import android.os.SystemClock
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@Composable
fun GitHugApp() {
    MaterialTheme(colorScheme = GitHugColorScheme) {
        val context = LocalContext.current
        val configuration = LocalConfiguration.current
        val density = LocalDensity.current
        val runtime = remember(context) { GitRepositoryRuntime(context.applicationContext) }
        if (!runtime.isNativeGitAvailable()) {
            MissingNativeGitScreen(message = runtime.unavailableMessage())
            return@MaterialTheme
        }

        val levels = remember { sampleLevels() }
        val paneLayoutStore = remember(context) { PaneLayoutStore(context.applicationContext) }
        val gameProgressStore = remember(context) { GameProgressStore(context.applicationContext) }
        val persistedPaneLayout by paneLayoutStore.layoutFlow.collectAsState(initial = defaultPaneLayout())
        val persistedProgress by gameProgressStore.progressFlow.collectAsState(initial = null)
        val scope = rememberCoroutineScope()
        val screenScrollState = rememberScrollState()
        val screenHeightDp = configuration.screenHeightDp

        var paneLayout by remember { mutableStateOf(defaultPaneLayout()) }
        var currentLevelIndex by remember { mutableStateOf(0) }
        var repo by remember { mutableStateOf(runtime.prepareLevel(levels.first())) }
        var commandInput by remember { mutableStateOf(TextFieldValue("")) }
        var inputFieldVersion by remember { mutableStateOf(0) }
        var suppressedImeEcho by remember { mutableStateOf<String?>(null) }
        var output by remember { mutableStateOf(listOf(runtime.startupBanner())) }
        var hintIndex by remember { mutableStateOf(0) }
        var activeExerciseDetail by remember { mutableStateOf<ExerciseDetailPanel?>(null) }
        var visibleHint by remember { mutableStateOf<String?>(null) }
        var completedLevels by remember { mutableStateOf(setOf<String>()) }
        var commandHistory by remember { mutableStateOf(listOf<String>()) }
        var historyIndex by remember { mutableStateOf(-1) }
        var historyDraft by remember { mutableStateOf("") }
        var hasRestoredProgress by remember { mutableStateOf(false) }
        var editorState by remember { mutableStateOf<TextEditorState?>(null) }
        var gitMessageEditorState by remember { mutableStateOf<GitMessageEditorState?>(null) }
        var manPageState by remember { mutableStateOf<ManPageState?>(null) }
        var solvedCelebrationTitle by remember { mutableStateOf<String?>(null) }
        var showTerminalInputHint by remember { mutableStateOf(true) }
        var showExerciseDescriptionHint by remember { mutableStateOf(true) }
        var showHelpOverlay by rememberSaveable { mutableStateOf(false) }
        var showHelpOnStart by rememberSaveable { mutableStateOf(true) }
        var helpPreferenceInitialized by rememberSaveable { mutableStateOf(false) }
        var showDiagnosticDialog by rememberSaveable { mutableStateOf(false) }
        var diagnosticSkinEnabled by rememberSaveable { mutableStateOf(false) }
        var headerTapTimes by remember { mutableStateOf(emptyList<Long>()) }
        var exerciseBounds by remember { mutableStateOf<Rect?>(null) }
        var promptBounds by remember { mutableStateOf<Rect?>(null) }
        val currentLevel = levels[currentLevelIndex]
        val appBackground = if (diagnosticSkinEnabled) DiagnosticAppBackground else AppBackground
        val imeBottom = WindowInsets.ime.getBottom(density)
        val navigationBottom = WindowInsets.navigationBars.getBottom(density)
        val bottomSystemPadding = with(density) {
            if (imeBottom > navigationBottom) 0.dp else navigationBottom.toDp()
        }

        LaunchedEffect(solvedCelebrationTitle) {
            if (solvedCelebrationTitle != null) {
                delay(3_000)
                solvedCelebrationTitle = null
            }
        }

        LaunchedEffect(persistedPaneLayout) {
            paneLayout = persistedPaneLayout
        }

        LaunchedEffect(currentLevelIndex) {
            delay(100)
            screenScrollState.animateScrollTo(0)
        }

        fun updatePaneLayout(transform: (PaneLayout) -> PaneLayout, persist: Boolean = true) {
            val updated = transform(paneLayout)
            paneLayout = updated
            if (persist) {
                scope.launch { paneLayoutStore.save(updated) }
            }
        }

        fun clearCommandInput(recreateField: Boolean = false) {
            commandInput = TextFieldValue(text = "", selection = TextRange.Zero)
            if (recreateField) {
                inputFieldVersion += 1
            }
        }

        suspend fun persistProgress(completed: Set<String>, activeLevelId: String?) {
            gameProgressStore.saveProgress(completed, activeLevelId)
        }

        LaunchedEffect(persistedProgress) {
            val restoredProgress = persistedProgress ?: return@LaunchedEffect
            val restoredCompletedLevels = restoredProgress.completedLevels
            completedLevels = restoredCompletedLevels
            showHelpOnStart = restoredProgress.showHelpOnStart
            if (!helpPreferenceInitialized) {
                helpPreferenceInitialized = true
                showHelpOverlay = restoredProgress.showHelpOnStart
            }
            AppLog.d(
                "GitHugApp",
                "Observed persisted progress completed=${restoredCompletedLevels.sorted()} activeLevelId=${restoredProgress.activeLevelId} restored=$hasRestoredProgress",
            )
            if (!hasRestoredProgress) {
                hasRestoredProgress = true
                val resumeIndex = restoredProgress.activeLevelId
                    ?.let { activeId -> levels.indexOfFirst { it.id == activeId }.takeIf { it >= 0 } }
                    ?: levels.indexOfFirst { it.id !in restoredCompletedLevels }.takeIf { it >= 0 }
                    ?: levels.lastIndex
                AppLog.d(
                    "GitHugApp",
                    "Restoring app to levelIndex=$resumeIndex levelId=${levels[resumeIndex].id}",
                )
                currentLevelIndex = resumeIndex
                repo = runtime.prepareLevel(levels[resumeIndex])
                output = listOf(
                    if (restoredCompletedLevels.size == levels.size) {
                        "🏁 All Githug levels completed."
                    } else if (restoredCompletedLevels.isEmpty()) {
                        runtime.startupBanner()
                    } else {
                        "Resumed at level: ${levels[resumeIndex].title}"
                    }
                )
                commandInput = TextFieldValue(text = "", selection = TextRange.Zero)
                suppressedImeEcho = null
                hintIndex = 0
                activeExerciseDetail = null
                visibleHint = null
                historyIndex = -1
                historyDraft = ""
                paneLayout = paneLayout.copy(
                    weights = paneLayout.weights + recommendedPaneWeights(
                        heights = recommendedPaneHeights(
                            level = levels[resumeIndex],
                            levelCount = levels.size,
                            outputLineCount = 1,
                            screenHeightDp = screenHeightDp,
                            suggestionsVisible = false,
                            visibleHint = null,
                        ),
                    )
                )
            }
        }

        fun applyRecommendedPaneWeights(
            persist: Boolean = true,
            outputLineCount: Int = terminalOutputLineCount(output),
        ) {
            updatePaneLayout(
                transform = { layout ->
                    layout.copy(
                        weights = layout.weights + recommendedPaneWeights(
                            heights = recommendedPaneHeights(
                                level = currentLevel,
                                levelCount = levels.size,
                                outputLineCount = outputLineCount,
                                screenHeightDp = screenHeightDp,
                                suggestionsVisible = activeExerciseDetail == ExerciseDetailPanel.SUGGESTIONS,
                                visibleHint = visibleHint,
                            ),
                        )
                    )
                },
                persist = persist,
            )
        }

        fun resetCurrentLevel(message: String = "Level reset.") {
            val resetLevelId = currentLevel.id
            val updatedCompletedLevels = completedLevels - resetLevelId
            completedLevels = updatedCompletedLevels
            scope.launch { persistProgress(updatedCompletedLevels, resetLevelId) }
            repo = runtime.prepareLevel(currentLevel)
            clearCommandInput()
            suppressedImeEcho = null
            output = listOf(message)
            hintIndex = 0
            activeExerciseDetail = null
            visibleHint = null
            historyIndex = -1
            historyDraft = ""
            editorState = null
            gitMessageEditorState = null
            manPageState = null
            applyRecommendedPaneWeights(persist = false, outputLineCount = terminalOutputLineCount(listOf(message)))
        }

        fun loadLevel(
            index: Int,
            message: List<String> = listOf("Loaded level: ${levels[index].title}"),
            keepSuppressedImeEcho: Boolean = false,
        ) {
            AppLog.d("GitHugApp", "Loading level index=$index id=${levels[index].id} title=${levels[index].title}")
            currentLevelIndex = index
            repo = runtime.prepareLevel(levels[index])
            output = message
            clearCommandInput(recreateField = true)
            if (!keepSuppressedImeEcho) {
                suppressedImeEcho = null
            }
            hintIndex = 0
            activeExerciseDetail = null
            visibleHint = null
            historyIndex = -1
            historyDraft = ""
            editorState = null
            gitMessageEditorState = null
            manPageState = null
            paneLayout = paneLayout.copy(
                weights = paneLayout.weights + recommendedPaneWeights(
                    heights = recommendedPaneHeights(
                        level = levels[index],
                        levelCount = levels.size,
                        outputLineCount = 1,
                        screenHeightDp = screenHeightDp,
                        suggestionsVisible = false,
                        visibleHint = null,
                    ),
                )
            )
        }

        fun setCommandText(text: String) {
            commandInput = TextFieldValue(text = text, selection = TextRange(text.length))
        }

        fun moveCursor(delta: Int) {
            val next = (commandInput.selection.start + delta).coerceIn(0, commandInput.text.length)
            commandInput = commandInput.copy(selection = TextRange(next))
        }

        fun historyUp() {
            if (commandHistory.isEmpty()) return
            if (historyIndex == -1) {
                historyDraft = commandInput.text
                historyIndex = commandHistory.lastIndex
            } else {
                historyIndex = (historyIndex - 1).coerceAtLeast(0)
            }
            setCommandText(commandHistory[historyIndex])
        }

        fun historyDown() {
            if (commandHistory.isEmpty() || historyIndex == -1) return
            if (historyIndex >= commandHistory.lastIndex) {
                historyIndex = -1
                setCommandText(historyDraft)
            } else {
                historyIndex += 1
                setCommandText(commandHistory[historyIndex])
            }
        }

        fun tabComplete() {
            val cursor = commandInput.selection.start.coerceIn(0, commandInput.text.length)
            val beforeCursor = commandInput.text.substring(0, cursor)
            val tokenStart = beforeCursor.lastIndexOf(' ').let { if (it == -1) 0 else it + 1 }
            val token = commandInput.text.substring(tokenStart, cursor)
            val leadingCommand = beforeCursor.trimStart().substringBefore(' ')
            val isCdCompletion = leadingCommand == "cd"
            if (token.isBlank() && !isCdCompletion) return

            val candidates = runtime.completionCandidates(currentLevel, repo, directoriesOnly = isCdCompletion)
            val matches = candidates.sorted().filter { it.startsWith(token) }
            if (matches.isEmpty()) return

            val replacement = if (matches.size == 1) matches.first() else commonPrefix(matches)
            if (replacement == token && matches.size > 1) {
                val newOutput = output + "completion> ${matches.joinToString(" ")}"
                output = newOutput
                applyRecommendedPaneWeights(persist = false, outputLineCount = terminalOutputLineCount(newOutput))
                return
            }

            val newText = commandInput.text.replaceRange(tokenStart, cursor, replacement)
            val newCursor = tokenStart + replacement.length
            commandInput = TextFieldValue(newText, selection = TextRange(newCursor))
        }

        fun applyCommandResult(raw: String, newRepo: RepoState, lines: List<String>, echoCommand: Boolean) {
            val levelForResult = currentLevel
            val solvedAfterCommand = levelForResult.validator(newRepo, raw)
            val wasAlreadyCompleted = currentLevel.id in completedLevels
            AppLog.d(
                "GitHugApp",
                "Command='$raw' level=${levelForResult.id} solved=$solvedAfterCommand alreadyCompleted=$wasAlreadyCompleted completedBefore=${completedLevels.sorted()}",
            )
            val newOutput = buildList {
                addAll(output)
                if (echoCommand) {
                    add("$ $raw")
                }
                addAll(lines)
                if (solvedAfterCommand && !wasAlreadyCompleted) {
                    add("")
                    add("👍 Level solved: ${levelForResult.title}")
                    add("Nice. ${completedLevels.size + 1}/${levels.size} levels complete.")
                }
            }
            if (solvedAfterCommand && !wasAlreadyCompleted) {
                val updatedCompletedLevels = completedLevels + levelForResult.id
                completedLevels = updatedCompletedLevels
                solvedCelebrationTitle = levelForResult.title
                val nextLevelIndex = levels.indexOfFirst { it.id !in updatedCompletedLevels }
                if (nextLevelIndex >= 0) {
                    val nextLevelId = levels[nextLevelIndex].id
                    AppLog.d(
                        "GitHugApp",
                        "Level solved ${levelForResult.id}; advancing to next incomplete index=$nextLevelIndex id=$nextLevelId",
                    )
                    scope.launch { persistProgress(updatedCompletedLevels, nextLevelId) }
                    loadLevel(nextLevelIndex, message = emptyList(), keepSuppressedImeEcho = true)
                } else {
                    AppLog.d("GitHugApp", "All levels completed")
                    scope.launch { persistProgress(updatedCompletedLevels, levels.lastOrNull()?.id) }
                    repo = newRepo
                    val completedOutput = newOutput + listOf("🏁 All Githug levels completed.")
                    output = completedOutput
                    clearCommandInput()
                    suppressedImeEcho = null
                    applyRecommendedPaneWeights(persist = false, outputLineCount = terminalOutputLineCount(completedOutput))
                }
            } else {
                AppLog.d("GitHugApp", "Staying on level=${levelForResult.id}")
                repo = newRepo
                output = newOutput
                applyRecommendedPaneWeights(persist = false, outputLineCount = terminalOutputLineCount(newOutput))
            }
        }

        fun openEditor(invocation: VisualEditorInvocation) {
            val path = invocation.path.orEmpty()
            val (content, lines) = if (path.isBlank()) {
                "" to emptyList()
            } else {
                runtime.readEditorFile(currentLevel, repo, path)
            }
            val newOutput = buildList {
                addAll(output)
                add("$ ${invocation.command}")
                addAll(lines)
                if (lines.isEmpty()) {
                    add("Opened ${invocation.editor} editor${if (path.isBlank()) "" else " for $path"}")
                }
            }
            output = newOutput
            if (lines.isEmpty()) {
                editorState = TextEditorState(
                    editor = invocation.editor,
                    originalCommand = invocation.command,
                    path = path,
                    content = content,
                    saveAsPath = path,
                )
            }
            applyRecommendedPaneWeights(persist = false, outputLineCount = terminalOutputLineCount(newOutput))
        }

        fun openManPage(invocation: GitHelpInvocation) {
            val content = runtime.gitManPage(currentLevel, repo, invocation.topic)
            val newOutput = buildList {
                addAll(output)
                add("$ ${invocation.command}")
                add("Opened git help viewer for ${invocation.topic}")
            }
            output = newOutput
            manPageState = ManPageState(topic = invocation.topic, content = content)
            applyRecommendedPaneWeights(persist = false, outputLineCount = terminalOutputLineCount(newOutput))
        }

        fun openGitMessageEditor(invocation: GitEditorInvocation) {
            val initialContent = runtime.gitEditorInitialContent(currentLevel, repo, invocation)
            val openedMessage = when (invocation.kind) {
                GitEditorCommandKind.REBASE_TODO -> "Opened Git rebase editor"
                GitEditorCommandKind.PATCH_HUNK -> "Opened Git patch editor"
                else -> "Opened Git message editor"
            }
            val newOutput = buildList {
                addAll(output)
                add("$ ${invocation.command}")
                add(openedMessage)
            }
            output = newOutput
            gitMessageEditorState = GitMessageEditorState(
                invocation = invocation.copy(initialContent = initialContent),
                content = initialContent,
            )
            applyRecommendedPaneWeights(persist = false, outputLineCount = terminalOutputLineCount(newOutput))
        }

        fun saveEditor() {
            val state = editorState ?: return
            val targetPath = state.saveAsPath.trim()
            if (targetPath.isBlank()) return
            val (newRepo, lines) = runtime.writeEditorFile(currentLevel, repo, targetPath, state.content)
            editorState = null
            applyCommandResult(state.originalCommand, newRepo, lines, echoCommand = false)
        }

        fun saveGitMessageEditor() {
            val state = gitMessageEditorState ?: return
            val (newRepo, lines) = runtime.executeGitEditorCommand(
                level = currentLevel,
                currentRepo = repo,
                invocation = state.invocation,
                message = state.content,
            )
            gitMessageEditorState = null
            applyCommandResult(state.invocation.command, newRepo, lines, echoCommand = false)
        }

        fun runCommand() {
            val submittedText = commandInput.text
            val raw = submittedText.trim()
            if (raw.isBlank()) return
            val isInteractiveInput = repo.interactiveAddSession != null

            showHelpOverlay = false
            showTerminalInputHint = false
            showExerciseDescriptionHint = false
            suppressedImeEcho = submittedText
            clearCommandInput(recreateField = true)

            if (!isInteractiveInput && commandHistory.lastOrNull() != raw) {
                commandHistory = commandHistory + raw
            }
            historyIndex = -1
            historyDraft = ""

            val editorInvocation = parseVisualEditorInvocation(raw)
            if (editorInvocation != null) {
                openEditor(editorInvocation)
                return
            }

            val helpInvocation = parseGitHelpInvocation(raw)
            if (helpInvocation != null) {
                openManPage(helpInvocation)
                return
            }

            val gitEditorInvocation = parseGitEditorInvocation(raw)
            if (gitEditorInvocation != null) {
                openGitMessageEditor(gitEditorInvocation)
                return
            }

            val patchHunkEditorInvocation = GitSandboxEngine.parsePatchHunkEditorInvocation(repo, raw)
            if (patchHunkEditorInvocation != null) {
                openGitMessageEditor(patchHunkEditorInvocation)
                return
            }

            val (newRepo, lines) = runtime.execute(currentLevel, repo, raw)
            applyCommandResult(raw, newRepo, lines, echoCommand = !isInteractiveInput)
        }

        fun showCommandHelp() {
            val newOutput = output + runtime.commandReferenceLines()
            output = newOutput
            applyRecommendedPaneWeights(persist = false, outputLineCount = terminalOutputLineCount(newOutput))
        }

        fun registerHeaderTap() {
            val now = SystemClock.elapsedRealtime()
            val recentTaps = (headerTapTimes + now).filter { tapTime -> now - tapTime <= 15_000L }
            if (recentTaps.size >= 7) {
                headerTapTimes = emptyList()
                showDiagnosticDialog = true
            } else {
                headerTapTimes = recentTaps
            }
        }

        Scaffold(
            containerColor = appBackground,
            contentWindowInsets = WindowInsets(0.dp),
        ) { padding ->
            Surface(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(padding),
                color = appBackground,
            ) {
                Box(modifier = Modifier.fillMaxSize()) {
                    Column(
                        modifier = Modifier
                            .fillMaxSize()
                            .background(appBackground)
                            .statusBarsPadding()
                            .verticalScroll(screenScrollState)
                            .padding(
                                start = 10.dp,
                                top = 8.dp,
                                end = 10.dp,
                                bottom = 8.dp + bottomSystemPadding,
                            ),
                        verticalArrangement = Arrangement.spacedBy(8.dp),
                    ) {
                        FixedHeader(onTap = { registerHeaderTap() })
                        Box(
                            modifier = Modifier.fillMaxWidth(),
                        ) {
                            PaneWorkspace(
                                modifier = Modifier.fillMaxWidth(),
                                paneLayout = paneLayout,
                                diagnosticSkinEnabled = diagnosticSkinEnabled,
                                onMovePane = { paneId, delta ->
                                    updatePaneLayout(transform = { layout -> movePane(layout, paneId, delta) })
                                },
                                onTogglePane = { paneId ->
                                    updatePaneLayout(transform = { layout ->
                                        val collapsed = layout.collapsed.toMutableSet()
                                        if (!collapsed.add(paneId)) collapsed.remove(paneId)
                                        layout.copy(collapsed = collapsed)
                                    })
                                },
                                levelsContent = {
                                    LevelsPane(levels, currentLevelIndex, completedLevels) { index -> loadLevel(index) }
                                },
                                visualContent = { VisualPane(repo) },
                                exerciseContent = {
                                    ExercisePane(
                                        level = currentLevel,
                                        visibleHint = if (activeExerciseDetail == ExerciseDetailPanel.HINT) visibleHint else null,
                                        suggestionsExpanded = activeExerciseDetail == ExerciseDetailPanel.SUGGESTIONS,
                                        showDescriptionHint = false,
                                        onBoundsChanged = { exerciseBounds = it },
                                        onToggleSuggestions = {
                                            showExerciseDescriptionHint = false
                                            showHelpOverlay = false
                                            activeExerciseDetail = if (activeExerciseDetail == ExerciseDetailPanel.SUGGESTIONS) null else ExerciseDetailPanel.SUGGESTIONS
                                            if (activeExerciseDetail == ExerciseDetailPanel.SUGGESTIONS) {
                                                visibleHint = null
                                            }
                                            applyRecommendedPaneWeights(persist = false)
                                        },
                                        onHint = {
                                            showExerciseDescriptionHint = false
                                            showHelpOverlay = false
                                            val hint = currentLevel.hints.getOrNull(hintIndex) ?: "No more hints for this level."
                                            visibleHint = hint
                                            activeExerciseDetail = ExerciseDetailPanel.HINT
                                            hintIndex = (hintIndex + 1).coerceAtMost(currentLevel.hints.size)
                                            applyRecommendedPaneWeights(persist = false)
                                        },
                                        onReset = {
                                            showExerciseDescriptionHint = false
                                            showHelpOverlay = false
                                            resetCurrentLevel()
                                        },
                                    )
                                },
                                terminalContent = {
                                    TerminalPane(
                                        output = output,
                                        inputFieldVersion = inputFieldVersion,
                                        commandInput = commandInput,
                                        showInputHint = false,
                                        onInputHintDismiss = {
                                            showTerminalInputHint = false
                                            showHelpOverlay = false
                                        },
                                        onValueChange = {
                                            val blockedEcho = suppressedImeEcho
                                            if (blockedEcho != null && commandInput.text.isEmpty()) {
                                                val blockedTrimmed = blockedEcho.trim()
                                                if (it.text == blockedEcho || (blockedTrimmed.isNotEmpty() && it.text == blockedTrimmed)) {
                                                    return@TerminalPane
                                                }
                                            }
                                            suppressedImeEcho = null
                                            if (it.text.isNotEmpty()) {
                                                showTerminalInputHint = false
                                                showExerciseDescriptionHint = false
                                                showHelpOverlay = false
                                            }
                                            commandInput = it
                                        },
                                        onRun = { runCommand() },
                                        onTab = { tabComplete() },
                                        onHelp = { showCommandHelp() },
                                        onCursorLeft = { moveCursor(-1) },
                                        onCursorRight = { moveCursor(1) },
                                        onHistoryUp = { historyUp() },
                                        onHistoryDown = { historyDown() },
                                        onPromptBoundsChanged = { promptBounds = it },
                                    )
                                },
                            )
                        }
                    }
                    if (showHelpOverlay) {
                        HelpCalloutOverlay(
                            showHelpOnStart = showHelpOnStart,
                            onShowHelpOnStartChange = { showHelpOnStart = it },
                            onOk = {
                                showHelpOverlay = false
                                scope.launch { gameProgressStore.saveShowHelpOnStart(showHelpOnStart) }
                            },
                            onClose = { showHelpOverlay = false },
                            exerciseBounds = exerciseBounds,
                            promptBounds = promptBounds,
                        )
                    }
                }
            }
        }

        editorState?.let { state ->
            TextEditorDialog(
                state = state,
                onContentChange = { editorState = state.copy(content = it) },
                onSaveAsPathChange = { editorState = state.copy(saveAsPath = it) },
                onClose = {
                    output = output + "Editor closed without saving"
                    editorState = null
                },
                onSave = { saveEditor() },
            )
        }

        gitMessageEditorState?.let { state ->
            GitMessageEditorDialog(
                state = state,
                onContentChange = { gitMessageEditorState = state.copy(content = it) },
                onClose = {
                    output = output + "Git editor closed without saving"
                    gitMessageEditorState = null
                },
                onSave = { saveGitMessageEditor() },
            )
        }

        manPageState?.let { state ->
            ManPageDialog(
                state = state,
                onClose = { manPageState = null },
            )
        }

        if (showDiagnosticDialog) {
            DiagnosticSkinDialog(
                diagnosticSkinEnabled = diagnosticSkinEnabled,
                onDiagnosticSkinEnabledChange = { diagnosticSkinEnabled = it },
                onDismiss = { showDiagnosticDialog = false },
            )
        }

        solvedCelebrationTitle?.let { title ->
            SolvedCelebrationOverlay(title = title)
        }
    }
}
