package theorycrafter.ui.market

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.automirrored.outlined.KeyboardArrowLeft
import androidx.compose.material.icons.automirrored.outlined.KeyboardArrowRight
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.dp
import compose.input.onMousePress
import compose.utils.*
import compose.widgets.*
import eve.data.*
import theorycrafter.TheorycrafterContext
import theorycrafter.fitting.utils.associateWithIndex
import theorycrafter.ui.Icons
import theorycrafter.ui.TheorycrafterTheme
import theorycrafter.ui.fiteditor.DefaultSuggestedIconContainer
import theorycrafter.utils.*
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
import kotlin.math.max


/**
 * The market tree itself.
 */
@Composable
fun MarketTree(
    onLeafMarketGroupSelected: (MarketTreeGroupItem) -> Unit,
    topLevelMarketGroups: List<MarketGroup> = with(TheorycrafterContext.eveData.marketGroups) {
        defaultMarketTreeToplevelGroups()
    },
    itemFilter: ((EveItemType) -> Boolean)? = null,
    initiallyAllExpanded: Boolean = false,
    modifier: Modifier,
) {
    val eveData = TheorycrafterContext.eveData
    val topLevelNodes = remember(eveData, itemFilter) {
        with(eveData, eveData.marketGroups) {
            buildVisualMarketGroupTree(
                topLevelMarketGroups = topLevelMarketGroups,
                itemFilter = itemFilter,
                miniMarketTree = false
            ).map {
                it.toTreeListNode()
            }
        }
    }
    if (topLevelNodes.isEmpty()) {
        Text(
            text = "No matching items",
            modifier.padding(TheorycrafterTheme.spacing.edgeMargins)
        )
        return
    }

    var treeListHeight: Int? by remember { mutableStateOf(null) }
    ContentWithScrollbar(
        modifier = modifier
            .onGloballyPositioned {
                treeListHeight = it.size.height
            },
    ) {
        val state = remember(topLevelNodes, initiallyAllExpanded) {
            MarketTreeListState(topLevelNodes, initiallyAllExpanded)
        }

        LaunchedEffect(state.selectedNode) {
            val node = state.selectedNode
            if (node is TreeListNode.Leaf) {
                onLeafMarketGroupSelected(node.value)
            }
        }

        val density = LocalDensity.current
        TreeList(
            modifier = Modifier
                .verticalScroll(scrollState)
                .focusWhenClicked()
                .moveSelectionWithKeys(
                    state = state,
                    itemsInPage = {
                        val itemHeight = with(density) {
                            TheorycrafterTheme.sizes.marketTreeGroupItemHeight.toPx()
                        }
                        (treeListHeight!! / itemHeight).toInt()
                    }
                )
                .expandCollapseWithKeys(state),
            state = state,
            topLevelNodes = topLevelNodes,
            childrenOf = remember {
                fun (node: VisualMarketGroup) = node.children.map { it.toTreeListNode() }
            },
            innerContent = { treeNode, level, isExpanded ->
                MarketGroupItem(state, treeNode, level, isExpanded)
            },
            leafContent = { treeNode, level ->
                MarketGroupItem(state, treeNode, level, expanded = false)
            },
        )
    }
}


/**
 * The default top level market groups shown in the market tree.
 */
context(MarketGroups)
private fun defaultMarketTreeToplevelGroups() = listOf(
    ammoAndCharges,
    deployableStructures,
    drones,
    filaments,
    implants,
    boosters,
    rigs,
    shipEquipment,
    ships,
    skills,
    subsystems,
)


/**
 * The interface for an item in the market tree.
 */
interface MarketTreeGroupItem {


    /**
     * The main market group corresponding to this item.
     *
     * Note that the EVE items in this [MarketTreeGroupItem] may not correspond exactly to the items in this group
     * because some groups may be split into several [SyntheticMarketGroup]s, and some items from other market groups may be
     * merged into this one.
     */
    val marketGroup: MarketGroup?


    /**
     * The EVE item types that should be displayed when this [MarketTreeGroupItem] is selected.
     */
    val itemTypes: List<EveItemType>


}


/**
 * A node in the tree of market group items we display.
 */
