package theorycrafter.ui

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.DropdownMenuState
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import compose.input.*
import compose.utils.*
import compose.widgets.*
import eve.data.ShipType
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import theorycrafter.fits.FitHandle
import theorycrafter.LocalTheorycrafterWindowManager
import theorycrafter.TestTags
import theorycrafter.TheorycrafterContext
import theorycrafter.ui.Icons.EveItemType
import theorycrafter.ui.Icons.Flagship
import theorycrafter.ui.widgets.*
import theorycrafter.utils.*
import java.text.SimpleDateFormat
import java.util.Date


/**
 * The panel displaying the list of fits, fits search and buttons.
 */
@Composable
fun FitListPanel(
    onRenameFit: (FitHandle, String) -> Unit,
    onCreateNewFit: () -> Unit,
    onImportFitFromClipboard: () -> Unit,
    onImportFitsFromXmlFile: () -> Unit,
    onImportFitsFromPyfa: () -> Unit,
    onExportFitsToXmlFile: () -> Unit,
    onDeleteFits: (Collection<FitHandle>) -> Unit,
    displayedFitHandle: FitHandle?
) {
    var searchText by remember { mutableStateOf("") }
    val query by remember {
        derivedStateOf {
            val pinnedSearches = TheorycrafterContext.settings.pinnedFitSearches
            buildString {
                pinnedSearches.joinTo(this, separator = " ")
                if (pinnedSearches.isNotEmpty() && searchText.isNotEmpty()) {
                    append(" ")
                }
                append(searchText)
            }
        }
    }

    val fitManagement = TheorycrafterContext.fits
    val filteredFitHandles: List<FitHandle> = remember(fitManagement.handlesKey, query) {
        runBlocking {
            TheorycrafterContext.queryFits(query) ?: fitManagement.handles
        }
    }
    val queryIsEmpty = query.isEmpty()

    val fitLazyListState = rememberLazyListState()
    val fitListState = remember(fitLazyListState) {
        FitListState(filteredFitHandles, fitLazyListState, queryIsEmpty)
    }.also {
        it.isQueryEmpty = queryIsEmpty
        remember(filteredFitHandles) {  // Only update when it actually changed, to avoid resetting the selection
            it.fitHandles = filteredFitHandles
        }
    }

    val fitSelectionModel = fitListState.selectionModel

    // When given a displayedFitHandle, select it in the list
    LaunchedEffect(fitSelectionModel, displayedFitHandle, queryIsEmpty) {
        if ((displayedFitHandle != null) && queryIsEmpty) {  // When there's a query, leave the selection alone
            val index = filteredFitHandles.indexOfOrNull(displayedFitHandle) ?: return@LaunchedEffect
            fitSelectionModel.selectIndex(index)
        }
    }

    // When a selected fit is removed from the end, select the last one instead
    LaunchedEffect(fitListState.fitHandles.size) {
        val selectedIndex = fitSelectionModel.selectedIndex
        if ((selectedIndex != null) && (selectedIndex > fitListState.fitHandles.lastIndex))
            fitSelectionModel.selectIndex(fitListState.fitHandles.lastIndex)
    }

    val fitOpener = LocalFitOpener.current
    val fitCopier = rememberFitCopier()
    val coroutineScope = rememberCoroutineScope()

    val currentFitListState by rememberUpdatedState(fitListState)

    fun duplicateFit(fitHandle: FitHandle) {
        coroutineScope.launch {
            val fitsContext = TheorycrafterContext.fits
            val copyName = copyName(fitHandle.name, allNames = fitsContext.handles.asSequence().map { it.name })
            val newFitHandle = fitsContext.duplicate(fitHandle, name = copyName)
            fitOpener.openFitPreferCurrentWindow(newFitHandle)
            searchText = ""

            // Let the scrolling to bring it into view complete.
            // For some reason setting fitHandleBeingRenamed immediately interrupts the scrolling.
            launch {
                delay(250)
                // It's appended to the end, so verifying it exists this way is quicker
                if (currentFitListState.fitHandles.lastIndexOf(newFitHandle) != -1) {
                    currentFitListState.fitHandleBeingRenamed = newFitHandle
                }
            }
        }
    }

    Column(
        modifier = Modifier
            .width(TheorycrafterTheme.sizes.savedFitsPanelWidth)
            .fillMaxHeight()
            .moveSelectionWithKeys(
                selectionModel = fitSelectionModel,
                itemsInPage = fitListState.lazyListState::itemsInPage,
                onPreview = true
            )
            .onKeyShortcut(FitListKeyShortcuts.RenameFit, onPreview = true) {
                fitListState.fitHandleBeingRenamed = fitSelectionModel.selectedHandle()
            }
            .onKeyShortcut(FitListKeyShortcuts.EditTags, onPreview = true) {
                fitListState.fitHandleForTagEditing = fitSelectionModel.selectedHandle()
            }
            .onKeyShortcut(FitListKeyShortcuts.DeleteFit) {
                fitSelectionModel.selectedHandle()?.let {
                    onDeleteFits(listOf(it))
                }
            }
            .onKeyShortcut(KeyShortcut.anyEnter(), onPreview = true) {
                // While the fit is being renamed, pressing ENTER should apply the new name,
                // so we don't want to handle or consume the event
                // Remove this after moving the fit-renaming into a dialog
                if (fitListState.fitHandleBeingRenamed != null) {
                    consumeEvent = false
                    return@onKeyShortcut
                }

                fitSelectionModel.selectedHandle()?.let {
                    fitOpener.openFitPreferCurrentWindow(it)
                }
            }
            .onKeyShortcut(KeyShortcut.anyEnter(KeyboardModifierMatcher.Command), onPreview = true) {
                fitSelectionModel.selectedHandle()?.let {
                    fitOpener.openFitInSecondaryWindow(it)
                }
            }
            .onKeyShortcut(FitListKeyShortcuts.CopyFitToClipboard, onPreview = true) {
                // While the fit is being renamed, pressing Ctrl-C should copy the text in the textfield,
                // so we don't want to handle or consume the event
                // Remove this after moving the fit-renaming into a dialog
                if (fitListState.fitHandleBeingRenamed != null) {
                    consumeEvent = false
                    return@onKeyShortcut
                }

                fitListState.selectionModel.selectedHandle()?.let {
                    fitCopier.copy(it)
                }
            }
    ) {
        FitSearchField(
            modifier = Modifier
                .fillMaxWidth()
                .padding(bottom = TheorycrafterTheme.spacing.medium)
                .padding(horizontal = TheorycrafterTheme.spacing.horizontalEdgeMargin),
            searchText = searchText,
            onSearched = { searchText = it },
            pinnedSearches = TheorycrafterContext.settings.pinnedFitSearches,
            onChangePinnedSearches = { TheorycrafterContext.settings.pinnedFitSearches = it }
        )

        FitList(
            state = fitListState,
            currentlyDisplayedFit = displayedFitHandle,
            modifier = Modifier
                .fillMaxWidth()
                .weight(1f),
            onRenameFit = onRenameFit,
            onDuplicateFit = { fitHandle ->
                duplicateFit(fitHandle)
            },
            onDeleteFits = onDeleteFits,
            fitCopier = fitCopier
        )

        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(
                    horizontal = TheorycrafterTheme.spacing.horizontalEdgeMargin,
                    vertical = TheorycrafterTheme.spacing.medium
                ),
            horizontalArrangement = Arrangement.spacedBy(TheorycrafterTheme.spacing.small, Alignment.End)
        ) {
            val tooltipPlacement = EasyTooltipPlacement.ElementTopCenter(
                offset = DpOffsetY(y = -TheorycrafterTheme.spacing.xxsmall)
            )
            TheorycrafterTheme.RaisedButtonWithText(
                text = "New Fit",
                onClick = onCreateNewFit,
                modifier = Modifier
                    .nonFocusable()
                    .tooltip(
                        text = "Create new fit",
                        keyShortcut = MainWindowKeyShortcuts.CreateNewFit,
                        placement = tooltipPlacement
                    )
            )
            ImportFitsButton(
                modifier = Modifier
                    .nonFocusable()
                    .tooltip(
                        text = "Import fits from external sources",
                        placement = tooltipPlacement
                    ),
                onImportFitFromClipboard = onImportFitFromClipboard,
                onImportFitsFromXmlFile = onImportFitsFromXmlFile,
                onImportFitsFromPyfa = onImportFitsFromPyfa,
            )
            ExportFitsButton(
                modifier = Modifier
                    .nonFocusable()
                    .tooltip(
                        text = "Export fits to external formats",
                        placement = tooltipPlacement
                    ),
                onExportFitsToXmlFile = onExportFitsToXmlFile,
            )
        }
    }

    fitListState.fitHandleForTagEditing?.let {
        FitTagEditorDialog(
            fitHandle = it,
            onDismiss = { fitListState.fitHandleForTagEditing = null }
        )
    }
}


