code:
package life.xchange.tui

import com.googlecode.lanterna.TerminalSize
import com.googlecode.lanterna.TextColor
import com.googlecode.lanterna.graphics.TextGraphics
import com.googlecode.lanterna.input.KeyType
import com.googlecode.lanterna.screen.Screen
import com.googlecode.lanterna.screen.TerminalScreen
import com.googlecode.lanterna.terminal.DefaultTerminalFactory
import com.googlecode.lanterna.terminal.Terminal
import life.xchange.*
import life.xchange.compatibility.CircularLoadOrderException
import life.xchange.db.*
import life.xchange.rework.ModFileAdder
import life.xchange.util.versionFinder
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import java.io.File
import java.io.IOException
import kotlin.concurrent.thread

/**
 * Enum representing the active pane in the TUI.
 */
enum class Pane {
    TOP, BOTTOM
}

/**
 * Creates a log function that captures log messages for display in the TUI.
 * @param screen The terminal screen.
 * @param statusMessage The status message to display.
 * @param duration The duration to display the output for (in milliseconds).
 * @param onComplete A function to call when the display is complete.
 * @return A function that can be used for logging.
 */
private fun createTuiLogger(
    screen: Screen,
    statusMessage: String,
    duration: Long = 5000,
    onComplete: () -> Unit
): (String) -> Unit {
    val logBuffer = StringBuilder()
    var timerStarted = false
    var displayActive = false

    return { message ->
        if (message.isNotBlank()) {
            logBuffer.appendLine(message)
        }

        if (displayActive) {
            updateLogDisplay(screen, logBuffer.toString(), statusMessage)
        }

        if (!displayActive) {
            displayActive = true
            timerStarted = true

            displayOutput(screen, logBuffer.toString(), statusMessage, duration) {
                displayActive = false
                timerStarted = false
                onComplete()
            }
        } else {
            TimerManager.startNewTimer(duration) {
                displayActive = false
                timerStarted = false
                onComplete()
            }
        }
    }
}

/**
 * Singleton object that manages timing operations for UI components.
 * Ensures only one timer is active at any given time by canceling previous
 * timers when a new one is started.
 */
private object TimerManager {
    @Volatile
    private var currentTimer: Thread? = null

    /**
     * Starts a new timer that executes the provided callback after the specified duration.
     * Automatically cancels any previously running timer.
     *
     * @param duration The time in milliseconds to wait before executing the callback
     * @param onComplete The callback function to execute when the timer completes
     */
    fun startNewTimer(duration: Long, onComplete: () -> Unit) {
        currentTimer?.interrupt()

        currentTimer = thread {
            try {
                Thread.sleep(duration)
                onComplete()
            } catch (e: InterruptedException) {
                // Do Nothing
            }
        }
    }
}

/**
 * Displays output in the TUI.
 * @param screen The terminal screen.
 * @param output The output to display.
 * @param statusMessage The status message to display.
 * @param duration The duration to display the output for (in milliseconds).
 * @param onComplete A function to call when the display is complete.
 */
private fun displayOutput(
    screen: Screen,
    output: String,
    statusMessage: String,
    duration: Long = 3000,
    onComplete: () -> Unit
) {
    updateLogDisplay(screen, output, statusMessage)

    TimerManager.startNewTimer(duration, onComplete)
}

/**
 * Updates the log display in the TUI without starting a new timer.
 * @param screen The terminal screen.
 * @param output The output to display.
 * @param statusMessage The status message to display.
 */
private fun updateLogDisplay(
    screen: Screen,
    output: String,
    statusMessage: String
) {
    val outputLines = output.lines()

    val tg = screen.newTextGraphics()
    val terminalSize = screen.terminalSize

    screen.clear()

    tg.foregroundColor = TextColor.ANSI.CYAN
    tg.putString(1, 0, "Output")
    tg.putString(1, 1, "=".repeat(terminalSize.columns - 2))

    val maxLines = terminalSize.rows - 4
    val displayLines = if (outputLines.size > maxLines) {
        outputLines.takeLast(maxLines)
    } else {
        outputLines
    }

    tg.foregroundColor = TextColor.ANSI.WHITE
    displayLines.forEachIndexed { index, line ->
        tg.putString(1, index + 2, line.take(terminalSize.columns - 2))
    }

    tg.foregroundColor = TextColor.ANSI.YELLOW
    tg.putString(0, terminalSize.rows - 1, statusMessage.take(terminalSize.columns))

    screen.refresh()
}

/**
 * Truncate text to fit within a maximum length, adding ellipsis if needed.
 * @param text The text to truncate.
 * @param maxLength The maximum length of the text.
 * @return The truncated text.
 */
private fun truncateText(text: String, maxLength: Int): String {
    return if (text.length > maxLength) {
        text.substring(0, maxLength - 3) + "..."
    } else {
        text
    }
}

/**
 * Display a mod in the TUI.
 * @param tg The text graphics to draw with.
 * @param mod The mod to display.
 * @param y The y-coordinate to draw at.
 * @param prefix The prefix to use for the order column.
 * @param terminalSize The size of the terminal.
 */
private fun displayMod(
    tg: TextGraphics,
    mod: UnpackedMod,
    y: Int,
    prefix: String,
    terminalSize: TerminalSize
) {
    val orderText = "$prefix ${mod.loadOrder ?: "N/A"}"
    tg.putString(1, y, orderText)

    val nameText = mod.name
    val maxNameLength = terminalSize.columns - 20
    val truncatedName = truncateText(nameText, maxNameLength)
    tg.putString(10, y, truncatedName)

    val statusText = if (mod.enabled) "Enabled" else "Disabled"
    val originalColor = tg.foregroundColor
    tg.foregroundColor = if (mod.enabled) TextColor.ANSI.GREEN else TextColor.ANSI.RED
    tg.putString(terminalSize.columns - 10, y, statusText)
    tg.foregroundColor = originalColor
}

/**
 * Draw the top pane of the TUI.
 */
private fun drawTopPane(
    tg: TextGraphics,
    terminalSize: TerminalSize,
    paneHeight: Int,
    mods: List<UnpackedMod>,
    selectedIndex: Int,
    offset: Int,
    isActive: Boolean,
    normalColor: TextColor,
    selectedColor: TextColor,
    pickedColor: TextColor,
    pickedModIndex: Int?,
    profileListActive: Boolean,
    profiles: List<ModProfile>,
    selectedProfileIndex: Int,
    profileListOffset: Int
) {
    tg.foregroundColor = TextColor.ANSI.CYAN
    if (profileListActive) {
        tg.putString(1, 0, "Profiles")
        tg.putString(1, 1, "=".repeat(terminalSize.columns - 2))

        val visibleProfiles = paneHeight - 4
        val endDisplayIndex = (profileListOffset + visibleProfiles).coerceAtMost(profiles.size)

        if (profiles.isEmpty()) {
            tg.foregroundColor = normalColor
            tg.putString(1, 3, "No profiles found")
        } else {
            for (i in profileListOffset until endDisplayIndex) {
                val y = i - profileListOffset + 3
                val profile = profiles[i]

                val prefix = if (i == selectedProfileIndex && isActive) ">" else " "

                val displayText = "$prefix ${profile.name}"

                tg.foregroundColor = if (i == selectedProfileIndex && isActive) selectedColor else normalColor

                val maxLength = terminalSize.columns - 2
                val truncatedText = if (displayText.length > maxLength) {
                    displayText.substring(0, maxLength - 3) + "..."
                } else {
                    displayText
                }

                tg.putString(1, y, truncatedText)
            }
        }
    } else {
        tg.putString(1, 0, "Mods")
        tg.putString(1, 1, "=".repeat(terminalSize.columns - 2))

        tg.foregroundColor = TextColor.ANSI.YELLOW
        tg.putString(1, 2, "Order")
        tg.putString(10, 2, "Name")
        tg.putString(terminalSize.columns - 10, 2, "Status")

        val visibleMods = paneHeight - 4
        val endDisplayIndex = (offset + visibleMods).coerceAtMost(mods.size)

        val pickedMod = if (pickedModIndex != null && pickedModIndex < mods.size) mods[pickedModIndex] else null

        for (i in offset until endDisplayIndex) {
            val y = i - offset + 3

            if (i == pickedModIndex) {
                continue
            }

            val mod = mods[i]

            val prefix = if (i == selectedIndex && isActive) ">" else " "

            tg.foregroundColor = if (i == selectedIndex && isActive) selectedColor else normalColor

            displayMod(tg, mod, y, prefix, terminalSize)
        }

        if (pickedMod != null && selectedIndex >= offset && selectedIndex < endDisplayIndex) {
            val y = selectedIndex - offset + 3

            tg.foregroundColor = pickedColor

            displayMod(tg, pickedMod, y, ">", terminalSize)
        }
    }
}

/**
 * Draw the bottom pane of the TUI.
 */
private fun drawBottomPane(
    tg: TextGraphics,
    terminalSize: TerminalSize,
    topPaneHeight: Int,
    menuOptions: List<String>,
    selectedIndex: Int,
    isActive: Boolean,
    normalColor: TextColor,
    selectedColor: TextColor
) {
    val menuItemWidth = terminalSize.columns / menuOptions.size

    for (i in menuOptions.indices) {
        val x = i * menuItemWidth
        val y = topPaneHeight + 1

        tg.foregroundColor = if (i == selectedIndex && isActive) selectedColor else normalColor

        val text = menuOptions[i]
        val padding = (menuItemWidth - text.length) / 2
        val displayText = " ".repeat(padding) + text + " ".repeat(padding)

        val truncatedText = truncateText(displayText, menuItemWidth)

        tg.putString(x, y, truncatedText)
    }
}

/**
 * Generic function to perform an operation with TUI logging.
 * @param screen The terminal screen.
 * @param statusMessage The status message to display.
 * @param onComplete A function to call when the operation is complete.
 */
