/**
 * The subsystems selector for the fit editor.
 */

package theorycrafter.ui.fiteditor

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.width
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ClipboardManager
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextAlign
import compose.utils.HSpacer
import compose.widgets.GridScope
import eve.data.ShipType
import eve.data.SubsystemType
import kotlinx.coroutines.launch
import theorycrafter.LocalTheorycrafterWindowManager
import theorycrafter.TestTags
import theorycrafter.TheorycrafterContext
import theorycrafter.fitting.Fit
import theorycrafter.fitting.FittingEngine
import theorycrafter.fitting.Subsystem
import theorycrafter.ui.LocalSnackbarHost
import theorycrafter.ui.TheorycrafterTheme
import theorycrafter.ui.fiteditor.effectcolumn.displayedSubsystemEffect
import theorycrafter.ui.fiteditor.effectcolumn.displayedSubsystemFitResources
import theorycrafter.ui.fiteditor.effectcolumn.shortEffectDescription
import theorycrafter.ui.shortName
import theorycrafter.ui.widgets.SlotRow
import theorycrafter.utils.AutoSuggest
import theorycrafter.utils.CompositionCounters
import theorycrafter.utils.onEmptyQueryReturn


/**
 * Returns a [FitEditingAction] that sets the subsystem of the given kind of the fit.
 */
private fun setSubsystemAction(fit: Fit, newType: SubsystemType): FitEditingAction? {
    val kind = newType.kind
    val prevType = fit.requiredSubsystemByKind!![kind].type
    if (prevType == newType)
        return null

    return object: FitEditingAction() {

        context(FitEditorUndoRedoContext)
        override fun FittingEngine.ModificationScope.performEdit() {
            fit.setSubsystem(newType)
        }

        context(FitEditorUndoRedoContext)
        override fun FittingEngine.ModificationScope.revertEdit() {
            fit.setSubsystem(prevType)
        }

    }
}


/**
 * Returns the auto-suggest for subsystems.
 */
@Composable
private fun ShipType.rememberAutoSuggest(
    subsystemKind: SubsystemType.Kind,
    carousel: Carousel<SubsystemType>,
): AutoSuggest<SubsystemType> {
    val autoSuggest = TheorycrafterContext.autoSuggest.subsystemTypes(this, subsystemKind)

    return remember(autoSuggest, carousel) {
        autoSuggest.onEmptyQueryReturn { carousel.items }
    }
}


/**
 * A subsystem selection widget.
 */
@Composable
private fun GridScope.GridRowScope.SubsystemSelectorRow(
    subsystemKind: SubsystemType.Kind,
    carousel: Carousel<SubsystemType>,
    onSubsystemSelected: (SubsystemType) -> Unit,
    onEditingCancelled: () -> Unit
) {
    val shipType = LocalFit.current.ship.type
    val autoSuggest = shipType.rememberAutoSuggest(subsystemKind, carousel)

    ItemSelectorRow(
        onItemSelected = { onSubsystemSelected(it) },
        onEditingCancelled = onEditingCancelled,
        autoSuggest = autoSuggest,
        autoSuggestItemToString = { it.shortName(includeKind = false) },
        suggestedItemContent = { subsystemType, text ->
            DefaultSuggestedEveItemTypeIcon(subsystemType)
            Text(text = text, Modifier.weight(1f))
            HSpacer(TheorycrafterTheme.spacing.medium)
            Text(
                text = subsystemType.displayedSubsystemFitResources()?.text ?: "",
                style = TheorycrafterTheme.textStyles.caption,
                textAlign = TextAlign.End
            )
            HSpacer(TheorycrafterTheme.spacing.small)
            Text(
                modifier = Modifier
                    .width(TheorycrafterTheme.sizes.fitEditorSubsystemEffectDescriptionInAutoSuggestWidth),
                text = subsystemType.shortEffectDescription() ?: "",
                style = TheorycrafterTheme.textStyles.caption,
                textAlign = TextAlign.End
            )
            if (TheorycrafterContext.settings.prices.showInFitEditorSuggestedItems) {
                ItemPrice(
                    itemType = subsystemType,
                    textAlign = TextAlign.End,
                    modifier = Modifier.width(TheorycrafterTheme.sizes.fitEditorSuggestedItemsPriceWidth)
                )
            }
        },
        hint = "${subsystemKind.displayName} Subsystem"
    )
}


/**
 * The row for a non-edited subsystem.
 */
@Composable
private fun GridScope.GridRowScope.SubsystemSlotContent(
    subsystem: Subsystem,
    carousel: Carousel<SubsystemType>
) {
    emptyCell(cellIndex = GridCols.STATE_ICON)  // No state icon
    cell(cellIndex = GridCols.TYPE_ICON) {
        TypeIconCellContent(subsystem)
    }
    cell(cellIndex = GridCols.NAME, colSpan = 3) {
        CarouselSlotContent(
            carousel = carousel,
            targetState = subsystem.type,
            modifier = Modifier.fillMaxWidth(),
            text = {
                it.shortName(includeKind=true)  // Skip the ship name; it's redundant
            }
        )
    }
    cell(cellIndex = GridCols.RANGE) {
        TextAndTooltipCell(subsystem.type.displayedSubsystemFitResources())
    }
    cell(cellIndex = GridCols.EFFECT) {
        TextAndTooltipCell(displayedSubsystemEffect(LocalFit.current, subsystem))
    }
    PriceCell(subsystem.type)
}