/**
 * The fits search field and pinned searches.
 */
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun FitSearchField(
    modifier: Modifier,
    searchText: String,
    onSearched: (String) -> Unit,
    pinnedSearches: List<String>,
    onChangePinnedSearches: (List<String>) -> Unit,
) {
    fun pinCurrentSearch() {
        onSearched("")
        onChangePinnedSearches(pinnedSearches + searchText.trim())
    }

    Column(modifier) {
        VerticallyCenteredRow(
            modifier = Modifier
                .fillMaxWidth()
                .height(IntrinsicSize.Min)
        ) {
            SearchField(
                modifier = Modifier.weight(1f),
                textFieldModifier = Modifier
                    .testTag(TestTags.FitList.SearchField)
                    .requestInitialFocus()
                    .onKeyShortcut(FitListKeyShortcuts.PinSearchShortcut, onPreview = true) {
                        pinCurrentSearch()
                    },
                searchText = searchText,
                placeholderText = "Fit or ship name, or #tag",
                focusKeyShortcut = MainWindowKeyShortcuts.FocusFitSearchField,
                onSearched = onSearched
            )

            AnimatedVisibility(visible = searchText.isNotEmpty()) {
                Row {
                    HSpacer(TheorycrafterTheme.spacing.xsmall)
                    IconButton(
                        onClick = { pinCurrentSearch() },
                        modifier = Modifier
                            .size(32.dp)
                            .tooltip("Pin search", keyShortcut = FitListKeyShortcuts.PinSearchShortcut.first())
                    ) {
                        Icons.PushPin(
                            modifier = Modifier
                                .padding(TheorycrafterTheme.spacing.xsmall)
                        )
                    }
                }
            }
        }

        if (pinnedSearches.isNotEmpty()) {
            VSpacer(TheorycrafterTheme.spacing.medium)
            FlowRow(
                horizontalArrangement = Arrangement.spacedBy(TheorycrafterTheme.spacing.xxsmall),
                verticalArrangement = Arrangement.spacedBy(TheorycrafterTheme.spacing.xsmall),
            ) {
                SingleLineText(
                    text = "Pinned:",
                    style = TheorycrafterTheme.textStyles.smallHeading,
                    modifier = Modifier.align(Alignment.CenterVertically)
                )
                for ((index, pinnedSearch) in pinnedSearches.withIndex()) {
                    CloseableChip(
                        text = pinnedSearch,
                        closeIconModifier = Modifier
                            .tooltip("Unpin search"),
                        onClose = {
                            onChangePinnedSearches(pinnedSearches.withIndexRemoved(index))
                        }
                    )
                }
            }
        }
    }
}