private fun withTuiLogging(
    screen: Screen,
    statusMessage: String,
    onComplete: () -> Unit,
    block: (logger: (String) -> Unit) -> Unit
) {
    val logBuffer = StringBuilder()
    val allLogs = java.util.Collections.synchronizedList(mutableListOf<String>())
    var displayActive = false
    val lock = Object()

    val logger: (String) -> Unit = { message ->
        synchronized(lock) {
            if (message.isNotBlank()) {
                allLogs.add(message)
                logBuffer.appendLine(message)

                if (displayActive) {
                    updateLogDisplay(screen, logBuffer.toString(), statusMessage)
                } else {
                    displayActive = true
                    displayOutput(screen, logBuffer.toString(), statusMessage, 0) {}
                }
            }
        }
    }

    thread {
        try {
            block(logger)

            Thread.sleep(500)

            synchronized(lock) {
                displayOutput(screen, logBuffer.toString(), statusMessage, 5000) {
                    onComplete()
                }
            }
        } catch (e: Exception) {
            synchronized(lock) {
                logBuffer.appendLine("Error: ${e.message}")
                displayOutput(screen, logBuffer.toString(), statusMessage, 5000) {
                    onComplete()
                }
            }
        }
    }
}

/**
 * Load mods with TUI logging.
 * @param screen The terminal screen.
 * @param copyFiles Whether to copy files instead of using symbolic links.
 * @param cleanModded Whether to clean the modded directory before loading.
 * @param onComplete A function to call when the operation is complete.
 */
private fun loadModsWithTuiLogging(
    screen: Screen,
    copyFiles: Boolean,
    cleanModded: Boolean,
    onComplete: () -> Unit
) {
    Settings.copyInsteadOfLink = copyFiles
    Settings.cleanModdedOnLoad = cleanModded

    withTuiLogging(screen, "Mods loaded successfully", onComplete) { logger ->
        val conflicts = ModProcessor(logger).loadMods(
            inputHtmlFile = htmlFile,
            outputDirectory = moddedDir,
            copyFiles = copyFiles
        )

        if (conflicts.isNotEmpty()) {
            logger("\nWarning: Some of the loaded mod files overlap. This may cause undefined behavior.")
            conflicts.forEach { (mod, conflictList) ->
                if (conflictList.isNotEmpty()) {
                    logger("$mod:")
                    conflictList.forEach { logger("  $it") }
                }
            }
        }

        logger("\nMods loaded successfully to $moddedDir")
    }
}

/**
 * Check for updates to the mod loader with TUI logging.
 * @param screen The terminal screen.
 * @param onComplete A function to call when the operation is complete.
 */
private fun checkForUpdatesWithTuiLogging(
    screen: Screen,
    onComplete: () -> Unit
) {
    withTuiLogging(screen, "Update check completed", onComplete) { logger ->
        logger("Checking for updates...")

        val release = versionFinder.getLatestReleaseWithLogging(logger)

        if (release.version > AppProperties.appVersion) {
            logger("A new version is available: ${release.version}")
            logger("Current version: ${AppProperties.appVersion}")
            logger("You can download the new version from: https://gitgud.io/xchange-life/mod-loader/-/releases")
            logger("---------------------------------------")
            logger("Press Down to return to the main menu.")
        } else {
            logger("You're running the latest version: ${AppProperties.appVersion}")
            logger("---------------------------------------")
            logger("Press Down to return to the main menu.")
        }
    }
}

/**
 * Display the list of CLI commands and their usage.
 */
internal fun showHelp() {
    println("X-Change Life Mod Loader CLI Commands:")
    println()
    println("--cli                       Launch the mod-loader TUI")
    println("--help                      Display this help message")
}

/**
 * Sort mod load order automatically with TUI logging.
 * @param screen The terminal screen.
 * @param onComplete A function to call when the operation is complete.
 */
private fun sortModLoadOrderWithTuiLogging(
    screen: Screen,
    onComplete: () -> Unit
) {
    withTuiLogging(screen, "Mods sorted successfully", onComplete) { logger ->
        logger("Sorting mod load order...")

        transaction {
            ModProcessor.updateLoadOrders()
            logger("Mod load order sorted successfully")
        }
    }
}

/**
 * Validate mod load order with TUI logging.
 * @param screen The terminal screen.
 * @param onComplete A function to call when the operation is complete.
 */
private fun validateModLoadOrderWithTuiLogging(
    screen: Screen,
    onComplete: () -> Unit
) {
    withTuiLogging(screen, "Validation completed", onComplete) { logger ->
        logger("Validating mod load order...")

        val orderMessages = ModProcessor.validateMods(htmlFile, log = logger)
        if (orderMessages.values.all { it.isEmpty() }) {
            logger("No conflicts found in mod load order")
        } else {
            logger("\nConflicts found:")
            orderMessages.forEach { (modName, conflicts) ->
                if (conflicts.isNotEmpty()) {
                    logger("$modName:")
                    conflicts.forEach { logger("  $it") }
                }
            }
        }
    }
}

/**
 * Save a profile with TUI logging.
 * @param screen The terminal screen.
 * @param profileName The name of the profile to save.
 * @param overwrite Whether to overwrite an existing profile with the same name.
 * @param onComplete A function to call when the operation is complete.
 */
private fun saveProfileWithTuiLogging(
    screen: Screen,
    profileName: String,
    overwrite: Boolean,
    onComplete: () -> Unit
) {
    withTuiLogging(screen, "Profile saved successfully", onComplete) { logger ->
        transaction {
            val existingProfile = ModProfile.find { ModProfiles.name eq profileName }.singleOrNull()

            if (existingProfile != null && !overwrite) {
                logger("Error: Profile '$profileName' already exists and overwrite is not allowed")
                return@transaction
            }

            val profile = if (existingProfile == null) {
                logger("Saving new profile '$profileName'")
                ModProfile.new { name = profileName }
            } else {
                logger("Updating existing profile '$profileName'")
                existingProfile
            }

            profile.mods = UnpackedMod.find { UnpackedMods.enabled eq true }
            logger("Profile '$profileName' saved successfully")
        }
    }
}

/**
 * Delete a profile with TUI logging.
 * @param screen The terminal screen.
 * @param profileName The name of the profile to delete.
 * @param onComplete A function to call when the operation is complete.
 */
private fun deleteProfileWithTuiLogging(
    screen: Screen,
    profileName: String,
    onComplete: () -> Unit
) {
    withTuiLogging(screen, "Profile deleted successfully", onComplete) { logger ->
        transaction {
            val profile = ModProfile.find { ModProfiles.name eq profileName }.singleOrNull()
            if (profile == null) {
                logger("Error: Profile not found: $profileName")
                return@transaction
            }

            profile.delete()
            logger("Profile '$profileName' deleted successfully")
        }
    }
}

/**
 * Shows a file browser dialog and returns the selected file path.
 * @param screen The terminal screen.
 * @param startDirectory The directory to start browsing from.
 * @param fileFilter Optional filter for which files to display.
 * @return The selected file path, or null if the user cancelled.
 */
private fun showFileBrowser(
    screen: Screen,
    startDirectory: String = System.getProperty("user.home"),
    fileFilter: (File) -> Boolean
): String? {
    var currentDirectory = File(startDirectory)
    var selectedIndex = 0
    var files = listFiles(currentDirectory, fileFilter)

    while (true) {
        screen.clear()
        val tg = screen.newTextGraphics()
        val terminalSize = screen.terminalSize

        tg.foregroundColor = TextColor.ANSI.CYAN
        tg.putString(1, 0, "Select a mod file to add")
        tg.putString(1, 1, "Current directory: ${currentDirectory.absolutePath}")
        tg.putString(1, 2, "=".repeat(terminalSize.columns - 2))

        val maxVisibleFiles = terminalSize.rows - 7

        val startIndex = if (selectedIndex >= maxVisibleFiles) selectedIndex - maxVisibleFiles + 1 else 0
        val endIndex = minOf(startIndex + maxVisibleFiles, files.size)
        val displayFiles = files.subList(startIndex, endIndex)

        displayFiles.forEachIndexed { index, file ->
            val actualIndex = startIndex + index
            val isSelected = actualIndex == selectedIndex
            val fileType = when {
                file.isDirectory -> "[DIR]"
                else -> "[FILE]"
            }

            tg.foregroundColor = if (isSelected) TextColor.ANSI.BLACK else TextColor.ANSI.WHITE
            tg.backgroundColor = if (isSelected) TextColor.ANSI.YELLOW else TextColor.ANSI.DEFAULT

            val displayName = "  $fileType ${file.name}".take(terminalSize.columns - 2)
            tg.putString(1, index + 3, displayName)

            tg.backgroundColor = TextColor.ANSI.DEFAULT
        }

        tg.foregroundColor = TextColor.ANSI.YELLOW
        tg.putString(1, terminalSize.rows - 3, "Up/Down: Navigate | Enter: Select | P: Parent Directory | ESC: Cancel")

        screen.refresh()

        val keyStroke = screen.readInput()
        when (keyStroke.keyType) {
            KeyType.ArrowUp -> {
                if (selectedIndex > 0) selectedIndex--
            }

            KeyType.ArrowDown -> {
                if (selectedIndex < files.size - 1) selectedIndex++
            }

            KeyType.Enter -> {
                if (files.isNotEmpty()) {
                    val selectedFile = files[selectedIndex]
                    if (selectedFile.isDirectory) {
                        currentDirectory = selectedFile
                        files = listFiles(currentDirectory, fileFilter)
                        selectedIndex = 0
                    } else {
                        return selectedFile.absolutePath
                    }
                }
            }

            KeyType.Character -> {
                if (keyStroke.character == 'p' || keyStroke.character == 'P') {
                    currentDirectory.parent?.let {
                        currentDirectory = File(it)
                        files = listFiles(currentDirectory, fileFilter)
                        selectedIndex = 0
                    }
                }
            }

            KeyType.Escape -> {
                return null
            }

            else -> { /* Ignore other keys */
            }
        }
    }
}

/**
 * Lists files in a directory, applying a filter.
 */
private fun listFiles(directory: File, fileFilter: (File) -> Boolean): List<File> {
    val files = directory.listFiles()?.filter(fileFilter)?.sortedWith(
        compareBy<File> { !it.isDirectory }.thenBy { it.name.lowercase() }
    ) ?: emptyList()

    return files
}

/**
 * Add a mod file with TUI logging.
 * @param screen The terminal screen.
 * @param startDirectory The directory to start browsing from.
 * @param onComplete A function to call when the operation is complete.
 * @param updateStatus A function to update the status message.
 */
