package theorycrafter.ui.tournaments

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import compose.utils.VSpacer
import compose.widgets.SingleLineText
import eve.data.EveData
import eve.data.ModuleType
import eve.data.SensorType
import eve.data.ShipType
import eve.data.typeid.*
import theorycrafter.TheorycrafterContext
import theorycrafter.fitting.Fit
import theorycrafter.tournaments.Composition
import theorycrafter.ui.TheorycrafterTheme
import theorycrafter.utils.NBSP


/**
 * Displays a summary of the utility modules of the composition.
 */
@Composable
fun UtilitySummary(
    composition: Composition,
    modifier: Modifier
) {
    val activeFitsByShip = activeFitsByShip(composition)

    val shipAndCountsByUtility: Map<Utility, Map<Composition.Ship, UtilityCounts>> by remember(activeFitsByShip) {
        derivedStateOf {
            shipAndCountsByUtility(activeFitsByShip)
        }
    }

    val shipTypeCount: Map<ShipType?, Int> by remember(composition) {
        derivedStateOf {
            composition.shipTypeCount
        }
    }

    SelectionContainer {
        Column(modifier) {
            SingleLineText(
                text = "Utilities\n",  // newline for copy/pasting
                style = TheorycrafterTheme.textStyles.mediumHeading,
            )
            VSpacer(TheorycrafterTheme.spacing.xxsmall)

            if (shipAndCountsByUtility.isEmpty()) {
                Text("None", fontWeight = FontWeight.Light)
            }
            else {
                Text(utilitiesSummary(activeFitsByShip, shipAndCountsByUtility, shipTypeCount))
            }
        }
    }
}


/**
 * Returns the active ships and their corresponding fits.
 */
private suspend fun suspendingActiveFitsByShip(composition: Composition): Map<Composition.Ship, Fit> {
    val fits = TheorycrafterContext.fits
    return composition.ships.mapNotNull { ship ->
        if ((ship == null) || !ship.active)
            return@mapNotNull null

        val fitId = ship.fitId ?: return@mapNotNull null
        val fitHandle = fits.handleById(fitId) ?: return@mapNotNull null
        val fit = fits.engineFitOf(fitHandle)
        ship to fit
    }.toMap()
}



/**
 * Returns an [AnnotatedString] describing the utilities of a composition.
 */
suspend fun utilitiesSummaryLines(composition: Composition): List<UtilitySummaryLine> {
    val activeFitsByShip = suspendingActiveFitsByShip(composition)
    val shipAndCountsByUtility = shipAndCountsByUtility(activeFitsByShip)
    val shipTypeCount = composition.shipTypeCount

    return utilitiesSummaryLines(
        activeFitsByShip = activeFitsByShip,
        shipAndCountsByUtility = shipAndCountsByUtility,
        shipTypeCount = shipTypeCount
    )
}


/**
 * Returns an [AnnotatedString] describing the utilities of a composition.
 */
private fun utilitiesSummary(
    activeFitsByShip: Map<Composition.Ship, Fit>,
    shipAndCountsByUtility: Map<Utility, Map<Composition.Ship, UtilityCounts>>,
    shipTypeCount: Map<ShipType?, Int>,
): AnnotatedString = buildAnnotatedString {
    val utilityLines = utilitiesSummaryLines(activeFitsByShip, shipAndCountsByUtility, shipTypeCount)
    for (line in utilityLines) {
        withStyle(TheorycrafterTheme.textStyles.compositionUtilitySummaryCounts.toSpanStyle()) {
            append("${line.count}x ${line.name}".replace(' ', NBSP))
        }
        withStyle(TheorycrafterTheme.textStyles.compositionUtilitySummaryDetails.toSpanStyle()) {
            append(": ")
            append(line.summary)
        }
        append('\n')
    }
}


/**
 * Returns a description the utilities of a fit.
 */
