package theorycrafter.ui.market

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.DropdownMenuState
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import compose.input.KeyShortcut
import compose.input.onKeyShortcut
import compose.input.onOpenContextMenuGesture
import compose.utils.bringIntoViewWhenSelectedWithMargins
import compose.utils.close
import compose.utils.focusWhenClicked
import compose.utils.setText
import compose.widgets.*
import eve.data.*
import eve.data.typeid.isArmorMaintenanceBot
import eve.data.typeid.isCapBooster
import eve.data.typeid.isHullMaintenanceBot
import eve.data.typeid.isShieldMaintenanceBot
import kotlinx.coroutines.launch
import theorycrafter.TheorycrafterContext
import theorycrafter.fitting.utils.associateWithIndex
import theorycrafter.ui.Icons
import theorycrafter.ui.LocalSnackbarHost
import theorycrafter.ui.TheorycrafterTheme
import theorycrafter.ui.market.EveItemKind.Companion.MetaGroup
import theorycrafter.ui.widgets.ContextMenu
import theorycrafter.ui.widgets.EmptyIcon
import theorycrafter.ui.widgets.MenuItem
import theorycrafter.utils.MetaGroupOrder


/**
 * Displays the list of [EveItemType]s (loosely) in a market group.
 */
@Composable
fun MarketGroupItemsList(
    itemTypes: List<EveItemType>?,
    marketGroup: MarketGroup?,
    onItemTypeSelected: (EveItemType?) -> Unit,
    modifier: Modifier,
) {
    if (itemTypes == null) {
        Box(modifier.padding(TheorycrafterTheme.spacing.edgeMargins)) {
            Text(
                text = "No market group selected",
                modifier = Modifier.align(Alignment.Center)
            )
        }
        return
    }

    val eveData = TheorycrafterContext.eveData
    val (itemKind, sortedItemTypes) = remember(marketGroup, itemTypes) {
        val kind = with(eveData) { itemTypes.kind(marketGroup) }
        val sorted = itemTypes.sortedWith(kind.itemComparator(eveData))
        Pair(kind, sorted)
    }

    var dropdownContainerCoordinates: LayoutCoordinates? = remember { null }
    Column(
        modifier = modifier
            .onGloballyPositioned { dropdownContainerCoordinates = it }
    ) {
        val contextMenuState by remember { mutableStateOf(DropdownMenuState()) }
        val itemTypeSelectionModel = rememberSingleItemSelectionModel(sortedItemTypes)

        val columnNames = itemKind.columnNames
        if (columnNames != null) {
            SimpleGridHeaderRow(
                modifier = Modifier
                    .padding(ITEM_LIST_HORIZONTAL_ROW_PADDING)
                    .padding(vertical = TheorycrafterTheme.spacing.medium),
                columnWidths = itemKind.columnWidths,
                defaultCellContentAlignment = itemKind.columnAlignments::get
            ) {
                for ((index, columnName) in columnNames.withIndex()) {
                    if (columnName == null) {
                        EmptyCell(index)
                    }
                    else {
                        TextCell(
                            index = index,
                            text = columnName
                        )
                    }
                }
            }
        }

        val clipboard = LocalClipboard.current
        val snackbarHost = LocalSnackbarHost.current
        val coroutineScope = rememberCoroutineScope()
        fun EveItemType.copyToClipboard() {
            clipboard.setText(name)
            coroutineScope.launch {
                snackbarHost.showSnackbar("Copied \"$name\" to clipboard")
            }
        }

        ContentWithScrollbar {
            ScrollShadow(scrollState, top = true, bottom = true)

            SimpleGrid(
                modifier = Modifier
                    .verticalScroll(scrollState)
                    .padding(
                        top = if (columnNames == null) TheorycrafterTheme.spacing.verticalEdgeMargin else 0.dp
                    )
                    .focusWhenClicked()
                    .moveSelectionWithKeys(itemTypeSelectionModel, itemsInPage = { 10 })
                    .onKeyShortcut(KeyShortcut.CopyToClipboard) {
                        itemTypeSelectionModel.selectedItem()?.copyToClipboard()
                    },
                columnWidths = itemKind.columnWidths,
                defaultCellContentAlignment = itemKind.columnAlignments::get,
                defaultRowModifier = Modifier
                    .padding(ITEM_LIST_HORIZONTAL_ROW_PADDING)
                    .height(TheorycrafterTheme.sizes.marketTreeItemHeight),
                rowSelectionModel = itemTypeSelectionModel,
            ) {
                for ((rowIndex, itemType) in sortedItemTypes.withIndex()) {
                    var rowBounds: LayoutCoordinates? = remember { null }
                    row(
                        rowIndex = rowIndex,
                        modifier = Modifier
                            .onGloballyPositioned { rowBounds = it }
                            .background(TheorycrafterTheme.colors.alternatingRowBackground(rowIndex))
                            .bringIntoViewWhenSelectedWithMargins()
                            .onOpenContextMenuGesture { position ->
                                itemTypeSelectionModel.selectItem(itemType)
                                dropdownContainerCoordinates?.let { containerCoords ->
                                    rowBounds?.let { rowBounds ->
                                        contextMenuState.status = DropdownMenuState.Status.Open(
                                            position = containerCoords.localPositionOf(rowBounds, position)
                                        )
                                    }
                                }
                            }
                            .then(defaultRowModifier)
                    ) {
                        itemKind.ItemRow(this, itemType)
                    }
                }
            }

            LaunchedEffect(itemTypeSelectionModel) {
                snapshotFlow { itemTypeSelectionModel.selectedItem() }
                    .collect {
                        onItemTypeSelected(it)
                    }
            }
        }

        ContextMenu(contextMenuState) {

            @Composable
            fun menuItem(
                text: String,
                icon: (@Composable () -> Unit)? = EmptyIcon,
                keyShortcut: KeyShortcut? = null,
                action: (EveItemType) -> Unit
            ) {
                MenuItem(
                    text = text,
                    icon = icon,
                    displayedKeyShortcut = keyShortcut,
                    reserveSpaceForKeyShortcut = true,
                    onCloseMenu = contextMenuState::close,
                    action = {
                        itemTypeSelectionModel.selectedItem()?.let(action)
                    }
                )
            }

            menuItem("Copy", icon = { Icons.Copy() }, KeyShortcut.CopyToClipboard) {
                it.copyToClipboard()
            }
        }
    }
}