context(EveData, MarketGroups)
private class VisualMarketGroup(


    /**
     * The corresponding real market group.
     */
    override val marketGroup: MarketGroup,


    /**
     * A fake acting as the market group for display purposes.
     *
     * This is used in the containing [VisualMarketGroup] of synthetic market group items.
     * When this is not `null`, [marketGroup] is still the parent of the items.
     */
    val syntheticMarketGroup: SyntheticMarketGroup? = null,


    /**
     * The parent.
     */
    var parent: VisualMarketGroup?,


    /**
     * Whether the item should show an empty space identical in size to the expand/collapse icon.
     */
    val leaveRoomForExpandCollapseIcon: Boolean,


    /**
     * Whether to show the market group icon.
     */
    val showMarketGroupIcon: Boolean,


    /**
     * The child [VisualMarketGroup]s; `null` to build them automatically from the children of the market group.
     */
    children: List<VisualMarketGroup>? = null,


    /**
     * Extra item types to show as part of this group, in addition to the items of [itemMarketGroups].
     */
    var extraItemTypes: List<EveItemType> = emptyList(),


    /**
     * Overrides the name of the group, if set.
     */
    var nameOverride: String? = null,


    /**
     * Only eve items that pass the filter are displayed.
     */
    val itemFilter: ((EveItemType) -> Boolean)?,


): MarketTreeGroupItem {


    /**
     * The child [VisualMarketGroup]s.
     */
    var children: MutableList<VisualMarketGroup> = (children ?: buildChildren(itemFilter)).toMutableList()


    /**
     * The market groups whose items are displayed when this visual market group is selected.
     *
     * This is not `null` only for leaves.
     */
    var itemMarketGroups: MutableList<MarketGroup>? = when {
        isSynthetic -> mutableListOf()
        this.children.isEmpty() -> mutableListOf(marketGroup)
        else -> null
    }


    /**
     * Returns whether this is a synthetic [VisualMarketGroup].
     */
    val isSynthetic: Boolean
        get() = syntheticMarketGroup != null


    /**
     * Returns whether the given [visualMarketGroup] is a descendent of this one.
     */
    fun isAncestorOf(visualMarketGroup: VisualMarketGroup): Boolean {
        var group = visualMarketGroup
        while (true) {
            val parent = group.parent ?: break
            if (parent == this)
                return true
            group = parent
        }

        return false
    }


    /**
     * Returns all the [EveItemType] that should be displayed when this group is selected.
     */
    override val itemTypes: List<EveItemType>
        get() {
            val allItemTypes = (itemMarketGroups ?: emptyList())
                .flatMap { itemTypesByMarketGroup[it] ?: emptyList() } +
                    extraItemTypes
            return if (itemFilter != null)
                allItemTypes.filter(itemFilter)
            else
                allItemTypes
        }


    /**
     * Returns the name of this group, as it should be displayed to the user.
     */
    val name: String
        get() = nameOverride
            ?: syntheticMarketGroup?.name
            ?: marketGroup.name


    /**
     * Returns whether this group is empty.
     *
     * A group is empty when it has no children and no eve items.
     */
    fun isEmpty(): Boolean {
        if (children.isNotEmpty())
            return false

        val itemMarketGroups = itemMarketGroups
        val itemFilter = itemFilter

        if (itemMarketGroups != null) {
            if (itemFilter == null) {
                if (itemMarketGroups.any { !itemTypesByMarketGroup[it].isNullOrEmpty() })
                    return false
            } else {
                if (itemMarketGroups.any { itemTypesByMarketGroup[it]?.any(itemFilter) == true })
                    return false
            }
        }

        if (itemFilter == null) {
            if (extraItemTypes.isNotEmpty())
                return false
        } else {
            if (extraItemTypes.any(itemFilter))
                return false
        }

        return true
    }


    override fun toString() = name


}


/**
 * Builds the tree of items we will display.
 */
context(EveData, MarketGroups)
private fun buildVisualMarketGroupTree(
    topLevelMarketGroups: List<MarketGroup>,
    itemFilter: ((EveItemType) -> Boolean)?,
    miniMarketTree: Boolean
): List<VisualMarketGroup> {
    val topLevelGroups = topLevelMarketGroups
        .map { marketGroup ->
            VisualMarketGroup(
                marketGroup = marketGroup,
                parent = null,
                leaveRoomForExpandCollapseIcon = true,
                showMarketGroupIcon = true,
                itemFilter = itemFilter
            )
        }

    val visualGroupByMarketGroup = mutableMapOf<MarketGroup, VisualMarketGroup>()
    topLevelGroups.inBfsOrder<VisualMarketGroup>(
        childrenOf = { it.children },
        isTree = true,
        initialQueueCapacity = 100,
    ) {
        if (!it.isSynthetic)
            visualGroupByMarketGroup[it.marketGroup] = it
    }

    return with(VisualGroupAdjustmentContext(visualGroupByMarketGroup)) {
        topLevelGroups
            .mergeSomeShipChildren()
            .moveSomeShipNodes()
            .showSomeSpecialShipsAsStandard()
            .splitSomeGroups()
            .mergeSomeGroupEveItems()
            .fixMutadaptiveRemoteArmorRepairers()
            .removeEmptyGroups()
            .applyIf(miniMarketTree) { pullUpSomeChildrenOfOnlyChildGroups() }
            .reorderSomeGroups()
    }
}


/**
 * The context passed to functions that adjust the market tree for display.
 */
private class VisualGroupAdjustmentContext(
    val visualGroupByMarketGroup: Map<MarketGroup, VisualMarketGroup>,
)


/**
 * Some ship groups are unnecessarily split into racial subgroups, which often have just one or two ships.
 * This function merges such subgroups into their parent
 */
context(EveData, VisualGroupAdjustmentContext)
private fun List<VisualMarketGroup>.mergeSomeShipChildren(): List<VisualMarketGroup> {
    // Merge the children of ship market groups where the children split the ships only by race.
    // We detect this by checking if any of the subgroups are named exactly after a race name.
    // Note that some such parent groups contain additional subgroups. For example, the "Carriers" group contains each
    // racial subgroup, and also a "Faction Carrier" subgroup.
    val raceNames = with(races) {
        listOf(amarr, caldari, gallente, minmatar, ore)
            .mapTo(mutableSetOf()) { it.name }
    }

    visualGroupByMarketGroup[marketGroups.ships]?.inBfsOrder(
        childrenOf = { it.children },
        isTree = true,
    ) { visualMarketGroup ->
        if (visualMarketGroup.children.any { it.marketGroup.name in raceNames }) {
            visualMarketGroup.itemMarketGroups = visualMarketGroup.children.map { it.marketGroup }.toMutableList()
            visualMarketGroup.children.clear()
        }
    }

    return this
}