/**
 * The [SlotContextAction] for pasting into a subsystem slot.
 */
@Composable
private fun SlotContextAction.Companion.rememberPasteSubsystem(
    clipboardManager: ClipboardManager,
    shipType: ShipType,
    subsystemKind: SubsystemType.Kind,
    setSubsystem: (SubsystemType) -> Unit,
): SlotContextAction {
    val snackbarHostState = LocalSnackbarHost.current
    val coroutineScope = rememberCoroutineScope()

    return remember(snackbarHostState, coroutineScope, shipType, subsystemKind, clipboardManager, setSubsystem) {
        pasteFromClipboard(
            clipboardManager = clipboardManager,
            itemFromClipboardText = ::subsystemFromClipboardText,
            pasteItem = { subsystemType ->
                val errorMessage = when {
                    subsystemType.shipId != shipType.itemId -> "Wrong ship type for subsystem ${subsystemType.name}"
                    subsystemKind != subsystemType.kind -> "Wrong subsystem for slot"
                    else -> null
                }
                if (errorMessage != null)
                    coroutineScope.launch { snackbarHostState.showSnackbar(errorMessage) }
                else
                    setSubsystem(subsystemType)
            },
        )
    }
}


/**
 * The row for a subsystem.
 */
@Composable
private fun GridScope.SubsystemRow(
    subsystem: Subsystem,
    actions: SubsystemActions,
) {
    val subsystemKind = subsystem.type.kind

    val testTag = TestTags.FitEditor.subsystemRow(subsystemKind)
    CompositionCounters.recomposed(testTag)

    val shipType = LocalFit.current.ship.type
    val carousel = rememberSubsystemCarousel(shipType, subsystemKind)
    val clipboardManager = LocalClipboardManager.current
    val pasteAction = SlotContextAction.rememberPasteSubsystem(clipboardManager, subsystem.fit.ship.type, subsystemKind) {
        actions.setSubsystem(it)
    }
    val windowManager = LocalTheorycrafterWindowManager.current
    val editPriceOverrideAction = SlotContextAction.rememberEditPriceOverride(subsystem.type)
    val contextActions = remember(subsystem, subsystem.fit.ship, actions, clipboardManager, windowManager, editPriceOverrideAction) {
        listOf(
            SlotContextAction.showInfo(windowManager, subsystem),
            SlotContextAction.Separator,
            SlotContextAction.copyToClipboard(clipboardManager, subsystem::clipboardText),
            pasteAction,
            SlotContextAction.Separator,
            editPriceOverrideAction
        )
    }

    val keyShortcuts = Modifier
        .carouselShortcuts(carousel, subsystem.type) {
            // There's always a subsystem fit,
            actions.setSubsystem(it, triggerCarouselAnimation = true)
        }

    SlotRow(
        modifier = Modifier
            .testTag(testTag),
        modifierWhenNotEditing = keyShortcuts,
        contextActions = contextActions,
        invalidityReason = subsystem.illegalFittingReason,
        editedRowContent = { onEditingCompleted ->
            SubsystemSelectorRow(
                subsystemKind = subsystemKind,
                carousel = carousel,
                onSubsystemSelected = {
                    actions.setSubsystem(it)
                    onEditingCompleted()
                },
                onEditingCancelled = onEditingCompleted
            )
        }
    ) {
        SubsystemSlotContent(
            subsystem = subsystem,
            carousel = carousel
        )
    }
}


/**
 * Bundles the actions passed to [SubsystemRow].
 */
private class SubsystemActions(
    private val set: (SubsystemType, triggerCarouselAnimation: Boolean) -> Unit,
) {

    fun setSubsystem(subsystemType: SubsystemType, triggerCarouselAnimation: Boolean = false) {
        set(subsystemType, triggerCarouselAnimation)
    }

}


/**
 * The section for selecting the subsystems.
 */
@Composable
fun GridScope.SubsystemsSection(
    firstRowIndex: Int,
    isFirst: Boolean,
    fit: Fit,
): Int {
    val subsystemByKind = fit.subsystemByKind ?: return 0

    var rowIndex = firstRowIndex

    // Section title
    SectionTitleRow(
        rowIndex = rowIndex++,
        isFirst = isFirst,
        text = AnnotatedString("Subsystems")
    )

    val undoRedoQueue = LocalFitEditorUndoRedoQueue.current
    for (subsystemKind in SubsystemType.Kind.entries) {
        inRow(rowIndex++) {
            // We set all subsystems before displaying a fit
            val subsystem = subsystemByKind[subsystemKind]!!
            SubsystemRow(
                subsystem = subsystem,
                actions = remember(fit, undoRedoQueue) {
                    SubsystemActions(
                        set = { newSubsystem, triggerCarouselAnimation ->
                            setSubsystemAction(fit, newSubsystem)?.let {
                                undoRedoQueue.performAndAppend(
                                    it.withCarouselAnimation(triggerCarouselAnimation)
                                )
                            }
                        }
                    )
                }
            )
        }
    }

    return rowIndex - firstRowIndex
}