private fun addModWithTuiLogging(
    screen: Screen,
    startDirectory: String = System.getProperty("user.dir"),
    onComplete: () -> Unit,
    updateStatus: (String) -> Unit
) {
    val modPath = showFileBrowser(
        screen = screen,
        startDirectory = startDirectory,
        fileFilter = { file -> ModFileType.entries.map(ModFileType::extension).any { file.name.endsWith(it, true) } || file.isDirectory }
    )

    if (modPath == null) {
        updateStatus("Add mod cancelled")
        onComplete()
        return
    }

    val file = File(modPath)
    if (!file.exists()) {
        val logger = createTuiLogger(
            screen = screen,
            statusMessage = "Error",
            onComplete = onComplete
        )
        logger("Error: File does not exist: $modPath")
        updateStatus("Error: File does not exist: $modPath")
        return
    }

    withTuiLogging(screen, "Mod added successfully", onComplete) { logger ->
        val mod = ModFileAdder.addModFile(file, log = logger)
        if (mod != null) {
            logger("Mod ${mod.name} added successfully")
            updateStatus("Mod ${mod.name} added successfully")
            logger("---------------------------------------")
            logger("Press Down to return to the main menu.")
        }
    }
}

/**
 * Remove a mod with TUI logging.
 * @param screen The terminal screen.
 * @param modName The name of the mod to remove.
 * @param onComplete A function to call when the operation is complete.
 */
private fun removeModWithTuiLogging(
    screen: Screen,
    modName: String,
    onComplete: () -> Unit
) {
    withTuiLogging(screen, "Mod removed successfully", onComplete) { logger ->
        transaction {
            val mod = UnpackedMod.find { UnpackedMods.name eq modName }.singleOrNull()
            if (mod == null) {
                logger("Error: Mod not found: $modName")
                return@transaction
            }

            if (mod.isBaseGame) {
                logger("Error: Cannot remove the base game")
                return@transaction
            }

            val removed = ModFileAdder.removeMod(mod, log = logger, doConfirm = false)
            if (removed) {
                logger("Mod $modName removed successfully")
                logger("---------------------------------------")
                logger("Press Down to return to the main menu.")
            }
        }
    }
}

/**
 * Run the TUI interface.
 */