/**
 * The names of ship groups whose children should be reparented, mapped to the names of groups whose children they
 * should become.
 */
private val OldToNewParentName = mapOf(
    "Precursor Battleships" to "Faction Battleships",
    "Precursor Battlecruisers" to "Faction Battlecruisers",
    "Precursor Dreadnoughts" to "Faction Dreadnoughts",
    "Precursor Cruisers" to "Faction Cruisers",
    "Precursor Destroyers" to "Faction Destroyers",
    "Precursor Frigates" to "Faction Frigates",
)


/**
 * Some ship groups are misplaced; this function moves them to the correct location in the tree.
 *
 * Typically this happens with Triglavian ships. For example, "Faction Battleships" has subgroups "EDENCOM",
 * "Navy Faction" and "Pirate Faction", but "Triglavian" is in a separate group named "Precursor Battleships" (and is
 * its only child).
 */
context(EveData, VisualGroupAdjustmentContext)
private fun List<VisualMarketGroup>.moveSomeShipNodes(): List<VisualMarketGroup> {
    val groupsWhoseChildrenToReparent = mutableSetOf<VisualMarketGroup>()
    val groupByName = mutableMapOf<String, VisualMarketGroup>()
    visualGroupByMarketGroup[marketGroups.ships]?.inBfsOrder(
        childrenOf = { if (it.isSynthetic) emptyList() else it.children },
        isTree = true
    ) { visualMarketGroup ->
        if (!visualMarketGroup.isSynthetic) {
            val name = visualMarketGroup.marketGroup.name
            if (name in OldToNewParentName)
                groupsWhoseChildrenToReparent.add(visualMarketGroup)
            groupByName[name] = visualMarketGroup
        }
    }

    for (oldParent in groupsWhoseChildrenToReparent) {
        val newParentName = OldToNewParentName[oldParent.marketGroup.name]!!
        val newParent = groupByName[newParentName]!!
        newParent.children += oldParent.children
        oldParent.children.forEach {
            it.parent = newParent
        }
        oldParent.parent?.let {
            it.children -= oldParent
        }
    }

    return this
}


/**
 * Maps the names of some ships that need to be displayed in additional market groups to the names of these groups.
 */
private val ShipNameToGroupName = listOf(
    "Apotheosis" to "Shuttles",
    "Metamorphosis" to "Standard Frigates",
    "Sunesis" to "Standard Destroyers",
    "Gnosis" to "Standard Battlecruisers",
    "Praxis" to "Standard Battleships"
)


/**
 * Some frequently used ships are hidden in the "Special Edition Ships" group; this adds them to the corresponding
 * standard group too.
 */
context(EveData, VisualGroupAdjustmentContext)
private fun List<VisualMarketGroup>.showSomeSpecialShipsAsStandard(): List<VisualMarketGroup> {
    val groupByName = mutableMapOf<String, VisualMarketGroup>()
    visualGroupByMarketGroup[marketGroups.ships]?.inBfsOrder(
        childrenOf = { if (it.isSynthetic) emptyList() else it.children },
        isTree = true
    ) { visualMarketGroup ->
        if (!visualMarketGroup.isSynthetic) {
            groupByName[visualMarketGroup.marketGroup.name] = visualMarketGroup
        }
    }

    if (groupByName.isEmpty())
        return this

    for ((shipName, groupName) in ShipNameToGroupName) {
        val ship = shipType(shipName)
        val group = groupByName[groupName] ?: continue
        group.extraItemTypes += ship
    }

    return this
}


/**
 * Splits the items of the given market group into subgroups by the variation parent of the items.
 */
context(EveData, VisualGroupAdjustmentContext)
private fun splitByVariationParent(
    marketGroup: MarketGroup,
    sortKey: (VisualMarketGroup) -> Comparable<*> = { it.syntheticMarketGroup!!.name },
    specialMappingNames: List<Pair<String, String>> = emptyList()
) {
    val group = visualGroupByMarketGroup[marketGroup] ?: return
    val itemTypes = itemTypesByMarketGroup[marketGroup]!!
    val specialMapping = specialMappingNames.associate { (key, value) ->
        val keyItemType = itemTypes.find { it.name == key }
        val valueItemType = itemTypes.find { it.name == value }
        keyItemType to valueItemType
    }
    val groupsByParent = itemTypes.groupBy {
        val specialVariationParent = specialMapping[it]?.variationParentOrNull
        specialVariationParent ?: it.variationParentOrNull!!
    }

    group.children = groupsByParent.entries
        .mapTo(mutableListOf()) { (parentItemType, itemTypes) ->
            with(marketGroups) {
                VisualMarketGroup(
                    marketGroup = marketGroup,
                    syntheticMarketGroup = SyntheticMarketGroup(parentItemType),
                    parent = group,
                    leaveRoomForExpandCollapseIcon = true,
                    showMarketGroupIcon = true,
                    children = emptyList(),
                    extraItemTypes = itemTypes,
                    itemFilter = group.itemFilter
                )
            }
        }
        .also { groups ->
            groups.sortBy {
                @Suppress("UNCHECKED_CAST")
                sortKey(it) as Comparable<Any>
            }
        }
    group.itemMarketGroups = null
}


/**
 * A [MarketGroup] simulacrum for grouping items whose variation parent is the given item type.
 */