/**
 * The state object for the [FitList].
 */
@Stable
private class FitListState(


    /**
     * The fit handles to display.
     */
    fitHandles: List<FitHandle>,


    /**
     * The state of the actual lazy list displaying the fit handles.
     */
    val lazyListState: LazyListState,


    /**
     * Whether the fit query is empty.
     */
    isQueryEmpty: Boolean


) {


    /**
     * The fit handles to display.
     */
    var fitHandles: List<FitHandle> by mutableStateOf(fitHandles).onSet { updatedFitHandles ->
        selectionModel = SingleItemListSelectionModel(
            items = updatedFitHandles,
            initialSelectedIndex = if (updatedFitHandles.isEmpty()) null else 0
        )
    }


    /**
     * Whether the fit query is empty.
     */
    var isQueryEmpty: Boolean by mutableStateOf(isQueryEmpty)


    /**
     * The selection model.
     */
    var selectionModel by mutableStateOf(SingleItemListSelectionModel(fitHandles))
        private set


    /**
     * The fit currently being renamed; `null` if none.
     */
    var fitHandleBeingRenamed: FitHandle? by mutableStateOf(null)


    /**
     * The fit show tags are being edited; `null` if none.
     */
    var fitHandleForTagEditing: FitHandle? by mutableStateOf(null)


}


/**
 * Returns the selected [FitHandle] in the selection model.
 */
private fun SingleItemListSelectionModel<FitHandle>.selectedHandle() = selectedItem()


/**
 * The list of fits itself.
 */