@Composable
fun fitUtilitiesSummary(fit: Fit): String = buildString {
    val utilities by remember(fit) {
        derivedStateOf { fitUtilities(fit) }
    }

    if (utilities.isEmpty())
        return "No utilities"

    val statePrefixAndSelectorList = listOf(
        "" to UtilityCounts::online,
        "Offline " to UtilityCounts::offline
    )
    for ((utility, counts) in utilities) {
        for ((statePrefix, countSelector) in statePrefixAndSelectorList) {
            val utilityCount = countSelector(counts)
            if (utilityCount == 0)
                continue
            appendLine("${utilityCount}x ${statePrefix}${utility.displayName}".replace(' ', NBSP))
        }
    }
}.trimEnd()


/**
 * The parts of a single utility line.
 */
class UtilitySummaryLine(
    val count: Int,
    val name: String,
    val summary: String
) {

    val plainTextSummary: String by lazy {
        summary
            .replace(NBSP, ' ')  // Non-breaking space to regular one
            .replace('‑', '-')  // Non-breaking hyphen to regular one
    }

}


/**
 * Returns the lines of the utilities summary of the composition.
 */
private fun utilitiesSummaryLines(
    activeFitsByShip: Map<Composition.Ship, Fit>,
    shipAndCountsByUtility: Map<Utility, Map<Composition.Ship, UtilityCounts>>,
    shipTypeCount: Map<ShipType?, Int>,
): List<UtilitySummaryLine> {
    return buildList {
        val statePrefixAndSelectorList = listOf(
            "" to UtilityCounts::online,
            "Offline " to UtilityCounts::offline
        )
        for (utility in Utility.entries) {
            val countsByShip = shipAndCountsByUtility[utility] ?: continue
            for ((statePrefix, countSelector) in statePrefixAndSelectorList) {
                val utilityCount = countsByShip.entries.sumOf { (ship, utility) ->
                    ship.amountOrOne * countSelector(utility)
                }
                if (utilityCount == 0)
                    continue
                val utilityName = "$statePrefix${utility.displayName}"
                val shipAndUtilityCount = countsByShip.entries.mapNotNull { (ship, countsOnShip) ->
                    val countOnShip = countSelector(countsOnShip)
                    if (countOnShip == 0)
                        null
                    else
                        ship to countOnShip
                }
                val utilitySummary = shipAndUtilityCount.joinToString(separator = ", ") { (ship, countOnShip) ->
                    val shipType = ship.shipType
                    val name = if (shipTypeCount[shipType] == 1)
                        shipType.name
                    else {
                        val fitHandle = TheorycrafterContext.fits.handleOf(activeFitsByShip[ship]!!)
                        val shipName = shipType.name.replace(' ', NBSP)
                        "${shipName}$NBSP‑$NBSP${fitHandle.name}"  // Non-breaking hyphen
                    }
                    val shipAmount = ship.amount
                    val nameWithShipCount = if (shipAmount == null) name else "${shipAmount}x$NBSP$name"
                    if (countOnShip == 1)
                        nameWithShipCount
                    else
                        "$nameWithShipCount (x${countOnShip})"
                }
                add(
                    UtilitySummaryLine(
                        count = utilityCount,
                        name = utilityName,
                        summary = utilitySummary
                    )
                )
            }
        }
    }
}


/**
 * The number of online and offline utilities of a certain kind.
 */
private class UtilityCounts(var online: Int = 0, var offline: Int = 0)


/**
 * Returns, for each [Utility], a mapping from the ships that have it, to the number of these utilities it has.
 */
private fun shipAndCountsByUtility(
    activeFitsByShip: Map<Composition.Ship, Fit>
): Map<Utility, Map<Composition.Ship, UtilityCounts>> {
    return buildMap {
        for ((ship, fit) in activeFitsByShip) {
            fun addUtility(utility: Utility, online: Boolean) {
                val countsByShip = getOrPut(utility, ::mutableMapOf)
                val counts = (countsByShip as MutableMap).getOrPut(ship, ::UtilityCounts)
                if (online)
                    counts.online += 1
                else
                    counts.offline += 1
            }

            for (module in fit.modules.all) {
                for (utility in Utility.entries) {
                    if (utility.matcher(TheorycrafterContext.eveData, module.type))
                        addUtility(utility, module.online)
                }
            }
        }
    }
}