private class SyntheticMarketGroup(val parentItemType: EveItemType) {

    /**
     * The name of the group to display to the user.
     */
    // Converts e.g. "Drone Link Augmentor I" to "Drone Link Augmentors"
    val name: String = parentItemType.name.substringBeforeLast(" ") + "s"

}


/**
 * The "Drone Upgrades" market group for some reason merges quite a few types; this function splits them into subgroups.
 */
context(EveData, VisualGroupAdjustmentContext)
private fun List<VisualMarketGroup>.splitSomeGroups(): List<VisualMarketGroup> {
    fun propModsSortKey(group: VisualMarketGroup): Int {
        return group.syntheticMarketGroup!!.name.substringBefore("MN").toIntOrNull() ?: 0
    }

    splitByVariationParent(marketGroups.droneUpgrades)
    splitByVariationParent(
        marketGroup = marketGroups.afterburners,
        sortKey = ::propModsSortKey,
        specialMappingNames = listOf("1MN Civilian Afterburner" to "1MN Afterburner I")
    )
    splitByVariationParent(marketGroups.microwarpdrives, sortKey = ::propModsSortKey)
    splitByVariationParent(
        marketGroup = marketGroups.damageControls,
        specialMappingNames = listOf("Civilian Damage Control" to "Damage Control I")
    )
    splitByVariationParent(marketGroups.warpScramblers)
    splitByVariationParent(
        marketGroup = marketGroups.warpDisruptors,
        specialMappingNames = listOf("Civilian Warp Disruptor" to "Warp Disruptor I")
    )
    splitByVariationParent(marketGroups.weaponDisruptors)

    return this
}


/**
 * The names of market groups whose items should be merged into another group, mapped to that group name.
 */
private val OldToNewItemParentGroupName = mapOf(
    "Micro Jump Field Generators" to "Micro Jump Drives",
    "Combat Utility Drones" to "Electronic Warfare Drones"
)


/**
 * Some leaf groups are unnecessarily split. For example, there is a separate "Micro Jump Field Generators" group whose
 * children are better in the "Micro Jump Drives" group. This function merges them.
 */
context(EveData, VisualGroupAdjustmentContext)
private fun List<VisualMarketGroup>.mergeSomeGroupEveItems(
): List<VisualMarketGroup> {
    val rootGroups = listOf(marketGroups.propulsion, marketGroups.drones).mapNotNull {
        visualGroupByMarketGroup[it]
    }

    val groupsWhoseChildrenToReparent = mutableSetOf<VisualMarketGroup>()
    val groupByName = mutableMapOf<String, VisualMarketGroup>()
    rootGroups.inBfsOrder<VisualMarketGroup>(
        childrenOf = { if (it.isSynthetic) emptyList() else it.children },
        isTree = true
    ) { visualMarketGroup ->
        if (!visualMarketGroup.isSynthetic) {
            val name = visualMarketGroup.marketGroup.name
            if (name in OldToNewItemParentGroupName)
                groupsWhoseChildrenToReparent.add(visualMarketGroup)
            groupByName[name] = visualMarketGroup
        }
    }

    for (oldParent in groupsWhoseChildrenToReparent) {
        val newParentName = OldToNewItemParentGroupName[oldParent.marketGroup.name]!!
        val newParent = groupByName[newParentName]!!
        newParent.itemMarketGroups?.add(oldParent.marketGroup)
        oldParent.parent?.let {
            it.children -= oldParent
        }
    }

    return this
}


/**
 * Moves the "Mutadaptive Remote Armor Repairers -> Medium" group into the "Remote Armor Repairers" group and renames it
 * to "Mutadaptive".
 */
context(EveData, VisualGroupAdjustmentContext)
private fun List<VisualMarketGroup>.fixMutadaptiveRemoteArmorRepairers(): List<VisualMarketGroup> {

    // Remove the "Medium" subgroup from "Mutadaptive Remote Armor Repairers"
    val mediumMutadaptiveRarsVisualGroup =
        visualGroupByMarketGroup[marketGroups.mediumMutadaptiveRemoteArmorRepairers] ?: return this
    mediumMutadaptiveRarsVisualGroup.parent!!.children.remove(mediumMutadaptiveRarsVisualGroup)

    // Add it to the "Remote Armor Repairers" group
    val rarsVisualGroup = visualGroupByMarketGroup[marketGroups.remoteArmoreRepairers]!!
    rarsVisualGroup.children.add(mediumMutadaptiveRarsVisualGroup)
    mediumMutadaptiveRarsVisualGroup.parent = rarsVisualGroup

    // Change its name
    mediumMutadaptiveRarsVisualGroup.nameOverride = "Mutadaptive"

    return this
}


/**
 * Some market groups don't have any eve items in them; this function removes such groups (and their empty ancestors).
 */
context(EveData)
private fun List<VisualMarketGroup>.removeEmptyGroups(): MutableList<VisualMarketGroup> {
    val result = mutableListOf<VisualMarketGroup>()

    for (group in this) {
        group.children = group.children.removeEmptyGroups()
        if (!group.isEmpty())
            result.add(group)
    }

    return result
}


/**
 * After filtering and removing empty groups, some groups will have a single child. For example, when the mini market
 * tree is used for fitting rigs, only one of "Capital", "Large", "Medium" and "Small" subgroups of each rig type group
 * remains. This function moves the children of such groups into the parent.
 */