@Composable
private fun FitList(
    state: FitListState,
    currentlyDisplayedFit: FitHandle?,
    modifier: Modifier = Modifier,
    onRenameFit: (FitHandle, String) -> Unit,
    onDuplicateFit: (FitHandle) -> Unit,
    onDeleteFits: (Collection<FitHandle>) -> Unit,
    fitCopier: FitCopier,
) {
    val fitHandles = state.fitHandles
    if (fitHandles.isEmpty()) {
        Text(
            text = if (state.isQueryEmpty)
                "No fits yet"
            else
                "No matching fits",
            textAlign = TextAlign.Center,
            modifier = modifier
                .fillMaxWidth()
                .padding(top = TheorycrafterTheme.spacing.xxlarge),
        )

        return
    }

    val selectionModel = state.selectionModel
    val fitOpener = LocalFitOpener.current

    // Need to Box up so that ContextMenu has the same bounds as the list
    Box(modifier) {
        val focusRequester = remember { FocusRequester() }
        val contextMenuState by remember { mutableStateOf(DropdownMenuState()) }

        ScrollShadow(state.lazyListState, top = true, bottom = true)

        LazyColumnExt(
            state = state.lazyListState,
            selection = selectionModel,
            selectionBackground = TheorycrafterTheme.colors.selectedItemBackground(),
            modifier = Modifier
                .testTag(TestTags.FitList.FitList)
                .onOpenContextMenuGesture { position ->
                    state.lazyListState.itemIndexAt(position.y)?.let { clickedIndex ->
                        selectionModel.selectIndex(clickedIndex)
                        contextMenuState.status = DropdownMenuState.Status.Open(position)
                    }
                }
                .focusWhenClicked(focusRequester)
        ) {
            items(count = fitHandles.size) { index ->
                val fitHandle = fitHandles[index]
                val shipType = TheorycrafterContext.eveData.shipType(fitHandle.shipTypeId)

                if (fitHandle == state.fitHandleBeingRenamed) {
                    FitNameEditor(
                        fitName = fitHandle.name,
                        shipType = shipType,
                        isFlagship = fitHandle.storedFit?.isFlagship ?: false,
                        modifier = Modifier.fillMaxWidth(),
                        onRename = { name ->
                            onRenameFit(fitHandle, name)
                        },
                        onEditingFinished = {
                            state.fitHandleBeingRenamed = null
                            focusRequester.requestFocus()
                        }
                    )
                }
                else {
                    FitListItem(
                        fitHandle = fitHandle,
                        isCurrentlyDisplayed = fitHandle == currentlyDisplayedFit,
                        modifier = Modifier
                            .fillMaxWidth()
                            .onMousePress(clickCount = ClickCount.DOUBLE, consumeEvent = true) {
                                fitOpener.openFitPreferCurrentWindow(fitHandle)
                            }
                            .onMousePress(
                                clickCount = ClickCount.DOUBLE,
                                keyboardModifier = KeyboardModifierMatcher.Command
                            ) {
                                fitOpener.openFitInSecondaryWindow(fitHandle)
                            }
                            .highlightOnHover()
                    )
                }
            }
        }

        val windowManager = LocalTheorycrafterWindowManager.current
        val coroutineScope = rememberCoroutineScope()
        ContextMenu(contextMenuState) {

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

            menuItem("Open in New Window", icon = { Icons.OpenInNew() }, KeyShortcut(Key.Enter, KeyboardModifierMatcher.Command)) {
                fitOpener.openFitInSecondaryWindow(it)
            }
            menuItem("Ship Info", icon = { Icons.EveItemInfo() }, keyShortcut = null) { fitHandle ->
                coroutineScope.launch {
                    val fit = TheorycrafterContext.fits.engineFitOf(fitHandle)
                    windowManager.showItemInfoWindow(fit.ship)
                }
            }
            MenuSeparator()
            menuItem("Copy", icon = { Icons.Copy() }, FitListKeyShortcuts.CopyFitToClipboard) {
                fitCopier.copy(it)
            }
            menuItem("Copy with Options…", icon = { Icons.CopyWithOptions() }, keyShortcut = null) {
                fitCopier.copyWithOptions(it)
            }
            menuItem("Rename", icon = { Icons.Edit() }, FitListKeyShortcuts.RenameFit) {
                state.fitHandleBeingRenamed = it
            }
            menuItem("Edit Tags…", icon = { Icons.Hashtag() }, keyShortcut = FitListKeyShortcuts.EditTags ) {
                state.fitHandleForTagEditing = it
            }
            MenuSeparator()
            menuItem("Duplicate", icon = { Icons.Duplicate() }, keyShortcut = null) {
                onDuplicateFit(it)
            }
            menuItem("Delete", icon = { Icons.Delete() }, FitListKeyShortcuts.DeleteFit.first()) {
                onDeleteFits(listOf(it))
            }
        }
    }
}