/**
 * Returns the utilities of the given fit.
 */
private fun fitUtilities(fit: Fit): Map<Utility, UtilityCounts> = buildMap {
    val modules = fit.modules.all
    for (utility in Utility.entries) {
        val counts = UtilityCounts()
        for (module in modules) {
            if (utility.matcher(TheorycrafterContext.eveData, module.type)) {
                if (module.online)
                    counts.online += 1
                else
                    counts.offline += 1
            }
        }
        if (counts.online > 0 || counts.offline > 0)
            put(utility, counts)
    }
}


/**
 * Returns a mapping from ship type to the number of types it appears in the composition.
 */
private val Composition.shipTypeCount: Map<ShipType?, Int>
    get() = ships.groupingBy { it?.shipType }.eachCount()



/**
 * The kinds of utilities a composition can have.
 */
private enum class Utility(
    val displayName: String,
    val matcher: context(EveData) (ModuleType) -> Boolean
) {
    ARMOR_COMMAND_BURST("Armor Burst", matcher = ModuleType::isArmorCommandBurst),
    SHIELD_COMMAND_BURST("Shield Burst", matcher = ModuleType::isShieldCommandBurst),
    SKIRMISH_COMMAND_BURST("Skirmish Burst", matcher = ModuleType::isSkirmishCommandBurst),
    INFORMATION_COMMAND_BURST("Information Burst", matcher = ModuleType::isInformationCommandBurst),
    WARP_SCRAMBLER("Scrambler or WDFG", matcher = { it.isWarpScrambler() || it.isWarpDisruptionFieldGenerator() }),
    WARP_DISRUPTOR("Warp disruptor", matcher = ModuleType::isWarpDisruptor),
    WEBIFIER("Web or Grappler", matcher = { it.isStasisWebifier() || it.isStasisGrappler() }),
    TARGET_PAINTER("Target Painter", matcher = ModuleType::isTargetPainter),
    REMOTE_SENSOR_DAMPENERS("Remote Sensor Dampener", matcher = ModuleType::isRemoteSensorDampener),
    TRACKING_DISRUPTOR("Tracking Disruptor", matcher = ModuleType::isTrackingDisruptor),
    GUIDANCE_DISRUPTOR("Guidance Disruptor", matcher = ModuleType::isGuidanceDisruptor),
    ECM_RADAR(
        displayName = "${SensorType.RADAR.displayName} ECM",
        matcher = { it.isEcm() && (it.ecmSensorType() == SensorType.RADAR) }
    ),
    ECM_MAGNETOMETRIC(
        displayName = "${SensorType.MAGNETOMETRIC.displayName} ECM",
        matcher = { it.isEcm() && (it.ecmSensorType() == SensorType.MAGNETOMETRIC) }
    ),
    ECM_GRAVIMETRIC(
        displayName = "${SensorType.GRAVIMETRIC.displayName} ECM",
        matcher = { it.isEcm() && (it.ecmSensorType() == SensorType.GRAVIMETRIC) }
    ),
    ECM_LADAR(
        displayName = "${SensorType.LADAR.displayName} ECM",
        matcher = { it.isEcm() && (it.ecmSensorType() == SensorType.LADAR) }
    ),
    ECM_MULTISPECTRAL(
        displayName = "Multispectral ECM",
        matcher = { it.isEcm() && (it.ecmSensorType() == null) }
    ),
    REMOTE_SENSOR_BOOSTER("Remote Sensor Booster", matcher = ModuleType::isRemoteSensorBooster),
    REMOTE_TRACKING_COMPUTER("Remote Tracking Computer", matcher = ModuleType::isRemoteTrackingComputer)
}