context(EveData)
private fun List<VisualMarketGroup>.pullUpSomeChildrenOfOnlyChildGroups(): List<VisualMarketGroup> {
    // In some parts of the tree, when encountering a group with a sole child we want to replace that group with
    // that child because the child is a more specific group.
    // This happens for example, with rigs, where, when fitting a frigate, we replace "Armor Rigs" with its sole child
    // "Small Armor Rigs".
    // In other parts of the tree, however, the child, while more specific, doesn't include a distinguishing substring
    // in its name, thus when we replace a group with its child, the parent of the group will end up with
    // children that all have the same name.
    // For example, "Frequency Crystals" has several children, all of whom have "Small" as their child. After filtering
    // for small Crystal size, if we replace the children of "Frequency Crystals" with their sole child, we end up with
    // all the children of "Frequency Crystals" named "Small".
    // In these cases, instead of replacing a group with its child, we have that group adopt its grandchildren, so that
    // the group itself survives.
    // ancestorsOfAdoptGrandchildrenPolicy is a list of market groups marking the parts of the tree where we adopt the
    // grandchildren, rather than replacing the group.
    val ancestorsOfAdoptGrandchildrenPolicy = with(marketGroups) {
        listOf(
            condenserPacks,
            exoticPlasmaCharges,
            frequencyCrystals,
            hybridCharges,
            projectileAmmo,
            skillHardwiring
        )
    }

    fun pullUp(group: VisualMarketGroup, adoptGrandchildren: Boolean? = null) {
        // adoptGrandchildren is null when unknown
        val actuallyAdoptGrandchildren = adoptGrandchildren ?: with (marketGroups) {
            ancestorsOfAdoptGrandchildrenPolicy.any { (it == group.marketGroup) || it.isAncestorOf(group.marketGroup) }
        }
        if (actuallyAdoptGrandchildren) {
            for (child in group.children)
                pullUp(child, true)
            val children = group.children
            if (children.size == 1) {
                // Replace the sole child with the grandchildren
                val child = children.first()
                if (child.itemMarketGroups != null) {
                    group.itemMarketGroups = child.itemMarketGroups
                    group.children = mutableListOf()
                }
                else {
                    group.children = child.children
                    group.children.forEach {
                        it.parent = group
                    }
                }
            }
        } else {
            // Here we replace a group with its child, but note that we can only do that when holding a reference to
            // the parent of that group. So the replaced group is not `group`, but each of its children.
            // We can't replace `group` by modifying `group.parent.children` because this function is called while
            // iterating `group.parent.children`; modifying it would throw a ConcurrentModificationException
            for ((index, child) in group.children.withIndex()) {
                pullUp(child, null)
                val grandchildren = child.children
                if (grandchildren.size == 1) {
                    // Replace the (child) group with its sole child
                    group.children[index] = grandchildren.first().also {
                        it.parent = group
                    }
                }
            }
        }
    }

    val result = mutableListOf<VisualMarketGroup>()
    for (group in this) {
        pullUp(group)
        if (group.children.size == 1) {
            result.addAll(group.children)
            group.children.forEach { it.parent = group.parent }
        } else {
            result.add(group)
        }
    }

    return result
}


/**
 * Some market groups have a poor ordering of their children. For example "Armor Plates" is sorted alphabetically, but
 * it would be better sorted by the size of the plates. This function reorders the children of such groups.
 */
context(EveData, VisualGroupAdjustmentContext)
private fun List<VisualMarketGroup>.reorderSomeGroups(): List<VisualMarketGroup> {
    // Armor plates
    visualGroupByMarketGroup[marketGroups.armorPlates]?.let { armorPlates ->
        armorPlates.children.sortBy {
            it.marketGroup.name.substringIntOrNull()
        }
    }

    // Resistance-boosting modules
    val resistanceGroups = with(marketGroups) {
        listOf(armorHardeners,
            armorResistanceCoatings,
            energizedArmorResistanceMembranes,
            shieldHardeners,
            shieldResistanceAmplifiers,
        )
    }
    for (group in resistanceGroups) {
        visualGroupByMarketGroup[group]?.let { visualGroup ->
            visualGroup.children.sortBy {
                val name = it.marketGroup.name
                when {
                    name.startsWith("EM") -> 0
                    name.startsWith("Thermal") -> 1
                    name.startsWith("Kinetic") -> 2
                    name.startsWith("Explosive") -> 3
                    else -> 4
                }
            }
        }
    }

    // Sized modules
    val sizedGroups = with(marketGroups) {
        listOf(
            armorRepairers,
            hullRepaiers,
            remoteArmorRepairers,
            remoteHullRepairers,
            capacitorBatteries,
            capacitorBoosters,
            energyNeutralizers,
            energyNosferatu,
            remoteCapacitorTransmitters,
            remoteShieldBoosters,
            shieldBoosters,
            shieldExtenders,
            smartBombs,
        )
    }
    val orderByName =
        listOf("Micro", "Small", "Medium", "Large", "Heavy", "Extra Large", "Capital").associateWithIndex()
    for (group in sizedGroups) {
        visualGroupByMarketGroup[group]?.let { visualGroup ->
            visualGroup.children.sortBy {
                orderByName[it.name] ?: orderByName.size
            }
        }
    }

    return this
}


/**
 * The type alias for the [TreeListState] for the market tree.
 */