fun runTui() {
    val terminal: Terminal
    val screen: Screen

    try {
        terminal = DefaultTerminalFactory().createTerminal()
        screen = TerminalScreen(terminal)
        screen.startScreen()
        screen.cursorPosition = null
    } catch (e: IOException) {
        println("Error initializing terminal: ${e.message}")
        return
    }

    val selectedColor = TextColor.ANSI.GREEN
    val normalColor = TextColor.ANSI.WHITE
    val statusColor = TextColor.ANSI.YELLOW
    val helpColor = TextColor.ANSI.BLUE
    val highlightColor = TextColor.ANSI.RED

    val menuOptions = listOf("Manage Mods", "Profiles", "Load Mods", "Check for Updates")

    var activePane = Pane.BOTTOM
    var topSelectedIndex = 0
    var bottomSelectedIndex = 0
    var topOffset = 0
    var statusMessage = "Welcome to $modLoaderName TUI. Press 'q' to quit."
    var quit = false
    var profileListActive = false
    var selectedProfileIndex = 0
    var profileListOffset = 0
    var pickedModIndex: Int? = null

    var mods = emptyList<UnpackedMod>()
    var profiles = emptyList<ModProfile>()

    try {
        transaction {
            mods = UnpackedMod.all().sorted().toList()
            profiles = ModProfile.all().sortedBy { it.name }.toList()
        }
    } catch (e: Exception) {
        statusMessage = "Error loading mods: ${e.message}"
    }

    while (!quit) {
        screen.clear()
        val tg = screen.newTextGraphics()
        val terminalSize = screen.terminalSize

        val topPaneHeight = terminalSize.rows - 4

        drawTopPane(
            tg = tg,
            terminalSize = terminalSize,
            paneHeight = topPaneHeight,
            mods = mods,
            selectedIndex = topSelectedIndex,
            offset = topOffset,
            isActive = activePane == Pane.TOP,
            normalColor = normalColor,
            selectedColor = selectedColor,
            pickedColor = highlightColor,
            pickedModIndex = pickedModIndex,
            profileListActive = profileListActive,
            profiles = profiles,
            selectedProfileIndex = selectedProfileIndex,
            profileListOffset = profileListOffset
        )

        tg.foregroundColor = normalColor
        tg.putString(0, topPaneHeight, "─".repeat(terminalSize.columns))

        drawBottomPane(
            tg = tg,
            terminalSize = terminalSize,
            topPaneHeight = topPaneHeight,
            menuOptions = menuOptions,
            selectedIndex = bottomSelectedIndex,
            isActive = activePane == Pane.BOTTOM,
            normalColor = normalColor,
            selectedColor = selectedColor
        )

        tg.foregroundColor = statusColor
        tg.putString(0, terminalSize.rows - 1, statusMessage.take(terminalSize.columns))

        tg.foregroundColor = helpColor
        val helpText = when {
            activePane == Pane.BOTTOM -> "←/→: Navigate menu  Enter: Select  q: Quit"
            profileListActive -> "(S)ave  (L)oad  (R)emove  Esc: Back to menu"
            pickedModIndex != null -> "↑/↓: Position mod  Space: Drop  Esc: Cancel"
            else -> "(A)dd  (R)emove  (E)nable  (D)isable  (S)ort  (V)alidate  Space: Move  Esc: Back to menu"
        }

        val helpY = terminalSize.rows - 2
        tg.putString(0, helpY, helpText.take(terminalSize.columns))

        screen.refresh()

        val input = screen.readInput()

        when (input.keyType) {
            KeyType.Escape -> {
                when {
                    pickedModIndex != null -> {
                        pickedModIndex = null
                        statusMessage = "Cancelled move operation"
                    }

                    profileListActive -> {
                        profileListActive = false
                        activePane = Pane.BOTTOM
                        statusMessage = "Returned to main menu"
                    }

                    activePane == Pane.TOP -> {
                        activePane = Pane.BOTTOM
                        statusMessage = "Moved to bottom pane"
                    }

                    else -> {
                        quit = true
                        statusMessage = "Exiting..."
                    }
                }
            }

            KeyType.ArrowUp -> {
                when {
                    activePane == Pane.TOP && !profileListActive -> {
                        if (topSelectedIndex > 0) {
                            topSelectedIndex--
                            if (topSelectedIndex < topOffset) {
                                topOffset = topSelectedIndex
                            }
                            statusMessage = "Selected ${mods[topSelectedIndex].name}"
                        }
                    }

                    activePane == Pane.TOP && profileListActive -> {
                        if (selectedProfileIndex > 0) {
                            selectedProfileIndex--
                            if (selectedProfileIndex < profileListOffset) {
                                profileListOffset = selectedProfileIndex
                            }
                            if (profiles.isNotEmpty()) {
                                statusMessage = "Selected ${profiles[selectedProfileIndex].name}"
                            }
                        }
                    }
                }
            }

            KeyType.ArrowDown -> {
                when {
                    activePane == Pane.TOP && !profileListActive -> {
                        if (topSelectedIndex < mods.size - 1) {
                            topSelectedIndex++
                            val visibleMods = topPaneHeight - 4
                            if (topSelectedIndex >= topOffset + visibleMods) {
                                topOffset = topSelectedIndex - visibleMods + 1
                            }
                            statusMessage = "Selected ${mods[topSelectedIndex].name}"
                        }
                    }

                    activePane == Pane.TOP && profileListActive -> {
                        if (selectedProfileIndex < profiles.size - 1) {
                            selectedProfileIndex++
                            val visibleProfiles = topPaneHeight - 4
                            if (selectedProfileIndex >= profileListOffset + visibleProfiles) {
                                profileListOffset = selectedProfileIndex - visibleProfiles + 1
                            }
                            if (profiles.isNotEmpty()) {
                                statusMessage = "Selected ${profiles[selectedProfileIndex].name}"
                            }
                        }
                    }
                }
            }

            KeyType.ArrowLeft -> {
                if (activePane == Pane.BOTTOM && bottomSelectedIndex > 0) {
                    bottomSelectedIndex--
                    statusMessage = "Selected ${menuOptions[bottomSelectedIndex]}"
                }
            }

            KeyType.ArrowRight -> {
                if (activePane == Pane.BOTTOM && bottomSelectedIndex < menuOptions.size - 1) {
                    bottomSelectedIndex++
                    statusMessage = "Selected ${menuOptions[bottomSelectedIndex]}"
                }
            }

            KeyType.Enter -> {
                when {
                    activePane == Pane.BOTTOM -> {
                        when (bottomSelectedIndex) {
                            0 -> {
                                activePane = Pane.TOP
                                profileListActive = false
                                statusMessage = "Manage Mods: Use arrow keys to navigate, see help for commands"
                            }

                            1 -> {
                                activePane = Pane.TOP
                                profileListActive = true
                                statusMessage = "Profiles: Use arrow keys to navigate, see help for commands"

                                try {
                                    transaction {
                                        profiles = ModProfile.all().sortedBy { it.name }.toList()
                                    }
                                } catch (e: Exception) {
                                    statusMessage = "Error loading profiles: ${e.message}"
                                }
                            }

                            2 -> {
                                val textGraphics = screen.newTextGraphics()
                                val terminalSize1 = screen.terminalSize

                                screen.clear()

                                textGraphics.foregroundColor = TextColor.ANSI.CYAN
                                textGraphics.putString(1, 0, "Load Mods")
                                textGraphics.putString(1, 1, "=".repeat(terminalSize1.columns - 2))

                                textGraphics.foregroundColor = TextColor.ANSI.WHITE
                                textGraphics.putString(1, 3, "Copy files instead of using symbolic links? (y/n)")

                                textGraphics.foregroundColor = TextColor.ANSI.BLUE
                                textGraphics.putString(1, terminalSize1.rows - 2, "Y: Yes   N: No   Esc: Cancel")

                                screen.refresh()

                                var copyFiles = false
                                var cleanModded = false
                                var inputDone = false
                                var cancelled = false

                                while (!inputDone) {
                                    val keyStroke = screen.readInput()

                                    when (keyStroke.keyType) {
                                        KeyType.Character -> {
                                            when (keyStroke.character.lowercaseChar()) {
                                                'y' -> {
                                                    copyFiles = true
                                                    inputDone = true
                                                }

                                                'n' -> {
                                                    copyFiles = false
                                                    inputDone = true
                                                }
                                            }
                                        }

                                        KeyType.Escape -> {
                                            cancelled = true
                                            inputDone = true
                                        }

                                        else -> {
                                        }
                                    }
                                }

                                if (!cancelled) {
                                    screen.clear()

                                    textGraphics.foregroundColor = TextColor.ANSI.CYAN
                                    textGraphics.putString(1, 0, "Load Mods")
                                    textGraphics.putString(1, 1, "=".repeat(terminalSize1.columns - 2))

                                    textGraphics.foregroundColor = TextColor.ANSI.WHITE
                                    textGraphics.putString(1, 3, "Clean modded directory before loading? (y/n)")

                                    textGraphics.foregroundColor = TextColor.ANSI.BLUE
                                    textGraphics.putString(1, terminalSize1.rows - 2, "Y: Yes   N: No   Esc: Cancel")

                                    screen.refresh()

                                    inputDone = false

                                    while (!inputDone) {
                                        val keyStroke = screen.readInput()

                                        when (keyStroke.keyType) {
                                            KeyType.Character -> {
                                                when (keyStroke.character.lowercaseChar()) {
                                                    'y' -> {
                                                        cleanModded = true
                                                        inputDone = true
                                                    }

                                                    'n' -> {
                                                        cleanModded = false
                                                        inputDone = true
                                                    }
                                                }
                                            }

                                            KeyType.Escape -> {
                                                cancelled = true
                                                inputDone = true
                                            }

                                            else -> {
                                            }
                                        }
                                    }

                                    if (!cancelled) {
                                        Settings.copyInsteadOfLink = copyFiles
                                        Settings.cleanModdedOnLoad = cleanModded

                                        loadModsWithTuiLogging(
                                            screen = screen,
                                            copyFiles = copyFiles,
                                            cleanModded = cleanModded,
                                            onComplete = {
                                                statusMessage = "Mods loaded successfully"
                                            }
                                        )
                                    } else {
                                        statusMessage = "Load mods cancelled"
                                    }
                                } else {
                                    statusMessage = "Load mods cancelled"
                                }
                            }

                            3 -> {
                                checkForUpdatesWithTuiLogging(
                                    screen = screen,
                                    onComplete = {
                                        statusMessage = "Update check completed"
                                    }
                                )
                            }
                        }
                    }

                    activePane == Pane.TOP && profileListActive -> {
                        if (profiles.isNotEmpty() && selectedProfileIndex < profiles.size) {
                            statusMessage =
                                "Selected profile: ${profiles[selectedProfileIndex].name}. Press S to save, L to load, R to remove."
                        }
                    }
                }
            }

            KeyType.Character -> {
                when (input.character) {
                    'q' -> {
                        quit = true
                        statusMessage = "Exiting..."
                    }

                    'a' -> {
                        if (activePane == Pane.TOP && !profileListActive) {
                            addModWithTuiLogging(
                                screen = screen,
                                startDirectory = System.getProperty("user.dir"),
                                onComplete = {
                                    try {
                                        transaction {
                                            mods = UnpackedMod.all().sorted().toList()
                                        }
                                    } catch (e: Exception) {
                                        statusMessage = "Error refreshing mods: ${e.message}"
                                    }
                                    statusMessage = "Add mod operation completed"
                                },
                                updateStatus = { message ->
                                    statusMessage = message
                                }
                            )
                        }
                    }

                    'r' -> {
                        if (activePane == Pane.TOP) {
                            if (!profileListActive) {
                                if (mods.isNotEmpty() && topSelectedIndex < mods.size) {
                                    val mod = mods[topSelectedIndex]

                                    if (!mod.isBaseGame) {
                                        val textGraphics = screen.newTextGraphics()
                                        val terminalSize1 = screen.terminalSize
                                        screen.clear()
                                        textGraphics.foregroundColor = TextColor.ANSI.CYAN
                                        textGraphics.putString(1, 0, "Remove Mod")
                                        textGraphics.putString(1, 1, "=".repeat(terminalSize1.columns - 2))
                                        textGraphics.foregroundColor = TextColor.ANSI.WHITE
                                        textGraphics.putString(1, 3, "Are you sure you want to remove ${mod.name}?")
                                        textGraphics.foregroundColor = TextColor.ANSI.BLUE
                                        textGraphics.putString(1, terminalSize1.rows - 2, "Y: Yes   N: No")

                                        screen.refresh()

                                        var confirm = false
                                        var inputDone = false

                                        while (!inputDone) {
                                            val keyStroke = screen.readInput()

                                            when (keyStroke.keyType) {
                                                KeyType.Character -> {
                                                    when (keyStroke.character.lowercaseChar()) {
                                                        'y' -> {
                                                            confirm = true
                                                            inputDone = true
                                                        }

                                                        'n' -> {
                                                            confirm = false
                                                            inputDone = true
                                                        }
                                                    }
                                                }

                                                KeyType.Escape -> {
                                                    confirm = false
                                                    inputDone = true
                                                }

                                                else -> {
                                                }
                                            }
                                        }

                                        if (confirm) {
                                            removeModWithTuiLogging(
                                                screen = screen,
                                                modName = mod.name,
                                                onComplete = {
                                                    try {
                                                        transaction {
                                                            mods = UnpackedMod.all().sorted().toList()
                                                        }

                                                    } catch (e: Exception) {
                                                        statusMessage = "Error refreshing mods: ${e.message}"
                                                    }
                                                    statusMessage = "Mod removed successfully"
                                                }
                                            )
                                        } else {
                                            statusMessage = "Removal cancelled"
                                        }
                                    } else {
                                        statusMessage = "Cannot remove the base game"
                                    }
                                }
                            } else if (profileListActive && profiles.isNotEmpty() && selectedProfileIndex < profiles.size) {
                                val profile = profiles[selectedProfileIndex]

                                val textGraphics = screen.newTextGraphics()
                                val terminalSize1 = screen.terminalSize

                                screen.clear()

                                textGraphics.foregroundColor = TextColor.ANSI.CYAN
                                textGraphics.putString(1, 0, "Remove Profile")
                                textGraphics.putString(1, 1, "=".repeat(terminalSize1.columns - 2))

                                textGraphics.foregroundColor = TextColor.ANSI.WHITE
                                textGraphics.putString(1, 3, "Are you sure you want to remove profile ${profile.name}?")

                                textGraphics.foregroundColor = TextColor.ANSI.BLUE
                                textGraphics.putString(1, terminalSize1.rows - 2, "Y: Yes   N: No")

                                screen.refresh()

                                var confirm = false
                                var inputDone = false

                                while (!inputDone) {
                                    val keyStroke = screen.readInput()

                                    when (keyStroke.keyType) {
                                        KeyType.Character -> {
                                            when (keyStroke.character.lowercaseChar()) {
                                                'y' -> {
                                                    confirm = true
                                                    inputDone = true
                                                }

                                                'n' -> {
                                                    confirm = false
                                                    inputDone = true
                                                }
                                            }
                                        }

                                        KeyType.Escape -> {
                                            confirm = false
                                            inputDone = true
                                        }

                                        else -> {
                                        }
                                    }
                                }

                                if (confirm) {
                                    deleteProfileWithTuiLogging(
                                        screen = screen,
                                        profileName = profile.name,
                                        onComplete = {
                                            try {
                                                transaction {
                                                    profiles = ModProfile.all().sortedBy { it.name }.toList()
                                                }
                                            } catch (e: Exception) {
                                                statusMessage = "Error refreshing profiles: ${e.message}"
                                            }
                                            statusMessage = "Profile removed successfully"
                                        }
                                    )
                                } else {
                                    statusMessage = "Removal cancelled"
                                }
                            }
                        }
                    }

                    'e' -> {
                        if (activePane == Pane.TOP && !profileListActive) {
                            if (mods.isNotEmpty() && topSelectedIndex < mods.size) {
                                val mod = mods[topSelectedIndex]

                                if (!mod.isBaseGame) {
                                    try {
                                        transaction {
                                            mod.enabled = true
                                            mods = UnpackedMod.all().sorted().toList()
                                        }
                                        statusMessage = "Mod ${mod.name} enabled"
                                    } catch (e: Exception) {
                                        statusMessage = "Error enabling mod: ${e.message}"
                                    }
                                } else {
                                    statusMessage = "Cannot enable/disable the base game"
                                }
                            }
                        }
                    }

                    'd' -> {
                        if (activePane == Pane.TOP && !profileListActive) {
                            if (mods.isNotEmpty() && topSelectedIndex < mods.size) {
                                val mod = mods[topSelectedIndex]

                                if (!mod.isBaseGame) {
                                    try {
                                        transaction {
                                            mod.enabled = false
                                            mods = UnpackedMod.all().sorted().toList()
                                        }
                                        statusMessage = "Mod ${mod.name} disabled"
                                    } catch (e: Exception) {
                                        statusMessage = "Error disabling mod: ${e.message}"
                                    }
                                } else {
                                    statusMessage = "Cannot enable/disable the base game"
                                }
                            }
                        }
                    }

                    's' -> {
                        if (activePane == Pane.TOP) {
                            if (!profileListActive) {
                                try {
                                    sortModLoadOrderWithTuiLogging(
                                        screen = screen,
                                        onComplete = {
                                            transaction {
                                                mods = UnpackedMod.all().sorted().toList()
                                            }
                                            statusMessage = "Mods sorted successfully"
                                        }
                                    )
                                } catch (e: CircularLoadOrderException) {
                                    statusMessage = "Error sorting mods: Circular dependency detected"
                                } catch (e: Exception) {
                                    statusMessage = "Error sorting mods: ${e.message}"
                                }
                            } else if (profileListActive) {
                                val textGraphics = screen.newTextGraphics()
                                val terminalSize1 = screen.terminalSize

                                screen.clear()

                                textGraphics.foregroundColor = TextColor.ANSI.CYAN
                                textGraphics.putString(1, 0, "Save Profile")
                                textGraphics.putString(1, 1, "=".repeat(terminalSize1.columns - 2))

                                textGraphics.foregroundColor = TextColor.ANSI.WHITE
                                textGraphics.putString(1, 3, "Enter profile name:")

                                textGraphics.putString(1, 5, "> ")

                                textGraphics.foregroundColor = TextColor.ANSI.BLUE
                                textGraphics.putString(1, terminalSize1.rows - 2, "Enter: Confirm   Esc: Cancel")

                                screen.refresh()

                                val profileName = StringBuilder()
                                var inputDone = false

                                while (!inputDone) {
                                    val keyStroke = screen.readInput()

                                    when (keyStroke.keyType) {
                                        KeyType.Escape -> {
                                            inputDone = true
                                            statusMessage = "Save profile cancelled"
                                        }

                                        KeyType.Enter -> {
                                            inputDone = true

                                            if (profileName.isNotBlank()) {
                                                var existingProfile: ModProfile? = null
                                                var confirm = false

                                                try {
                                                    transaction {
                                                        existingProfile =
                                                            ModProfile.find { ModProfiles.name eq profileName.toString() }
                                                                .singleOrNull()
                                                    }

                                                    if (existingProfile != null) {
                                                        screen.clear()

                                                        textGraphics.foregroundColor = TextColor.ANSI.CYAN
                                                        textGraphics.putString(1, 0, "Confirm Overwrite")
                                                        textGraphics.putString(
                                                            1,
                                                            1,
                                                            "=".repeat(terminalSize1.columns - 2)
                                                        )

                                                        textGraphics.foregroundColor = TextColor.ANSI.WHITE
                                                        textGraphics.putString(
                                                            1,
                                                            3,
                                                            "Profile already exists. Overwrite?"
                                                        )

                                                        textGraphics.foregroundColor = TextColor.ANSI.BLUE
                                                        textGraphics.putString(
                                                            1,
                                                            terminalSize1.rows - 2,
                                                            "Y: Yes   N: No"
                                                        )

                                                        screen.refresh()

                                                        var confirmDone = false

                                                        while (!confirmDone) {
                                                            val confirmInput = screen.readInput()

                                                            when (confirmInput.keyType) {
                                                                KeyType.Character -> {
                                                                    when (confirmInput.character.lowercaseChar()) {
                                                                        'y' -> {
                                                                            confirm = true
                                                                            confirmDone = true
                                                                        }

                                                                        'n' -> {
                                                                            confirm = false
                                                                            confirmDone = true
                                                                        }
                                                                    }
                                                                }

                                                                KeyType.Escape -> {
                                                                    confirm = false
                                                                    confirmDone = true
                                                                }

                                                                else -> {
                                                                }
                                                            }
                                                        }
                                                    } else {
                                                        confirm = true
                                                    }

                                                    if (confirm) {
                                                        saveProfileWithTuiLogging(
                                                            screen = screen,
                                                            profileName = profileName.toString(),
                                                            overwrite = existingProfile != null,
                                                            onComplete = {
                                                                try {
                                                                    transaction {
                                                                        profiles = ModProfile.all().sortedBy { it.name }
                                                                            .toList()
                                                                    }
                                                                } catch (e: Exception) {
                                                                    statusMessage =
                                                                        "Error refreshing profiles: ${e.message}"
                                                                }
                                                                statusMessage =
                                                                    if (existingProfile != null) "Profile updated successfully" else "Profile saved successfully"
                                                            }
                                                        )
                                                    } else {
                                                        statusMessage = "Save cancelled"
                                                    }
                                                } catch (e: Exception) {
                                                    statusMessage = "Error checking profile: ${e.message}"
                                                }
                                            } else {
                                                statusMessage = "No name entered, save cancelled"
                                            }
                                        }

                                        KeyType.Backspace -> {
                                            if (profileName.isNotEmpty()) {
                                                profileName.deleteCharAt(profileName.length - 1)

                                                textGraphics.foregroundColor = TextColor.ANSI.WHITE
                                                textGraphics.putString(3, 5, " ".repeat(terminalSize1.columns - 4))
                                                textGraphics.putString(3, 5, profileName.toString())
                                                screen.refresh()
                                            }
                                        }

                                        KeyType.Character -> {
                                            profileName.append(keyStroke.character)

                                            textGraphics.foregroundColor = TextColor.ANSI.WHITE
                                            textGraphics.putString(3, 5, profileName.toString())
                                            screen.refresh()
                                        }

                                        else -> {
                                        }
                                    }
                                }
                            }
                        }
                    }

                    'v' -> {
                        if (activePane == Pane.TOP && !profileListActive) {
                            try {
                                validateModLoadOrderWithTuiLogging(
                                    screen = screen,
                                    onComplete = {
                                        statusMessage = "Validation completed"
                                    }
                                )
                            } catch (e: Exception) {
                                statusMessage = "Error validating mods: ${e.message}"
                            }
                        }
                    }

                    'l' -> {
                        if (activePane == Pane.TOP && profileListActive && profiles.isNotEmpty() && selectedProfileIndex < profiles.size) {
                            val profile = profiles[selectedProfileIndex]

                            try {
                                transaction {
                                    UnpackedMods.update { it[enabled] = false }
                                    profile.mods.forEach { it.enabled = true }
                                    mods = UnpackedMod.all().sorted().toList()
                                }
                                statusMessage = "Profile ${profile.name} loaded successfully"
                            } catch (e: Exception) {
                                statusMessage = "Error loading profile: ${e.message}"
                            }
                        }
                    }

                    ' ' -> {
                        if (activePane == Pane.TOP && !profileListActive && mods.isNotEmpty() && topSelectedIndex < mods.size) {
                            val mod = mods[topSelectedIndex]

                            if (!mod.isBaseGame) {
                                if (pickedModIndex == null) {
                                    pickedModIndex = topSelectedIndex
                                    statusMessage = "Picked up ${mod.name}. Use ↑/↓ to position, Space to drop."
                                } else {
                                    val pickIndex = pickedModIndex
                                    val pickedMod = mods[pickIndex]

                                    if (pickIndex == topSelectedIndex || topSelectedIndex == 0) {
                                        pickedModIndex = null
                                        statusMessage = "Dropped ${pickedMod.name} (no change)"
                                    } else {
                                        try {
                                            transaction {
                                                val pickedModLoadOrder = pickedMod.loadOrder ?: 0
                                                val targetLoadOrder = if (topSelectedIndex < mods.size) {
                                                    mods[topSelectedIndex].loadOrder ?: 0
                                                } else {
                                                    (mods.mapNotNull { it.loadOrder }.maxOrNull() ?: 0) + 1
                                                }

                                                if (pickedModLoadOrder < targetLoadOrder) {
                                                    mods.forEach { mod ->
                                                        val modLoadOrder = mod.loadOrder ?: 0
                                                        if (modLoadOrder in (pickedModLoadOrder + 1)..targetLoadOrder && !mod.isBaseGame && mod.id != pickedMod.id) {
                                                            mod.loadOrder = modLoadOrder - 1
                                                        }
                                                    }
                                                } else if (pickedModLoadOrder > targetLoadOrder) {
                                                    mods.forEach { mod ->
                                                        val modLoadOrder = mod.loadOrder ?: 0
                                                        if (modLoadOrder in targetLoadOrder..<pickedModLoadOrder && !mod.isBaseGame && mod.id != pickedMod.id) {
                                                            mod.loadOrder = modLoadOrder + 1
                                                        }
                                                    }
                                                }

                                                pickedMod.loadOrder = targetLoadOrder

                                                mods = UnpackedMod.all().sorted().toList()
                                            }

                                            val newIndex = mods.indexOfFirst { it.id == pickedMod.id }
                                            if (newIndex != -1) {
                                                topSelectedIndex = newIndex
                                            }

                                            statusMessage = "Moved ${pickedMod.name} to new position"
                                        } catch (e: Exception) {
                                            statusMessage = "Error moving mod: ${e.message}"
                                        }

                                        pickedModIndex = null
                                    }
                                }
                            } else {
                                statusMessage = "Cannot move the base game"
                            }
                        }
                    }
                }
            }

            else -> {
            }
        }
    }

    try {
        screen.stopScreen()
    } catch (e: IOException) {
        println("Error stopping screen: ${e.message}")
    }
}