/**
 * Returns the given eve items, which are loosely the children of the given market group, sorted in the order they
 * should be displayed.
 */
fun List<EveItemType>.sortedForDisplay(parentMarketGroup: MarketGroup?): List<EveItemType> {
    val eveData = TheorycrafterContext.eveData
    val kind = with(eveData) { this@sortedForDisplay.kind(parentMarketGroup) }
    return this.sortedWith(kind.itemComparator(eveData))
}


/**
 * The kinds of items we can display.
 */
private class EveItemKind<T: EveItemType>(


    /**
     * The name of this kind, for debugging purposes.
     */
    private val name: String,


    /**
     * Converts the item's type to the correct one.
     */
    private val cast: (EveItemType) -> T,


    /**
     * The names of the columns.
     */
    val columnNames: List<String?>?,


    /**
     * The column widths.
     */
    val columnWidths: List<Dp>,


    /**
     * The column alignments.
     */
    val columnAlignments: List<Alignment>,


    /**
     * The comparator for sorting the items.
     */
    private val comparator: (EveData) -> Comparator<in T>,


    /**
     * The composable function for the cells of the row displaying the item.
     */
    private val itemCells: @Composable GridScope.GridRowScope.(T) -> Unit


) {


    /**
     * Returns an item comparator for sorting the items for display.
     */
    fun itemComparator(eveData: EveData): Comparator<EveItemType> {
        val comparator = comparator(eveData)
        return Comparator { a, b ->
            comparator.compare(cast(a), cast(b))
        }
    }


    /**
     * The composable for rendering the item.
     */
    @Composable
    fun ItemRow(scope: GridScope.GridRowScope, itemType: EveItemType) {
        with(scope) {
            itemCells(cast(itemType))
        }
    }


    override fun toString(): String {
        return "EveItemKind($name)"
    }

    companion object {


        /**
         * The width of the icon column.
         */
        private val IconWidth = TheorycrafterTheme.sizes.marketTreeItemIconSize +
                TheorycrafterTheme.spacing.xxsmall * 2


        /**
         * The comparator by meta level.
         *
         * Note that this is an old attribute and new items may not have it, so it should typically be used together
         * with [MetaGroup].
         */
        private val MetaLevel = compareBy(EveItemType::metaLevel)


        /**
         * The comparator by meta group.
         */
        private val MetaGroup = compareBy<EveItemType> {
            it.metaGroupId?.let { metaGroupId -> MetaGroupOrder[metaGroupId] }
        }


        /**
         * The comparator by race.
         */
        private fun Race(eveData: EveData): Comparator<EveItemType> {
            val racesOrder = with(eveData.races) {
                listOf(amarr, caldari, gallente, minmatar, ore, pirate, jove, triglavian).associateWithIndex()
            }
            return compareBy { racesOrder[it.race] }
        }


        /**
         * The comparator by item name.
         */
        private val ItemName = compareBy(EveItemType::name)


        /**
        * The comparator by variation parent name.
         */
        private fun VariationParentName(eveData: EveData) = compareBy<EveItemType> {
            with(eveData) { it.variationParentOrNull?.name }
        }


        /**
         * The comparator that puts "Civilian" modules first.
         */
        private val CivilianModulesFirst = compareBy<EveItemType> {
            when {
                it !is ModuleType -> 0
                it.name.contains("Civilian") -> -1
                else -> 1
            }
        }


        /**
         * The comparator by item volume.
         */
        private val Volume = compareBy(EveItemType::volume)


        /**
         * The comparator by whether the drone is an 'Integrated' drone.
         */
        private val IsIntegratedDrone = compareBy<EveItemType> { it.name.contains("Integrated") }


        /**
         * The comparator by the type of EWAR a drone is.
         */
        private val DroneEwarType = compareBy<EveItemType> {
            val name = it.name
            val typeEnd = name.indexOf('-')
            if (typeEnd == -1)
                return@compareBy ""
            val typeStart = name.lastIndexOf(' ', startIndex = typeEnd)
            if (typeStart == -1)
                return@compareBy ""
            name.substring(typeStart+1, typeEnd)
        }


        /**
         * The comparator for the type of defense repaired by a logistic drone.
         */
        private fun LogiDroneRepType(eveData: EveData) = compareBy<EveItemType> {
            if (it !is DroneType)
                return@compareBy 0

            with(eveData) {
                when {
                    it.isShieldMaintenanceBot() -> 1
                    it.isArmorMaintenanceBot() -> 2
                    it.isHullMaintenanceBot() -> 3
                    else -> 4
                }
            }
        }


        /**
        * The [EveItemKind] for ships.
         */
        val Ship = EveItemKind(
            name = "Ship",
            cast = { it as ShipType },
            columnNames = listOf(null, null, "High", "Med", "Low"),
            columnWidths = listOf(
                IconWidth,       // Icon
                Dp.Unspecified,  // Name
                40.dp,           // High slots
                40.dp,           // Med slots
                40.dp            // Low slots
            ),
            columnAlignments = listOf(
                Alignment.Center,       // Icon
                Alignment.CenterStart,  // Name
                Alignment.CenterEnd,    // High slots
                Alignment.CenterEnd,    // Med slots
                Alignment.CenterEnd     // Low slots
            ),
            comparator = { eveData ->
                MetaGroup then Race(eveData) then ItemName
            },
            itemCells = { ShipItemCells(it) }
        )


        /**
         * The [EveItemKind] for drones.
         */
        val Drone = EveItemKind(
            name = "Drone",
            cast = { it as DroneType },
            columnNames = null,
            columnWidths = listOf(
                IconWidth,       // Icon
                Dp.Unspecified,  // Name
            ),
            columnAlignments = listOf(
                Alignment.Center,       // Icon
                Alignment.CenterStart,  // Name
            ),
            comparator = { eveData ->
                LogiDroneRepType(eveData) then
                MetaGroup then
                MetaLevel then
                IsIntegratedDrone then
                Race(eveData) then
                DroneEwarType then // Needed because Minmatar have both SW and TP drones
                Volume then
                ItemName
            },
            itemCells = { DefaultItemCells(it) }
        )


        /**
         * The [EveItemKind] for learning attribute-enhancing implants.
         */
        val AttributeEnhancer = EveItemKind(
            name = "Attribute Enhancers",
            cast = { it as ImplantType },
            columnNames = listOf(null, null, "Bonus"),
            columnWidths = listOf(
                IconWidth,       // Icon
                Dp.Unspecified,  // Name
                80.dp,           // Bonus
            ),
            columnAlignments = listOf(
                Alignment.Center,       // Icon
                Alignment.CenterStart,  // Name
                Alignment.CenterEnd,    // Bonus
            ),
            comparator = {
                compareBy(ImplantType::skillLearningBonus) then MetaLevel then ItemName
            },
            itemCells = { AttributeEnhancingImplant(it) }
        )


        /**
         * The [EveItemKind] for modules (except rigs).
         */
        val Module = EveItemKind(
            name = "Module",
            cast = { it as ModuleType },
            columnNames = listOf(null, null, "Slot", "Power", "CPU"),
            columnWidths = listOf(
                IconWidth,       // Icon
                Dp.Unspecified,  // Name
                60.dp,           // Slot name
                60.dp,           // Powergrid
                60.dp            // CPU
            ),
            columnAlignments = listOf(
                Alignment.Center,       // Icon
                Alignment.CenterStart,  // Name
                Alignment.CenterStart,    // Powergrid
                Alignment.CenterEnd,    // Powergrid
                Alignment.CenterEnd     // CPU
            ),
            comparator = { eveData ->
                CivilianModulesFirst then VariationParentName(eveData) then MetaGroup then MetaLevel then ItemName
            },
            itemCells = { ModuleItemRow(it) }
        )


        /**
         * The [EveItemKind] for rigs (except rigs).
         */
        val Rig = EveItemKind(
            name = "Rig",
            cast = { it as ModuleType },
            columnNames = listOf(null, null, "Calibration"),
            columnWidths = listOf(
                IconWidth,       // Icon
                Dp.Unspecified,  // Name
                80.dp,           // Calibration
            ),
            columnAlignments = listOf(
                Alignment.Center,       // Icon
                Alignment.CenterStart,  // Name
                Alignment.CenterEnd     // Calibration
            ),
            comparator = { eveData ->
                VariationParentName(eveData) then MetaLevel then ItemName
            },
            itemCells = { RigItemRow(it) }
        )


        /**
         * The [EveItemKind] for cap booster charges.
         */
        val CapBoosterCharge = EveItemKind(
            name = "Cap Booster Charges",
            cast = { it as ChargeType },
            columnNames = listOf(null, null, "Volume ($METER_CUBED)"),
            columnWidths = listOf(
                IconWidth,       // Icon
                Dp.Unspecified,  // Name
                100.dp           // Volume
            ),
            columnAlignments = listOf(
                Alignment.Center,       // Icon
                Alignment.CenterStart,  // Name
                Alignment.CenterEnd     // Volume
            ),
            comparator = {
                MetaGroup then compareBy(EveItemType::volume)
            },
            itemCells = { CapBoosterChargeItemRow(it) }
        )


        /**
        * The default [EveItemKind], for mixed types and types that aren't handled specifically.
         */
        val Other = EveItemKind(
            name = "Other",
            cast = { it },
            columnNames = null,
            columnWidths = listOf(
                IconWidth,      // Icon
                Dp.Unspecified  // Name
            ),
            columnAlignments = listOf(
                Alignment.Center,       // Icon
                Alignment.CenterStart   // Name
            ),
            comparator = {
                MetaGroup then MetaLevel then ItemName
            },
            itemCells = { DefaultItemCells(it) }
        )


        /**
         * The [EveItemKind] for filaments.
         */
        val Filaments = EveItemKind(
            name = "Filaments",
            cast = { it as MiscItemType },
            columnNames = Other.columnNames,
            columnWidths = Other.columnWidths,
            columnAlignments = Other.columnAlignments,
            comparator = {
                compareBy<EveItemType>(
                    { it.name.filter { c -> !c.isDigit() } },
                    { (it as MiscItemType).maximumShipsJumped }
                )
            },
            itemCells = Other.itemCells
        )


    }


}