/**
 * The "Import Fits" button.
 */
@Composable
private fun ImportFitsButton(
    modifier: Modifier,
    onImportFitFromClipboard: () -> Unit,
    onImportFitsFromXmlFile: () -> Unit,
    onImportFitsFromPyfa: () -> Unit
) {
    MenuButton(
        modifier = modifier,
        offset = DpOffsetY(y = TheorycrafterTheme.spacing.xxxsmall),
        content = { onClick ->
            TheorycrafterTheme.RaisedButtonWithText(
                text = "Import",
                onClick = onClick
            )
        },
        menuContent = { onCloseMenu ->

            @Composable
            fun menuItem(
                text: String,
                icon: (@Composable () -> Unit)? = null,
                keyShortcut: KeyShortcut? = null,
                action: () -> Unit) {
                MenuItem(
                    text = text,
                    icon = icon,
                    displayedKeyShortcut = keyShortcut,
                    reserveSpaceForKeyShortcut = true,
                    keyShortcutDescriptorWidth = TheorycrafterTheme.sizes.menuItemKeyShortcutWidthMedium,
                    onCloseMenu = onCloseMenu,
                    action = action
                )
            }

            menuItem(
                text = "Import fits from Pyfa…",
                icon = { Icons.ImportFromPyfa() },
                action = onImportFitsFromPyfa
            )
            menuItem(
                text = "Import fits from XML File…",
                icon = { Icons.ImportFromFile() },
                action = onImportFitsFromXmlFile
            )
            menuItem(
                text = "Paste fit from Clipboard",
                icon = { Icons.Paste() },
                keyShortcut = MainWindowKeyShortcuts.PasteFitFromClipboard,
                action = onImportFitFromClipboard
            )
        }
    )
}

/**
 * The "Export Fits" button.
 */
@Composable
private fun ExportFitsButton(
    modifier: Modifier,
    onExportFitsToXmlFile: () -> Unit
) {
    MenuButton(
        modifier = modifier,
        offset = DpOffsetY(y = TheorycrafterTheme.spacing.xxxsmall),
        content = { onClick ->
            TheorycrafterTheme.RaisedButtonWithText(
                text = "Export",
                onClick = onClick
            )
        },
        menuContent = { onCloseMenu ->

            @Composable
            fun menuItem(
                text: String,
                icon: (@Composable () -> Unit)? = null,
                keyShortcut: KeyShortcut? = null,
                action: () -> Unit) {
                MenuItem(
                    text = text,
                    icon = icon,
                    displayedKeyShortcut = keyShortcut,
                    reserveSpaceForKeyShortcut = true,
                    keyShortcutDescriptorWidth = TheorycrafterTheme.sizes.menuItemKeyShortcutWidthMedium,
                    onCloseMenu = onCloseMenu,
                    action = action
                )
            }

            menuItem(
                text = "Export fits to XML File…",
                icon = { Icons.Share() },
                action = onExportFitsToXmlFile
            )
        }
    )
}


/**
 * The format for displaying the fit creation and last modification times.
 */
private val FitTimesFormatter = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.MEDIUM, SimpleDateFormat.SHORT)


/**
 * An item in the fit list.
 */