code:
package life.xchange.ui

import life.xchange.db.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction

class AllPassagesTable : FinalTable({
  transaction {
    val aroundPassages = UnpackedModPassages.alias("around")
    val aroundMods = UnpackedMods.alias("aroundMods")

    UnpackedModPassages
      .innerJoin(UnpackedMods)
      .leftJoin(aroundPassages, additionalConstraint = {
        (UnpackedModPassages.name eq aroundPassages[UnpackedModPassages.name]) and
            (stringLiteral("around") eq anyFrom(aroundPassages[UnpackedModPassages.tags]))
      })
      .leftJoin(aroundMods, { aroundPassages[UnpackedModPassages.mod] }, { aroundMods[UnpackedMods.id] })
      .selectAll()
      .where {
        (UnpackedMods.loadOrder neq null) and
            not(stringLiteral("around") eq anyFrom(UnpackedModPassages.tags))
      }
      .orderBy(UnpackedModPassages.name.lowerCase() to SortOrder.ASC, aroundMods[UnpackedMods.loadOrder] to SortOrder.ASC)
      .groupBy { it[UnpackedModPassages.name] }
      .map { (name, list) ->
        @Suppress("UselessCallOnCollection")
        FinalMod(name, list.maxBy { it[UnpackedMods.loadOrder]!! }[UnpackedMods.name],
          list.mapNotNull { it[aroundMods[UnpackedMods.name]] }.distinct().joinToString())
      }
  }
}, nameColumn, providedByColumn, aroundedByColumn)

class AllFilesTable : FinalTable({
  transaction {
    UnpackedModFiles
      .innerJoin(UnpackedMods)
      .selectAll()
      .where {
        (UnpackedMods.loadOrder neq null) and
            (UnpackedModFiles.path notInList UnpackedModFile.specialNames)
      }
      .orderBy(UnpackedModFiles.path.lowerCase())
      .groupBy { it[UnpackedModFiles.path] }
      .map { (path, list) -> FinalMod(path, list.maxBy { it[UnpackedMods.loadOrder]!! }[UnpackedMods.name])}
  }
}, nameColumn, providedByColumn) {
  val fileTypeFilterer by lazy { buildFileFilterer(FinalMod::name) }
}

fun <T, M : DefinedTableModel<T>> DefinedTable<T, M>.fileFilterOptions(getName: (T) -> String): Array<String> = arrayOf(
  "All files",
  *tableModel.items.map(getName).map { name ->
    when {
      '.' in name -> "*." + name.takeLastWhile { it != '.' }
      else -> name
    }.lowercase()
  }.distinct().sorted().toTypedArray()
)
fun <T, M : DefinedTableModel<T>> DefinedTable<T, M>.buildFileFilterer(getName: (T) -> String) =
  FileFilterer(this, fileFilterOptions(getName), getName)