/**
 * Returns the [EveItemKind] of the given list of items.
 */
context(EveData)
private fun List<EveItemType>.kind(marketGroup: MarketGroup?): EveItemKind<*> {
    if (isEmpty())
        return EveItemKind.Other

    fun MarketGroup?.isDescendentOf(group: MarketGroup) = with(marketGroups) {
        if (this@isDescendentOf != null)
            group.isAncestorOf(this@isDescendentOf)
        else
            false
    }

    return when {
        all { (it is ModuleType) && (it.slotType == ModuleSlotType.RIG) } -> EveItemKind.Rig
        all { it is ModuleType } -> EveItemKind.Module
        all { it is ShipType } -> EveItemKind.Ship
        all { it is DroneType } -> EveItemKind.Drone
        all { (it as? ChargeType)?.isCapBooster() == true } -> EveItemKind.CapBoosterCharge
        marketGroup.isDescendentOf(marketGroups.filaments) -> EveItemKind.Filaments
        marketGroup.isDescendentOf(marketGroups.attributeEnhancers) -> EveItemKind.AttributeEnhancer
        else -> EveItemKind.Other
    }
}


/**
 * The horizontal padding of rows in the item list.
 */
private val ITEM_LIST_HORIZONTAL_ROW_PADDING = PaddingValues(
    start = TheorycrafterTheme.spacing.horizontalEdgeMargin,
    // Add a bit more space to take us away from the scrollbar
    end = TheorycrafterTheme.spacing.horizontalEdgeMargin + TheorycrafterTheme.spacing.small,
)