@Composable
private fun FitListItem(
    fitHandle: FitHandle,
    isCurrentlyDisplayed: Boolean,
    modifier: Modifier = Modifier,
) {
    val storedFit = TheorycrafterContext.fits.storedFitOf(fitHandle)
    val shipType = TheorycrafterContext.eveData.shipType(storedFit.shipTypeId)
    val isFlagship = storedFit.isFlagship
    val tags = storedFit.tags
    val localTextStyle = LocalTextStyle.current
    val detailedTextStyle = TheorycrafterTheme.textStyles.detailedText().toSpanStyle()
    FitListItemLayout(
        shipType = shipType,
        isFlagship = isFlagship,
        showCurrentItemIndicator = isCurrentlyDisplayed,
        modifier = modifier
            .testTag(TestTags.FitList.Item.Item)
            .tooltip(
                annotatedText = {
                    buildAnnotatedString {
                        appendLine(shipType.name)
                        withStyle(SpanStyle(fontSize = localTextStyle.fontSize/3)) { appendLine() }
                        if (tags.isNotEmpty()) {
                            withStyle(detailedTextStyle) {
                                for (tag in tags.take(4)) {
                                    append("#$tag ")
                                }
                                if (tags.size > 4) {
                                    appendLine("+${tags.size - 4} more")
                                } else {
                                    appendLine()
                                }
                            }
                            withStyle(SpanStyle(fontSize = localTextStyle.fontSize/3)) { appendLine() }
                        }
                        withStyle(detailedTextStyle) {
                            append(
                                """
                            Created: ${FitTimesFormatter.format(Date(storedFit.creationTime))}
                            Last modified: ${FitTimesFormatter.format(Date(storedFit.lastModifiedTime))}
                            """.trimIndent()
                            )
                        }
                    }
                },
                placement = EasyTooltipPlacement.ElementCenterEnd(
                    offset = DpOffsetX(TheorycrafterTheme.spacing.xxxsmall)
                )
            ),
    ) {
        Text(
            text = fitHandle.name,
            overflow = TextOverflow.Ellipsis,
            maxLines = 2,
            modifier = Modifier
                .testTag(TestTags.FitList.Item.FitName)
                .weight(1f)
        )

        if (tags.isNotEmpty()) {
            HSpacer(TheorycrafterTheme.spacing.medium)

            val text = buildString {
                append("#")
                append(tags.first())
                if (tags.size > 1) {
                    append(" +")
                    append(tags.size - 1)
                }
            }
            SingleLineText(
                text = text,
                style = TheorycrafterTheme.textStyles.fitListTag()
            )
        }
    }
}


/**
 * The fit name being edited.
 */
@Composable
private fun FitNameEditor(
    fitName: String,
    shipType: ShipType,
    isFlagship: Boolean,
    modifier: Modifier,
    onRename: (String) -> Unit,
    onEditingFinished: () -> Unit,
) {
    FitListItemLayout(
        shipType = shipType,
        isFlagship = isFlagship,
        modifier = modifier
    ) {
        ItemNameEditor(
            itemName = fitName,
            modifier = Modifier.fillMaxWidth(),
            onRename = onRename,
            onEditingFinished = onEditingFinished
        )
    }
}


/**
 * The layout for an item in the fit list; used both for the regular item and while being edited.
 */
@Composable
private fun FitListItemLayout(
    shipType: ShipType,
    isFlagship: Boolean,
    showCurrentItemIndicator: Boolean = false,
    modifier: Modifier = Modifier,
    fitNameContent: @Composable RowScope.() -> Unit,
) {
    Box(Modifier.height(TheorycrafterTheme.sizes.fitListItemHeight)) {  // Need fixed size for scrolling to work well
        VerticallyCenteredRow(
            modifier = modifier
                .wrapContentHeight(Alignment.CenterVertically, unbounded = true)
                .padding(
                    horizontal = TheorycrafterTheme.spacing.horizontalEdgeMargin,
                    vertical = TheorycrafterTheme.spacing.xsmall
                ),
        ) {
            Box(Modifier.size(TheorycrafterTheme.sizes.eveTypeIconMedium)) {
                EveItemType(
                    itemType = shipType,
                    modifier = Modifier.fillMaxSize()
                )
                if (isFlagship) {
                    Flagship(
                        Modifier
                            .fillMaxSize(fraction = 0.5f)
                            .align(Alignment.BottomEnd)
                    )
                }
            }

            HSpacer(TheorycrafterTheme.spacing.small)

            fitNameContent()
        }

        if (showCurrentItemIndicator) {
            Box(Modifier
                .padding(start = TheorycrafterTheme.spacing.horizontalEdgeMargin/4)
                .size(4.dp)
                .align(Alignment.CenterStart)
                .background(
                    color = TheorycrafterTheme.colors.currentItemIndicator,
                    shape = CircleShape,
                )
            )
        }
    }
}


/**
 * The key shortcuts in the fit list.
 */
private object FitListKeyShortcuts {


    /**
     * The shortcut to copy the fit to the clipboard.
     */
    val CopyFitToClipboard = KeyShortcut.CopyToClipboard


    /**
     * The shortcut to rename the fit.
     */
    val RenameFit = KeyShortcut.RenameItem


    /**
     * The shortcut to delete the fit.
     */
    val DeleteFit = KeyShortcut.DeleteItem


    /**
     * The shortcut to edit the fit tags.
     */
    val EditTags = KeyShortcut(Key.F3)


    /**
     * The shortcut to pin a search.
     */
    val PinSearchShortcut = KeyShortcut.anyEnter(KeyboardModifierMatcher.Alt)


}