private typealias  MarketTreeListState = TreeListState<VisualMarketGroup, VisualMarketGroup>


/**
 * Creates a new [MarketTreeListState].
 */
private fun MarketTreeListState(
    topLevelNodes: List<MarketTreeListNode>,
    initiallyAllExpanded: Boolean
): MarketTreeListState {
    return TreeListState<VisualMarketGroup, VisualMarketGroup>(
        isAncestorOf = { node ->
            value.isAncestorOf(node.value)
        }
    ).also { state ->
        if (initiallyAllExpanded) {
            topLevelNodes.inBfsOrder(
                childrenOf = {
                    if (it is TreeListNode.Inner<VisualMarketGroup>)
                        it.value.children.map(VisualMarketGroup::toTreeListNode)
                    else
                        emptyList()
               },
                isTree = true,
            ) { node: MarketTreeListNode ->
                (node as? TreeListNode.Inner<VisualMarketGroup>)?.let { state.expand(it) }
            }
        } else {
            // Expand the ship equipment group, for convenience
            val shipEquipmentGroup = TheorycrafterContext.eveData.marketGroups.shipEquipment
            val shipEquipmentNode = topLevelNodes.find {
                (it as? MarketTreeListNode)?.value?.marketGroup == shipEquipmentGroup
            } as TreeListNode.Inner<VisualMarketGroup>?
            if (shipEquipmentNode != null)
                state.expand(shipEquipmentNode)
        }
    }
}


/**
 * The type alias for the [TreeListNode] for the market tree.
 */
private typealias MarketTreeListNode = TreeListNode<VisualMarketGroup, VisualMarketGroup>


/**
 * Creates a [MarketTreeListNode] corresponding to the given market group.
 */
private fun VisualMarketGroup.toTreeListNode(): MarketTreeListNode {
    return if (children.isEmpty())
        TreeListNode.Leaf(this)
    else
        TreeListNode.Inner(this)
}


/**
 * Returns the children of the given market group item.
 */
context(EveData, MarketGroups)
private fun VisualMarketGroup.buildChildren(
    itemFilter: ((EveItemType) -> Boolean)?
): List<VisualMarketGroup> {
    val children = childrenOf(marketGroup) ?: return emptyList()
    val firstIconId = children.getOrNull(0)?.iconId
    val allHaveSameIcon = children.all { it.iconId == firstIconId }
    val allLeaves = children.all { childrenOf(it) == null }

    val parentHasGroupIcon = showMarketGroupIcon

    // If all the market groups are leaves, there's no point in making space for the expand/collapse icon.
    // Except if the parent is showing a market group icon (and it's always showing an expand/collapse icon), then we
    // must leave empty space for the expand/collapse icon to create the correct visual offset.
    val childrenLeaveRoomForExpandCollapseIcon = !allLeaves || parentHasGroupIcon

    // When all the children have the same icon, there's no point in showing it.
    // Except if we aren't showing the expand/collapse icon, then we should show the group icon because we need at least
    // one to create the correct visual offset.
    // Also, if the parent is showing a group icon (and it's always showing an expand/collapse icon), and all the
    // children are leaves we still need to show the group icon to create the correct visual offset.
    val childrenShowMarketGroupIcon =
        !allHaveSameIcon || !childrenLeaveRoomForExpandCollapseIcon || (parentHasGroupIcon && allLeaves)

    return children.map { childMarketGroup ->
        VisualMarketGroup(
            marketGroup = childMarketGroup,
            parent = this,
            leaveRoomForExpandCollapseIcon = childrenLeaveRoomForExpandCollapseIcon,
            showMarketGroupIcon = childrenShowMarketGroupIcon,
            itemFilter = itemFilter
        )
    }
}


/**
 * A single item in the market tree.
 */
@Composable
private fun MarketGroupItem(
    state: MarketTreeListState,
    treeNode: MarketTreeListNode,
    level: Int,
    expanded: Boolean,
) {
    val visualGroup = treeNode.value
    VerticallyCenteredRow(
        horizontalArrangement = Arrangement.spacedBy(TheorycrafterTheme.spacing.xxsmall),
        modifier = Modifier
            .thenIf(treeNode is TreeListNode.Inner) {
                onMousePress {
                    state.toggleExpanded(treeNode as TreeListNode.Inner)
                }
            }
            .onMousePress {
                state.selectedNode = treeNode
            }
            .highlightOnHover()
            .bringIntoViewWhenSelectedWithMargins(
                // To show a peek into the next item
                PaddingValues(vertical = TheorycrafterTheme.sizes.marketTreeGroupItemHeight/2)
            )
            .padding(
                start = TheorycrafterTheme.spacing.horizontalEdgeMargin +
                        TheorycrafterTheme.sizes.marketTreePerLevelOffset * level,
                end = TheorycrafterTheme.spacing.horizontalEdgeMargin,
            )
            .height(TheorycrafterTheme.sizes.marketTreeGroupItemHeight)
            .fillMaxWidth()
    ) {
        if (visualGroup.leaveRoomForExpandCollapseIcon) {
            Box(Modifier.size(TheorycrafterTheme.sizes.marketTreeCollapseExpandIconSize)) {
                if (treeNode is TreeListNode.Inner) {
                    Icons.TreeListExpandCollapse(
                        expanded = expanded,
                        modifier = Modifier.fillMaxSize()
                    )
                }
            }
        }

        if (visualGroup.showMarketGroupIcon) {
            visualGroup.MarketGroupIcon()
        }
        Text(visualGroup.name)
//         For debugging
//        Text(
//            if (visualGroup.nameOverride != null)
//                "**${visualGroup.nameOverride}"
//            else if (visualGroup.syntheticMarketGroup != null)
//                "*${visualGroup.syntheticMarketGroup.name} (itemId=${visualGroup.syntheticMarketGroup.parentItemType.itemId})"
//            else
//                "${visualGroup.marketGroup.name} (${visualGroup.marketGroup.id})"
//        )
    }
}