private const val nameColumn = "Name"
private const val providedByColumn = "Provided By"
private const val aroundedByColumn = "Arounded By"

data class FinalMod(
  val name: String,
  val providedBy: String,
  val aroundedBy: String = "",
)

open class FinalTable(private val itemLoader: () -> List<FinalMod>, private vararg val columnNames: String) : DefinedTable<FinalMod, FinalTable.FinalTableModel>() {
  inner class FinalTableModel : DefinedTableModel<FinalMod>(listOf(
    ColumnDefinition(FinalMod::name, String::class.java, nameColumn, 600),
    ColumnDefinition(FinalMod::providedBy, String::class.java, providedByColumn, 350),
    ColumnDefinition(FinalMod::aroundedBy, String::class.java, aroundedByColumn, 600),
  ).filter { it.name in columnNames }) {
    override fun loadItems() = itemLoader()
  }

  override val tableModel = FinalTableModel()

  init {
    initTableStuff()
  }
}

repo_name: xchange/life

license: unknown


code:
package life.xchange.ui

data class ColumnDefinition<T, V : Comparable<V>>(
  val property: (T) -> Any?,
  val typeClass: Class<V>,
  val name: String,
  val preferredWidth: Int,
  val sorter: Comparator<V>? = null,
)


code:
package life.xchange.ui

import java.awt.*
import javax.swing.JCheckBox
import javax.swing.JLabel
import javax.swing.JPanel
import javax.swing.JSeparator
import kotlin.reflect.KMutableProperty0

open class ColumnedFlowPanel(protected val columns: Int) : JPanel() {
  private var currentRow = 0
  private var currentColumn = 0

  protected val defaults: GridBagConstraints.() -> Unit = {
    anchor = GridBagConstraints.WEST
    fill = GridBagConstraints.BOTH
    insets = Insets(5, 5, 5, 5)
    weightx = 1.0
    weighty = 1.0
  }

  private fun add(comp: Component, row: Int, column: Int, colspan: Int, constraints: GridBagConstraints.() -> Unit) {
    add(comp, GridBagConstraints().apply {
      defaults()
      constraints()
      gridx = column
      gridy = row
      gridwidth = colspan
    })
  }

  fun addNext(comp: Component, colspan: Int = 1, constraints: GridBagConstraints.() -> Unit = {}) {
    if (currentColumn + colspan > columns) {
      nextRow()
    }

    add(comp, currentRow, currentColumn, colspan, constraints)

    currentColumn += colspan
  }

  fun heading(text: String) {
    addNext(JLabel(text).apply {
      font = font.deriveFont(Font.BOLD + Font.ITALIC)
    }, columns)
  }

  fun setting(setting: KMutableProperty0<Boolean>, text: String, colspan: Int = 1, onSave: (Boolean) -> Unit = {}) = JCheckBox(
    text
  ).also { box ->
    box.addActionListener {
      setting.set(box.isSelected)
      onSave(box.isSelected)
    }
    box.isSelected = setting.get()
    addNext(box, colspan)
  }

  fun separator(block: JSeparator.() -> Unit = {}) {
    addNext(JSeparator().apply(block), columns)
  }

  fun nextRow() {
    currentRow++
    currentColumn = 0
  }

  init {
    layout = GridBagLayout()
  }
}


code:
package life.xchange.ui

import java.awt.*
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.io.IOException
import javax.imageio.ImageIO
import javax.swing.JWindow

object CustomSplash {
    private var window: JWindow? = null
    private var duration: Long = 0

    private val splashImage: Image? by lazy {
        try {
            ImageIO.read(javaClass.classLoader.getResourceAsStream("splash.png"))
        } catch (e: IOException) {
            println("Could not load splash image: ${e.message}")
            null
        }
    }

    /**
     * Display the splash screen
     * @param minDuration minimum display time in milliseconds, 0 for no minimum
     */
    fun show(minDuration: Long = 0) {
        if (splashImage == null) return

        duration = if (minDuration > 0) System.currentTimeMillis() + minDuration else 0

        window = JWindow().apply {
            val width = splashImage!!.getWidth(null)
            val height = splashImage!!.getHeight(null)

            size = Dimension(width, height)
            setLocationRelativeTo(null)

            contentPane = object : Container() {
                override fun paint(g: Graphics) {
                    g.drawImage(splashImage, 0, 0, this)
                }
            }

            addMouseListener(object : MouseAdapter() {
                override fun mouseClicked(e: MouseEvent) {
                    close()
                }
            })

            isVisible = true
        }
    }

    /**
     * Close the splash screen if it's been displayed for at least the minimum duration
     */
    fun close() {
        if (duration > 0) {
            val waitTime = duration - System.currentTimeMillis()
            if (waitTime > 0) {
                try {
                    Thread.sleep(waitTime)
                } catch (e: InterruptedException) {
                    // Ignore
                }
            }
        }

        window?.let {
            it.isVisible = false
            it.dispose()
            window = null
        }
    }
}


code:
package life.xchange.ui

import javax.swing.JTable
import javax.swing.RowFilter
import javax.swing.table.TableRowSorter

abstract class DefinedTable<T, M : DefinedTableModel<T>> : JTable() {
  abstract val tableModel: M
  private val sorter by lazy { TableRowSorter(tableModel) }
  private val filterers = mutableSetOf<TableFilterer<T>>()

  val selectedItems: Collection<T> get() = selectedRows.map(::convertRowIndexToModel).mapNotNull(tableModel::getRowAt)
  val selectedItem: T? get() = tableModel.getRowAt(convertRowIndexToModel(selectedRow))

  fun initTableStuff() {
    model = tableModel
    tableModel.setColumnWidths(columnModel)
    autoResizeMode = AUTO_RESIZE_NEXT_COLUMN
    rowSorter = sorter
    tableModel.columns.forEachIndexed { index, colDef ->
      colDef.sorter?.let { sorter.setComparator(index, it) }
    }
  }

  fun addFilterer(filterer: TableFilterer<T>) {
    filterers.add(filterer)
  }

  fun filter() {
    sorter.rowFilter = object : RowFilter<M, Int>() {
      override fun include(entry: Entry<out M, out Int>): Boolean {
        val row = tableModel.getRowAt(entry.identifier) ?: return false

        return filterers.all { it.isEmpty() || it.filter(row) }
      }
    }
  }

  fun filter(predicate: (T) -> Boolean) {
    sorter.rowFilter = object : RowFilter<M, Int>() {
      override fun include(entry: Entry<out M, out Int>): Boolean =
        tableModel.getRowAt(entry.identifier)?.let(predicate) ?: false
    }
  }
}

interface TableFilterer<T> {
  fun isEmpty(): Boolean
  fun filter(value: T): Boolean
}


code:
package life.xchange.ui

import life.xchange.onValueChange
import java.awt.event.ItemEvent
import javax.swing.*

class DefinedTableFilterPanel<T, M : DefinedTableModel<T>, D : DefinedTable<T, M>, S : JScrollPane>(
  table: D,
  getName: (T) -> String,
  scrollMaker: (D) -> S,
  getFileFilter: (D.() -> FileFilterer<T>)? = null,
) : JPanel() {
  init {
    layout = BoxLayout(this, BoxLayout.PAGE_AXIS)
    add(NameFilterer(table, getName))
    add(scrollMaker(table))
    getFileFilter?.let {
      add(it.invoke(table))
    }
  }
}

class NameFilterer<T>(private val table: DefinedTable<T, *>, private val getName: (T) -> String) : JTextField(), TableFilterer<T> {
  override fun isEmpty() = text.isEmpty()
  override fun filter(value: T) = getName(value).lowercase().contains(text.lowercase())

  init {
    table.addFilterer(this)
    onValueChange { table.filter() }
  }
}

class FileFilterer<T>(private val table: DefinedTable<T, *>, items: Array<String>, val getName: (T) -> String) : JComboBox<String>(items),
  TableFilterer<T> {
  override fun isEmpty() = selectedItem == "All files"
  override fun filter(value: T): Boolean =
    getName(value).lowercase().endsWith(selectedItem?.toString()?.replace("*", "") ?: "")

  init {
    table.addFilterer(this)
    addItemListener { if (it.stateChange == ItemEvent.SELECTED) table.filter() }
  }
}


code:
package life.xchange.ui

import org.jetbrains.exposed.dao.Entity
import javax.swing.table.AbstractTableModel
import javax.swing.table.TableColumnModel

abstract class DefinedEntityTableModel<T : Entity<*>>(columns: List<ColumnDefinition<T, *>>, autoLoad: Boolean = true) : DefinedTableModel<T>(columns, autoLoad) {
  fun refresh() {
    val newItems = loadItems()

    val onlyUpdates = items.size == newItems.size
        && items.map { it.id }.zip(newItems.map { it.id }).all { it.first == it.second }

    items.clear()
    items.addAll(newItems)

    if (onlyUpdates) {
      // don't clear the current selection by refreshing the whole table, just update the row data
      fireTableRowsUpdated(0, items.size - 1)
    } else {
      fireTableDataChanged()
    }
  }
}

abstract class DefinedTableModel<T>(val columns: List<ColumnDefinition<T, *>>, autoLoad: Boolean = true) : AbstractTableModel() {
  val items: MutableList<T> = mutableListOf()

  abstract fun loadItems(): List<T>

  override fun getColumnName(column: Int) = columns[column].name

  override fun getColumnClass(column: Int) = columns[column].typeClass
  override fun getColumnCount() = columns.size
  override fun getRowCount() = items.size
  override fun getValueAt(row: Int, column: Int) = columns[column].property(items[row])
  fun setColumnWidths(columnModel: TableColumnModel) {
    columns.forEachIndexed { index, columnDefinition ->
      columnModel.getColumn(index).preferredWidth = columnDefinition.preferredWidth
    }
  }

  fun getRowAt(row: Int) = when (row) {
    in 0 until items.size -> items[row]
    else -> null
  }

  init {
    if (autoLoad) this.items.addAll(this.loadItems())
  }
}


code:
package life.xchange.ui

import life.xchange.close
import life.xchange.gui
import java.awt.Component
import java.awt.GraphicsConfiguration
import java.awt.event.ActionEvent
import java.awt.event.KeyEvent
import javax.swing.AbstractAction
import javax.swing.JFrame
import javax.swing.JTable.WHEN_IN_FOCUSED_WINDOW
import javax.swing.KeyStroke