/**
 * The row cells for the item name and icon.
 */
@Composable
private fun GridScope.GridRowScope.IconAndNameItemCells(itemType: EveItemType) {
    cell(0) {
        Icons.EveItemType(
            itemType = itemType,
            modifier = Modifier
                .size(TheorycrafterTheme.sizes.marketTreeItemIconSize)
        )
    }
    cell(1) {
        SingleLineText(itemType.name)
    }
}


/**
 * The default item row.
 */
@Composable
private fun GridScope.GridRowScope.DefaultItemCells(itemType: EveItemType) {
    IconAndNameItemCells(itemType)
}


/**
 * The row for ships.
 */
@Composable
private fun GridScope.GridRowScope.ShipItemCells(shipType: ShipType) {
    IconAndNameItemCells(shipType)

    cell(2) {
        Text(shipType.fitting.slots.high.toString())
    }
    cell(3) {
        Text(shipType.fitting.slots.med.toString())
    }
    cell(4) {
        Text(shipType.fitting.slots.low.toString())
    }
}


/**
 * The row for attribute-enhancing implants.
 */
@Composable
private fun GridScope.GridRowScope.AttributeEnhancingImplant(implantType: ImplantType) {
    IconAndNameItemCells(implantType)

    cell(2) {
        Text(implantType.skillLearningBonus?.let { "+$it" } ?: "")
    }
}


/**
 * The row for modules.
 */
@Composable
private fun GridScope.GridRowScope.ModuleItemRow(moduleType: ModuleType) {
    IconAndNameItemCells(moduleType)

    cell(2) {
        Text(moduleType.slotType.slotName)
    }
    cell(3) {
        Text(moduleType.powerNeed?.asPower(withUnits = false) ?: "")
    }
    cell(4) {
        Text(moduleType.cpuNeed?.asCpu(withUnits = false) ?: "")
    }
}


/**
 * The row for rigs.
 */
@Composable
private fun GridScope.GridRowScope.RigItemRow(moduleType: ModuleType) {
    IconAndNameItemCells(moduleType)

    cell(2) {
        Text(moduleType.calibrationNeed?.asCalibration(withUnits = false) ?: "")
    }
}


/**
 * The row for cap booster charges.
 */
@Composable
private fun GridScope.GridRowScope.CapBoosterChargeItemRow(chargeType: ChargeType) {
    IconAndNameItemCells(chargeType)

    cell(2) {
        Text(chargeType.volume.asVolume(withUnits = false))
    }
}