/**
 * The market group icon for the given [VisualMarketGroup].
 */
@Composable
private fun VisualMarketGroup.MarketGroupIcon() {
    val marketGroup = marketGroup
    val syntheticMarketGroup = syntheticMarketGroup
    DefaultSuggestedIconContainer {
        if (syntheticMarketGroup != null) {
            Icons.EveItemType(
                itemType = syntheticMarketGroup.parentItemType,
                modifier = Modifier.fillMaxSize()
            )
        }
        else {
            Icons.MarketGroup(
                marketGroup = marketGroup,
                modifier = Modifier.fillMaxSize()
            )
        }
    }
}


/**
 * A compact version of the market tree, suitable for displaying in a popup.
 */
@Composable
fun MiniMarketTree(


    /**
     * The market groups to display at the top level.
     */
    toplevelMarketGroups: List<MarketGroup>,


    /**
     * The title to show when displaying the top-level groups.
     */
    toplevelTitle: String,


    /**
     * Only items passing this filter will be displayed.
     * Market groups which are empty after filtering are also not displayed.
     */
    itemFilter: ((EveItemType) -> Boolean)?,


    /**
     * Invoked when an eve item is selected.
     */
    onItemPressed: (EveItemType) -> Unit,


    /**
     * The UI to display in the item's row.
     */
    itemContent: @Composable RowScope.(EveItemType) -> Unit,


    /**
     * A modifier to apply to the mini market tree top-level element.
     */
    modifier: Modifier = Modifier,


) {
    if (toplevelMarketGroups.isEmpty()) {
        Text("No market groups", modifier.padding(TheorycrafterTheme.spacing.edgeMargins))
        return
    }

    val eveData = TheorycrafterContext.eveData
    val toplevelVisualGroups = remember(eveData) {
        with(eveData, eveData.marketGroups) {
            buildVisualMarketGroupTree(
                topLevelMarketGroups = toplevelMarketGroups,
                itemFilter = itemFilter,
                miniMarketTree = true
            )
        }
    }
    if (toplevelVisualGroups.isEmpty()) {
        Text("No matching market groups", modifier.padding(TheorycrafterTheme.spacing.edgeMargins))
        return
    }

    val commonParentMarketGroup = remember(toplevelVisualGroups) {
        val parentGroups = toplevelVisualGroups.mapTo(mutableSetOf()) { group ->
            group.marketGroup.parentGroupId?.let {
                eveData.marketGroups[it]
            }
        }
        if (parentGroups.size == 1)
            parentGroups.first()
        else
            null
    }

    // The parent of the displayed groups; null when displaying the toplevel groups
    var displayedGroupsParent: VisualMarketGroup? by remember(toplevelVisualGroups) {
        // When there is one toplevel group, show only its contents.
        mutableStateOf(toplevelVisualGroups.singleOrNull())
    }

    AnimatedContent(
        targetState = displayedGroupsParent,
        transitionSpec = {
            if (targetState?.parent == initialState) {
                slideInHorizontally { it } togetherWith  slideOutHorizontally { -it }
            } else {
                slideInHorizontally { -it } togetherWith  slideOutHorizontally { it }
            }
        },
        modifier = modifier
    ) { currentDisplayedGroup ->
        val displayedGroups = currentDisplayedGroup?.children ?: toplevelVisualGroups
        Column {
            // The title and back widget
            MiniMarketTreeTitle(
                backEnabled = when {
                    currentDisplayedGroup == null -> false  // displaying the toplevel groups
                    // When there's only one toplevel group, we never want to display it in the list; only its children.
                    (currentDisplayedGroup.parent == null) && (toplevelVisualGroups.size == 1) -> false
                    else -> true
                },
                title = when {
                    currentDisplayedGroup != null -> currentDisplayedGroup.name
                    commonParentMarketGroup != null -> commonParentMarketGroup.name
                    else -> toplevelTitle
                },
                modifier = Modifier.fillMaxWidth(),
                onBackPressed = {
                    displayedGroupsParent = currentDisplayedGroup!!.parent
                }
            )

            // Separator
            Box(
                Modifier
                    .fillMaxWidth()
                    .height(1.dp)
                    .background(TheorycrafterTheme.colors.menuItemSeparator())
            )

            // The market groups or eve items
            ContentWithScrollbar {
                ScrollShadow(scrollState, top = true)
                Column(
                    modifier = modifier.verticalScroll(scrollState)
                ) {
                    if (displayedGroups.isEmpty()) {
                        val itemTypes = remember(currentDisplayedGroup) {
                            currentDisplayedGroup!!.itemTypes.sortedForDisplay(currentDisplayedGroup.marketGroup)
                        }
                        MiniMarketTreeEveItemTypes(
                            itemTypes = itemTypes,
                            itemContent = itemContent,
                            onItemTypePressed = { onItemPressed(it) },
                        )
                    } else {
                        MiniMarketTreeGroups(
                            groups = displayedGroups,
                            onGroupPressed = { displayedGroupsParent = it }
                        )
                    }
                }
            }
        }
    }
}