class EscFrame(title: String = "", graphicsConfiguration: GraphicsConfiguration? = null) : JFrame(title, graphicsConfiguration) {
  init {
    rootPane.getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "Close")
    rootPane.actionMap.put("Close", object : AbstractAction() {
      override fun actionPerformed(p0: ActionEvent?) {
        close()
      }
    })
    defaultCloseOperation = DISPOSE_ON_CLOSE
  }

  companion object {
    fun show(title: String, vararg components: Component, parent: Component? = gui, init: EscFrame.() -> Unit = {}) {
      EscFrame(title).apply {
        components.forEach(::add)
        pack()
        setLocationRelativeTo(parent)
        init()
        isVisible = true
      }
    }
  }
}


code:
package life.xchange.ui

import life.xchange.db.UnpackedMod
import life.xchange.db.UnpackedModFile
import life.xchange.db.UnpackedModFiles
import life.xchange.db.UnpackedMods
import org.jetbrains.exposed.sql.alias
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.innerJoin
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction

class FileTable(private val mod: UnpackedMod) : OverwrittenTable({
  transaction {
    val otherMods = UnpackedMods.alias("otherMods")
    val otherFiles = UnpackedModFiles.alias("otherFiles")

    val others = UnpackedModFiles
      .innerJoin(otherFiles, { path }, { get(UnpackedModFiles.path) })
      .innerJoin(otherMods, { otherFiles[UnpackedModFiles.mod] }, { otherMods[UnpackedMods.id] })
      .selectAll()
      .where {
        (UnpackedModFiles.mod eq mod.id) and
            (otherMods[UnpackedMods.loadOrder] neq null) and
            (otherFiles[UnpackedModFiles.mod] neq mod.id)
      }
      .groupBy { it[UnpackedModFiles.path] }
      .mapValues { (_, list) -> list.map { UnpackedMod.wrapRow(it, otherMods) } }

    mod.files.filterNot(UnpackedModFile::isSpecial).map { file ->
      OverwrittenMod(file, file.path) { name -> others[name] ?: listOf() }
    }.sorted()
  }
}) {
  val fileTypeFilterer by lazy { buildFileFilterer(OverwrittenMod<*, *>::name) }

  override fun load() {
    super.load()

    fileTypeFilterer.removeAllItems()
    fileFilterOptions(OverwrittenMod<*, *>::name).forEach { fileTypeFilterer.addItem(it) }
  }
}


code:
package life.xchange.ui

import life.xchange.compatibility.ModLoadOrder
import life.xchange.db.ModReference
import life.xchange.db.UnpackedMod
import life.xchange.fullyExpandTree
import life.xchange.util.capitalize
import org.jetbrains.exposed.sql.transactions.transaction
import java.text.NumberFormat
import javax.swing.JScrollPane
import javax.swing.JTree
import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.DefaultTreeModel

class ModInfoDisplay(mod: UnpackedMod) : JScrollPane() {
  private val treeModel = DefaultTreeModel(modNode(mod))
  private val tree = JTree(treeModel)

  init {
    setViewportView(tree)
    fullyExpandTree(tree)
  }

  private fun modNode(mod: UnpackedMod): DefaultMutableTreeNode = when {
    mod.metadataReadFailure != null -> metadataReadFailureAsNode(mod)
    mod.version == null -> noModInfoAsNode(mod)
    else -> modInfoAsNode(mod)
  }

  private fun noModInfoAsNode(mod: UnpackedMod): DefaultMutableTreeNode {
    return DefaultMutableTreeNode(mod.name).apply {
      add(DefaultMutableTreeNode("No metadata provided"))
      addCounts(mod)
      addReferences(mod)
    }
  }

  private fun metadataReadFailureAsNode(mod: UnpackedMod): DefaultMutableTreeNode {
    val e = mod.metadataReadFailure ?: error("${mod.name} didn't fail to load its metadata, how did we get here?")

    return DefaultMutableTreeNode(mod.name).apply {
      add(DefaultMutableTreeNode("Failed to read metadata"))
      add(DefaultMutableTreeNode(e))
      addCounts(mod)
      addReferences(mod)
    }
  }

  private fun String.countToNode(node: DefaultMutableTreeNode, num: Number) {
    val format = NumberFormat.getIntegerInstance()

    node.add(DefaultMutableTreeNode("${format.format(num)} $this${if (num.toInt() > 1) "s" else ""}"))
  }

  private fun DefaultMutableTreeNode.addCounts(mod: UnpackedMod) {
    transaction {
      mod.files.filterNot { it.isSpecial }.takeUnless { it.isEmpty() }?.let {
        "file".countToNode(this@addCounts, it.size)
      }
      mod.passages.takeUnless { it.empty() }?.let {
        "passage".countToNode(this@addCounts, it.count())
      }
    }
  }

  private fun modInfoAsNode(mod: UnpackedMod): DefaultMutableTreeNode {
    val root = DefaultMutableTreeNode(mod.name)
    listOf(
      "By ${mod.author}",
      "Version ${mod.version}",
      mod.baseGameVersion?.let { "For base game version $it" },
    ).mapNotNull { it?.let(::DefaultMutableTreeNode) }.forEach(root::add)

    root.addCounts(mod)
    root.addReferences(mod)

    return root
  }

  private fun DefaultMutableTreeNode.addReferences(mod: UnpackedMod) {
    transaction {
      mod.references.groupBy { it.relationship }.forEach { (relationship, references) ->
        references.addTo(this@addReferences, "${relationship.name.lowercase().capitalize()} Mods")
      }
    }
  }

  private fun List<ModReference>.addTo(root: DefaultMutableTreeNode, title: String) {
    if (isNotEmpty()) {
      val node = DefaultMutableTreeNode(title)
      map {
        DefaultMutableTreeNode(buildString {
          append(it.otherModName)
          if (it.versionSpec != null) append(" version ${it.versionSpec}")
          val order = it.order?.takeUnless { it == ModLoadOrder.ANY }
          if (order != null) append(" - load $order")
          if (it.isManual) append(" (manual)")
        })
      }.forEach(node::add)
      root.add(node)
    }
  }
}


code:
package life.xchange.ui

import life.xchange.*
import life.xchange.compatibility.Version
import life.xchange.db.UnpackedMod
import org.jetbrains.exposed.sql.transactions.transaction
import java.awt.Dimension
import java.awt.event.KeyEvent
import javax.swing.*
import javax.swing.event.ChangeEvent
import javax.swing.event.ListSelectionEvent
import javax.swing.event.TableColumnModelEvent
import javax.swing.event.TableColumnModelListener

class ModTable : DefinedTable<UnpackedMod, ModTable.ModTableModel>() {
  inner class ModTableModel : DefinedEntityTableModel<UnpackedMod>(columns) {
    override fun loadItems() = transaction { UnpackedMod.all().sorted() }

    override fun setValueAt(aValue: Any?, rowIndex: Int, columnIndex: Int) {
      if (columns[columnIndex].property == UnpackedMod::enabled) {
        transaction {
          items[rowIndex].enabled = aValue as Boolean
          refresh()
        }
      }
    }

    override fun isCellEditable(rowIndex: Int, columnIndex: Int): Boolean =
      !items[rowIndex].isBaseGame && columns[columnIndex].property == UnpackedMod::enabled
  }

  override val tableModel = ModTableModel()

  init {
    initTableStuff()

    var originalIndex: Int? = null
    var newIndex: Int? = null

    tableHeader.onMouse(released = {
      val from = originalIndex
      val to = newIndex
      if (from != null && to != null) {
        val newList = Settings.columnOrder.toMutableList()

        newList.add(to, newList.removeAt(from))

        Settings.columnOrder = newList
      }
      originalIndex = null
      newIndex = null
    })

    columnModel.addColumnModelListener(object : TableColumnModelListener {
      override fun columnAdded(p0: TableColumnModelEvent?) {}
      override fun columnRemoved(p0: TableColumnModelEvent?) {}
      override fun columnMarginChanged(p0: ChangeEvent?) {}
      override fun columnSelectionChanged(p0: ListSelectionEvent?) {}

      override fun columnMoved(event: TableColumnModelEvent) {
        if (originalIndex == null) originalIndex = event.fromIndex
        newIndex = event.toIndex.takeUnless { it == originalIndex }
      }
    })

    onMouse(pressed = {
      when (it.clickCount) {
        2 -> showInfo()
      }
    })

    onKey(pressed = {
      when (it.keyCode) {
        KeyEvent.VK_SPACE -> toggleEnabled()
        KeyEvent.VK_DELETE, KeyEvent.VK_BACK_SPACE -> delete()
        KeyEvent.VK_ENTER -> {
          it.consume()
          showInfo()
        }
      }
    })
  }

  fun showInfo() {
    if (selectedItems.isEmpty()) return

    val items = selectedItems

    transaction {
      ModProcessor.updateLoadOrders()
      loadMods()
    }

    items.forEach(::showModInfo)
  }

  private fun showModInfo(mod: UnpackedMod) {
    val pane = JTabbedPane().apply {
      addTab("Info", ModInfoDisplay(mod))
      addTab("Passages", DefinedTableFilterPanel(PassageTable(mod), OverwrittenMod<*, *>::name, ::OverwrittenTableScroller))
      addTab("Files", DefinedTableFilterPanel(FileTable(mod), OverwrittenMod<*, *>::name, ::OverwrittenTableScroller, FileTable::fileTypeFilterer))

      addChangeListener {
        when (val selected = selectedComponent) {
          is OverwrittenTableScroller -> selected.loadTable()
          is JPanel -> selected.components.filterIsInstance<OverwrittenTableScroller>()
            .forEach(OverwrittenTableScroller::loadTable)
        }
      }

      preferredSize = Dimension(900, 400)
    }

    EscFrame.show("Mod Information - ${mod.name}", pane)
  }

  private fun toggleEnabled() {
    onSelectedMods { mods ->
      transaction {
        mods.forEach { it.enabled = !it.enabled }
        loadMods()
      }
    }
  }

  private fun delete() {
    onSelectedMods(gui::removeMods)
  }

  private fun onSelectedMods(block: (List<UnpackedMod>) -> Unit) {
    selectedItems.filterNot(UnpackedMod::isBaseGame).takeUnless(List<UnpackedMod>::isEmpty)?.let(block)
  }

  fun loadMods() {
    tableModel.refresh()
  }
}

private val UnpackedMod.displayName get() = name + if (metadataReadFailure != null && Settings.showMetadataReadFailures) " {!}" else ""
data class NullableWrapper<T : Comparable<T>>(val value: T?) : Comparable<NullableWrapper<T>> {
  override fun compareTo(other: NullableWrapper<T>): Int = when {
    value == null && other.value == null -> 0
    value == null -> 1
    other.value == null -> -1
    else -> value.compareTo(other.value)
  }

  override fun toString(): String = value?.toString() ?: ""
}
private val UnpackedMod.nAuthor get() = NullableWrapper(author)
private val UnpackedMod.nVersion get() = NullableWrapper(version)
private val UnpackedMod.nLoadOrder get() = NullableWrapper(loadOrder)
@Suppress("RemoveExplicitTypeArguments")
private val columns = listOf(
  ColumnDefinition(UnpackedMod::enabled, Boolean::class.javaObjectType, "Enabled", 75) { a, b -> b.compareTo(a) },
  ColumnDefinition(UnpackedMod::displayName, String::class.java, "Mod Name", 400),
  ColumnDefinition<UnpackedMod, NullableWrapper<Version>>(UnpackedMod::nVersion, NullableWrapper(Version("")).javaClass, "Version", 100),
  ColumnDefinition<UnpackedMod, NullableWrapper<String>>(UnpackedMod::nAuthor, NullableWrapper("").javaClass, "Author", 150),
  ColumnDefinition<UnpackedMod, NullableWrapper<Int>>(UnpackedMod::nLoadOrder, NullableWrapper(0).javaClass, "Load Order", 100)
).run {
  val setting = Settings.columnOrder
  when {
    setting.size == size && map { it.name }.containsAll(setting) -> sortedBy { setting.indexOf(it.name) }
    else -> {
      Settings.columnOrder = map { it.name }
      this
    }
  }
}


code:
package life.xchange.ui

import life.xchange.db.HasMod
import life.xchange.db.UnpackedMod
import org.jetbrains.exposed.dao.Entity

class OverwrittenMod<T, ID>(
  entity: T,
  val name: String,
  getAllLoadedNamed: (String) -> Iterable<UnpackedMod>
) : Entity<ID>(entity.id), Comparable<OverwrittenMod<T, *>> where T : Entity<ID>, T : HasMod, ID : Comparable<ID> {
  val overwrites: String
  val overwrittenBy: String

  init {
    val otherEntities = when {
      entity.mod.loadOrder != null -> getAllLoadedNamed(name).sortedBy { it.loadOrder }
      else -> listOf()
    }.partition { it.loadOrder!! < entity.mod.loadOrder!! }

    overwrites = otherEntities.first.joinToString { it.name }
    overwrittenBy = otherEntities.second.joinToString { it.name }
  }

  override fun compareTo(other: OverwrittenMod<T, *>): Int = sequenceOf(
    overwrites.isEmpty().compareTo(other.overwrites.isEmpty()),
    overwrittenBy.isEmpty().compareTo(other.overwrittenBy.isEmpty()) * if (overwrites.isEmpty()) 1 else -1,
    name.lowercase().compareTo(other.name.lowercase())
  ).firstOrNull { it != 0 } ?: 0
}


code:
package life.xchange.ui

import life.xchange.carefully
import javax.swing.JScrollPane

class OverwrittenTableScroller(private var table: OverwrittenTable) : JScrollPane(table) {
  fun loadTable() {
    table.load()
  }
}

open class OverwrittenTable(private val itemLoader: () -> List<OverwrittenMod<*, *>>) : DefinedTable<OverwrittenMod<*, *>, OverwrittenTable.OverwrittenTableModel>() {
  inner class OverwrittenTableModel : DefinedEntityTableModel<OverwrittenMod<*, *>>(listOf(
    ColumnDefinition(OverwrittenMod<*, *>::overwrites, String::class.java, "Overwrites", 225),
    ColumnDefinition(OverwrittenMod<*, *>::name, String::class.java, "Name", 450),
    ColumnDefinition(OverwrittenMod<*, *>::overwrittenBy, String::class.java, "Overwritten By", 225),
  ), autoLoad = false) {
    override fun loadItems() = itemLoader()
  }

  override val tableModel = OverwrittenTableModel()
  private var isLoaded = false

  init {
    initTableStuff()
  }

  open fun load() {
    carefully {
      if (!isLoaded) tableModel.refresh()
      isLoaded = true
    }
  }
}


code:
package life.xchange.ui

import life.xchange.db.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction

class PassageTable(private val mod: UnpackedMod) : OverwrittenTable({
  transaction {
    val otherMods = UnpackedMods.alias("otherMods")
    val otherPassages = UnpackedModPassages.alias("otherPassages")

    val others = UnpackedModPassages
      .innerJoin(otherPassages, { name }, { get(UnpackedModPassages.name) })
      .innerJoin(otherMods, { otherPassages[UnpackedModPassages.mod] }, { otherMods[UnpackedMods.id] })
      .selectAll()
      .where {
        (UnpackedModPassages.mod eq mod.id) and
            (otherMods[UnpackedMods.loadOrder] neq null) and
            (otherPassages[UnpackedModPassages.mod] neq mod.id) and
            not(stringLiteral("around") eq anyFrom(otherPassages[UnpackedModPassages.tags]))
      }
      .groupBy { it[UnpackedModPassages.name] }
      .mapValues { (_, list) -> list.map { UnpackedMod.wrapRow(it, otherMods) } }

    mod.passages.map { passage ->
      OverwrittenMod(passage, passage.toString()) { name ->
        when {
          "around" in passage.tags -> null
          else -> others[name]
        } ?: listOf()
      }
    }.sorted()
  }
})


code:
package life.xchange.ui

import life.xchange.Settings
import life.xchange.buildPanel
import life.xchange.text
import java.awt.*
import java.awt.event.FocusEvent
import java.awt.event.FocusListener
import java.net.URI
import java.net.URISyntaxException
import javax.swing.*
import kotlin.reflect.KProperty1

class SettingsPanel private constructor(vararg highlighted: KProperty1<SettingsPanel, out JComponent>): ColumnedFlowPanel(3) {
  val cleanModdedOnLoadBox by lazy { setting(Settings::cleanModdedOnLoad, "Delete modded directory before loading for fresh load") }
  val closeAfterLoadBox by lazy { setting(Settings::closeAfterLoad, "Close loader after load finishes") }
  val copyInsteadOfLinkBox by lazy { setting(Settings::copyInsteadOfLink, "Load mods via copy instead of link (only use if directed to)") }
  val debugLoadProcessBox by lazy { setting(Settings::debugLoadProcess, "Debug load process") }
  val deleteArchiveOnAddBox by lazy { setting(Settings::deleteArchiveOnAdd, "Delete origin mod file after unpacking") }
  val enableModsOnAddBox by lazy { setting(Settings::enableModsOnAdd, "Enable new mods after unpacking") }
  val launchVersionCheckBox by lazy { setting(Settings::launchVersionCheck, "Check for new mod loader versions on launch") }
  val showFileOperationsBox by lazy { setting(Settings::showFileOperations, "Show file operations on load") }
  val showFilesBox by lazy { setting(Settings::showFiles, "Show new files on load") }
  val showMetadataReadFailuresBox by lazy { setting(Settings::showMetadataReadFailures, "Show metadata read failures") }
  val showNewPassagesBox by lazy { setting(Settings::showNewPassages, "Show new passages on load") }
  val showOverwrittenBox by lazy { setting(Settings::showOverwritten, "Show overwritten passages and files on load") }

  val runAfterLoadBox by lazy {
    setting(Settings::runAfterLoad, "Open modded file in default browser after load finishes") {
      moddedFileLocationBox.isEnabled = it
      defaultButton.isEnabled = it
    }
  }

  val moddedFileLocationBox by lazy {
    JTextField().apply {
      isEnabled = Settings.runAfterLoad
      text = Settings.runAfterLoadURI
      addFocusListener(object : FocusListener {
        var lastValid = ""
        override fun focusGained(event: FocusEvent) {
          lastValid = text
        }

        override fun focusLost(event: FocusEvent) {
          try {
            URI(text)
            Settings.runAfterLoadURI = text
          } catch (_: URISyntaxException) {
            JOptionPane.showMessageDialog(
              this@SettingsPanel,
              "The value '$text' is not a valid URL",
              "Invalid URL",
              JOptionPane.ERROR_MESSAGE
            )
            text = lastValid
          }
        }
      })
      addNext(this)
    }
  }

  val defaultButton by lazy {
    JButton("Default").apply {
      isEnabled = Settings.runAfterLoad
      addActionListener {
        Settings.runAfterLoadURI = Settings.defaultURI
        moddedFileLocationBox.text = Settings.runAfterLoadURI
      }
      addNext(this) {
        fill = GridBagConstraints.VERTICAL
      }
    }
  }

  val keepFilesPanel by lazy {
    buildPanel(axis = BoxLayout.LINE_AXIS) {
      text("Keep ")
      add(JSpinner(SpinnerNumberModel(Settings.keepOldFiles, 0, 9, 1)).apply {
        addChangeListener { Settings.keepOldFiles = value as Int }
      })
      text(" old modded file(s)")
      addNext(this)
    }
  }

  init {
    heading("Unpacking")
    deleteArchiveOnAddBox
    enableModsOnAddBox
    separator()

    heading("View")
    showMetadataReadFailuresBox
    separator()

    heading("Load output")
    showNewPassagesBox
    showFilesBox
    showOverwrittenBox
    showFileOperationsBox
    debugLoadProcessBox
    separator()

    heading("Load process")
    cleanModdedOnLoadBox
    copyInsteadOfLinkBox
    closeAfterLoadBox
    runAfterLoadBox
    moddedFileLocationBox
    defaultButton
    keepFilesPanel
    separator()

    heading("Application")
    launchVersionCheckBox

    highlighted.forEach {
      val component = it.get(this)
      component.background = UIManager.getColor("textHighlight")
    }
  }

  companion object {
    fun show(vararg highlighted: KProperty1<SettingsPanel, out JComponent>) {
      EscFrame.show("Settings", SettingsPanel(*highlighted))
    }
  }
}


code:
package life.xchange.util

fun <K, V> Map<K, Iterable<V>>.flatten(): List<Pair<K, V>> {
  return flatMap { (k, v) -> v.map { k to it } }
}