/**
 * The widget for the title of the mini market tree.
 */
@Composable
private fun MiniMarketTreeTitle(
    backEnabled: Boolean,
    title: String,
    modifier: Modifier,
    onBackPressed: () -> Unit
) {
    val spacing = with(LocalDensity.current) {
        TheorycrafterTheme.spacing.xxsmall.roundToPx()
    }
    Layout(
        modifier = modifier,
        measurePolicy = { measurables, constraints ->
            val backWidgetMeasurable = measurables[0]
            val titleMeasurable = measurables[1]

            val backWidgetPlaceable = backWidgetMeasurable.measure(Constraints())
            val titlePlaceable = titleMeasurable.measure(Constraints())

            val width = max(
                backWidgetPlaceable.width*2 + spacing*2 + titlePlaceable.width,
                if (constraints.hasBoundedWidth) constraints.maxWidth else 0
            )
            val height = max(backWidgetPlaceable.height, titlePlaceable.height)
            layout(width = width, height = height) {
                backWidgetPlaceable.place(
                    x = 0,
                    y = (height - backWidgetPlaceable.height)/2
                )
                titlePlaceable.place(
                    x = (width - titlePlaceable.width)/2,
                    y = (height - titlePlaceable.height)/2
                )
            }
        },
        content = {
            ProvideEnabledLocalContentAlpha(backEnabled) {
                VerticallyCenteredRow(
                    modifier = Modifier
                        .thenIf(backEnabled) { highlightOnHover() }
                        .thenIf(backEnabled) { onMousePress { onBackPressed() } }
                        .padding(
                            horizontal = TheorycrafterTheme.spacing.horizontalEdgeMargin,
                            vertical = TheorycrafterTheme.spacing.xsmall
                        ),
                    horizontalArrangement = Arrangement.spacedBy(TheorycrafterTheme.spacing.xxsmall),
                ) {
                    Icon(
                        modifier = Modifier.size(TheorycrafterTheme.sizes.miniMarketTreeArrowIconSize),
                        imageVector = TheorycrafterTheme.autoMirroredIconStyle.KeyboardArrowLeft,
                        contentDescription = "Show parent market group",
                    )
                    Text("Back")
                }
            }

            Text(
                text = title,
                modifier = Modifier
                    .padding(horizontal = TheorycrafterTheme.spacing.xxsmall)
            )
        },
    )
}


/**
 * A list of [VisualMarketGroup] in the mini market tree.
 */
@Suppress("UnusedReceiverParameter")
@Composable
private fun ColumnScope.MiniMarketTreeGroups(
    groups: List<VisualMarketGroup>,
    onGroupPressed: (VisualMarketGroup) -> Unit,
) {
    for (group in groups) {
        key(group) {
            MiniMarketTreeGroupItem(
                visualGroup = group,
                modifier = Modifier
                    .fillMaxWidth()
                    .onMousePress {
                        onGroupPressed(group)
                    }
            )
        }
    }
}


/**
 * A [VisualMarketGroup] item in the list of groups in the mini market tree.
 */
@Composable
private fun MiniMarketTreeGroupItem(
    visualGroup: VisualMarketGroup,
    modifier: Modifier
) {
    MiniMarketTreeListItemRow(modifier) {
        visualGroup.MarketGroupIcon()
        Text(visualGroup.name)

        Spacer(Modifier.weight(1f))

        Icon(
            modifier = Modifier.size(TheorycrafterTheme.sizes.miniMarketTreeArrowIconSize),
            imageVector = TheorycrafterTheme.autoMirroredIconStyle.KeyboardArrowRight,
            contentDescription = "Show group",
        )
    }
}


/**
 * A list of even item types in the mini market tree.
 */
@Suppress("UnusedReceiverParameter")
@Composable
private fun ColumnScope.MiniMarketTreeEveItemTypes(
    itemTypes: List<EveItemType>,
    itemContent: @Composable RowScope.(EveItemType) -> Unit,
    onItemTypePressed: (EveItemType) -> Unit
) {
    for (itemType in itemTypes) {
        key(itemType) {
            MiniMarketTreeEveItemType(
                itemType = itemType,
                itemContent = itemContent,
                modifier = Modifier
                    .fillMaxWidth()
                    .onMousePress {
                        onItemTypePressed(itemType)
                    }
            )
        }
    }
}


/**
 * A single eve item type in the mini market tree.
 */
@Composable
fun MiniMarketTreeEveItemType(
    itemType: EveItemType,
    itemContent: @Composable RowScope.(EveItemType) -> Unit,
    modifier: Modifier
) {
    MiniMarketTreeListItemRow(modifier) {
        itemContent(itemType)
    }
}


/**
 * The container for list items (groups and eve items) in the mini market tree.
 */
@Composable
private fun MiniMarketTreeListItemRow(
    modifier: Modifier,
    content: @Composable RowScope.() -> Unit
) {
    VerticallyCenteredRow(
        horizontalArrangement = Arrangement.spacedBy(TheorycrafterTheme.spacing.xxsmall),
        modifier = modifier
            .highlightOnHover()
            .padding(
                horizontal = TheorycrafterTheme.spacing.horizontalEdgeMargin,
            ),
        content = content
    )
}
