package theorycrafter.ui

import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import compose.utils.VSpacer
import compose.widgets.FlatButtonWithText
import compose.widgets.LazyColumnExt
import compose.widgets.SingleLineText
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import theorycrafter.TheorycrafterContext
import theorycrafter.USER_DESKTOP_DIR
import theorycrafter.USER_HOME_DIR
import theorycrafter.formats.FitsImportResult
import theorycrafter.formats.UnloadedFit
import theorycrafter.storage.StoredFit
import theorycrafter.storage.produceDuplicateFitDetector
import theorycrafter.ui.widgets.CheckmarkedRow
import theorycrafter.ui.widgets.InnerDialog
import theorycrafter.ui.widgets.SearchField
import theorycrafter.utils.NativeWriteFilePickerDialog
import theorycrafter.utils.StringSearch
import theorycrafter.utils.extensionFilenameFilter
import theorycrafter.utils.mutableStateSetOf
import java.io.File
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter


/**
 * The dialog to import fits.
 */
@Composable
fun ImportFitsDialog(
    readFits: suspend () -> FitsImportResult,
    fitsSourceName: String,
    warnAboutMutatedItems: Boolean,
    defaultTags: List<String> = emptyList(),
    onImport: (importResult: FitsImportResult, fitsToImport: Collection<StoredFit>) -> Unit,
    onDismiss: () -> Unit
) {
    val selectedFits: MutableSet<StoredFit> = remember { mutableStateSetOf() }

    val loadResult by produceState<FitsImportResult?>(initialValue = null) {
        value = withContext(Dispatchers.IO) {
            readFits()
        }
    }

    val duplicateFitDetector by produceDuplicateFitDetector(TheorycrafterContext.fits)

    val sortedFits = remember(loadResult) {
        loadResult?.fits?.sortedBy { it.name } ?: emptyList()
    }
    val (existingFits, newFits) = remember(sortedFits, duplicateFitDetector) {
        duplicateFitDetector?.let { detector ->
            sortedFits.partition { fit ->
                detector.duplicateOfExists(fit)
            }
        } ?: Pair(emptyList(), emptyList())
    }

    LaunchedEffect(newFits) {
        selectedFits.clear()
        selectedFits.addAll(newFits)
    }

    var tags: List<String> by remember { mutableStateOf(defaultTags) }

    InnerDialog(
        title = when {
            loadResult == null -> "Loading fits from $fitsSourceName"
            duplicateFitDetector == null -> "Processing fits from $fitsSourceName"
            else -> "Loaded ${loadResult!!.fits.size} of ${loadResult!!.totalFitsCount} fits from $fitsSourceName"
        },
        confirmText = "Import Selected Fits",
        dismissText = "Close",
        onConfirm = {
            loadResult?.let {
                val fitsToImport = newFits
                    .intersect(selectedFits)
                    .map { fit ->
                        fit.copy(
                            fitTimes = StoredFit.FitTimes.Unchanged,
                            tags = fit.tags + tags
                        )
                    }
                onImport(it, fitsToImport)
            }
        },
        confirmEnabled = (loadResult != null) && selectedFits.isNotEmpty(),
        onDismiss = onDismiss,
        modifier = Modifier.width(500.dp)
    ) {
        Column(modifier = Modifier.height(700.dp)) {
            val loadedResult = loadResult
            if (loadedResult == null) {
                Text("Loading fits")
                return@Column
            }
            val duplicateDetector = duplicateFitDetector
            if (duplicateDetector == null) {
                Text("Processing existing fits")
                return@Column
            }

            if (newFits.isNotEmpty()) {
                Text(
                    text = "Fits to import:",
                    modifier = Modifier
                        .padding(bottom = TheorycrafterTheme.spacing.small),
                )

                FitSelectionList(
                    allFits = newFits,
                    selectedFits = selectedFits,
                    allowSearchByTag = false,
                    modifier = Modifier
                        .fillMaxWidth()
                        .weight(3f)
                )
            }

            if (warnAboutMutatedItems) {
                Text(
                    text = "*Abyssal/mutated items may be replaced by their Tech 1 counterparts.",
                    style = TheorycrafterTheme.textStyles.footnote,
                    modifier = Modifier.padding(top = TheorycrafterTheme.spacing.small)
                )
            }

            FitTagEditor(
                tags = tags,
                addTagLabel = "Tag imported fits (optional)",
                onTagsChanged = { tags = it },
                modifier = Modifier
                    .padding(top = TheorycrafterTheme.spacing.medium)
                    .fillMaxWidth()
                    .height(140.dp)
            )

            if (existingFits.isNotEmpty()) {
                Text(
                    text = "Fits already in your database:",
                    modifier = Modifier
                        .padding(
                            top = TheorycrafterTheme.spacing.xlarge,
                            bottom = TheorycrafterTheme.spacing.xxsmall
                        ),
                )
                LazyColumnExt(
                    showScrollShadows = true,
                    modifier = Modifier
                        .fillMaxWidth()
                        .weight(1f)
                        .padding(start = TheorycrafterTheme.spacing.small)
                ) {
                    items(existingFits) {
                        ExistingFitItem(it)
                    }
                }
            }

            if (loadedResult.unsuccessful.isNotEmpty()) {
                Text(
                    text = "Could not load these fits:",
                    modifier = Modifier
                        .padding(
                            top = TheorycrafterTheme.spacing.xlarge,
                            bottom = TheorycrafterTheme.spacing.xxsmall
                        ),
                )
                LazyColumnExt(
                    showScrollShadows = true,
                    modifier = Modifier
                        .fillMaxWidth()
                        .weight(1f)
                        .padding(start = TheorycrafterTheme.spacing.small)
                ) {
                    items(loadedResult.unsuccessful) {
                        UnloadedFitItem(it)
                    }
                }
            }
        }
    }
}


/**
 * An item in the list of fits that could not be loaded.
 */
@Composable
private fun UnloadedFitItem(unloadedFit: UnloadedFit) {
    SingleLineText(
        text = "${unloadedFit.name} (${unloadedFit.shipTypeName})",
        modifier = Modifier
            .padding(vertical = TheorycrafterTheme.spacing.xxsmall)
    )
}


/**
 * An item in the list of fits that is a duplicate of an existing fit.
 */
@Composable
private fun ExistingFitItem(storedFit: StoredFit) {
    val shipType = remember(storedFit) {
        TheorycrafterContext.eveData.shipTypeOrNull(storedFit.shipTypeId)
    }
    SingleLineText(
        text = "${storedFit.name} (${shipType?.name ?: "Unknown"})",
        modifier = Modifier
            .padding(vertical = TheorycrafterTheme.spacing.xxsmall)
    )
}


/**
 * The dialog to export fits.
 */
@Composable
fun ExportFitsDialog(
    onExport: (fitsToExport: Collection<StoredFit>, file: File) -> Unit,
    onDismiss: () -> Unit
) {
    var exportFile by remember {
        val dateString = DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDateTime.now())
        val directory = if (USER_DESKTOP_DIR.isDirectory) USER_DESKTOP_DIR else USER_HOME_DIR
        val defaultFile = File(directory, "fits_$dateString.xml")
        mutableStateOf(defaultFile)
    }

    val selectedFits: MutableSet<StoredFit> = remember { mutableStateSetOf() }
    var showFileSelectionDialog by remember { mutableStateOf(false) }

    val allFits = remember {
        TheorycrafterContext.fits.handles.map { TheorycrafterContext.fits.storedFitOf(it) }
            .sortedBy { it.name }
    }

    LaunchedEffect(allFits) {
        selectedFits.clear()
        selectedFits.addAll(allFits)
    }

    if (showFileSelectionDialog) {
        NativeWriteFilePickerDialog(
            title = "Specify XML file…",
            suggestedFile = exportFile,
            filenameFilter = extensionFilenameFilter(ignoreCase = true, "xml"),
            onCompletedSelecting = { file ->
                showFileSelectionDialog = false
                if (file != null) {
                    exportFile = file
                }
            }
        )
    }

    InnerDialog(
        confirmText = "Export Selected Fits",
        dismissText = "Cancel",
        onConfirm = {
            onExport(selectedFits, exportFile)
        },
        confirmEnabled = selectedFits.isNotEmpty(),
        onDismiss = onDismiss,
        modifier = Modifier.width(500.dp)
    ) {
        Column(modifier = Modifier.height(480.dp)) {
            // File selection UI
            Column(Modifier.fillMaxWidth()) {
                Text(
                    text = "Export to file:",
                    modifier = Modifier.padding(end = TheorycrafterTheme.spacing.small)
                )
                VSpacer(TheorycrafterTheme.spacing.small)
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(bottom = TheorycrafterTheme.spacing.medium)
                ) {
                    var textFieldValue by remember(exportFile) {
                        mutableStateOf(
                            TextFieldValue(exportFile.absolutePath, TextRange(exportFile.absolutePath.length))
                        )
                    }
                    TheorycrafterTheme.TextField(
                        value = textFieldValue,
                        onValueChange = { textFieldValue = it },
                        readOnly = true,
                        singleLine = true,
                        modifier = Modifier
                            .weight(1f)
                            .alignByBaseline()
                    )
                    FlatButtonWithText(
                        text = "Change…",
                        modifier = Modifier.alignByBaseline(),
                        onClick = {
                            showFileSelectionDialog = true
                        }
                    )
                }
            }

            VSpacer(TheorycrafterTheme.spacing.large)

            // Fit selection UI
            Text(
                text = "Fits to export:",
                modifier = Modifier
                    .padding(bottom = TheorycrafterTheme.spacing.small),
            )

            FitSelectionList(
                allFits = allFits,
                selectedFits = selectedFits,
                allowSearchByTag = true,
                modifier = Modifier
                    .fillMaxWidth()
                    .weight(1f)
            )
        }
    }
}


/**
 * A reusable component for selecting fits from a list.
 */
@Composable
fun FitSelectionList(
    allFits: List<StoredFit>,
    selectedFits: MutableSet<StoredFit>,
    allowSearchByTag: Boolean,
    modifier: Modifier = Modifier
) {
    val fitSearch = remember(allFits) { StringSearch<StoredFit>(minCharacters = 1) }
    LaunchedEffect(allFits) {
        val eveData = TheorycrafterContext.eveData
        for (fit in allFits) {
            fitSearch.addItem(fit, fit.name)
            fitSearch.addItem(fit, eveData.shipType(fit.shipTypeId).name)
            if (allowSearchByTag) {
                for (tag in fit.tags)
                    fitSearch.addItem(fit, "#$tag")
            }
        }
    }

    var query by remember { mutableStateOf("") }
    val filteredFits by produceState(allFits, allFits, query) {
        value = if (query.isEmpty()) allFits else {
            withContext(Dispatchers.Default) {
                fitSearch.query(query) ?: allFits
            }
        }
    }

    Column(modifier = modifier) {
        SearchField(
            modifier = Modifier
                .fillMaxWidth()
                .padding(bottom = TheorycrafterTheme.spacing.small),
            placeholderText = if (allowSearchByTag)
                "Filter by fit or ship name, or #tag"
            else
                "Filter by fit or ship name",
            searchText = query,
            onSearched = { query = it }
        )

        if (filteredFits.isEmpty()) {
            SingleLineText(
                text = "No matching fits",
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(TheorycrafterTheme.spacing.medium)
                    .weight(1f),
                textAlign = TextAlign.Center,
            )
        } else {
            LazyColumnExt(
                showScrollShadows = true,
                modifier = Modifier
                    .fillMaxWidth()
                    .weight(1f)
            ) {
                items(
                    items = filteredFits,
                    key = { it }
                ) { fit ->
                    val selected by remember(fit) {
                        derivedStateOf(structuralEqualityPolicy()) { fit in selectedFits }
                    }
                    FitListItem(
                        fit = fit,
                        selected = selected,
                        onSelectedChanged = {
                            if (it)
                                selectedFits.add(fit)
                            else
                                selectedFits.remove(fit)
                        },
                        modifier = Modifier.fillMaxWidth()
                    )
                }
            }
        }

        Row(
            horizontalArrangement = Arrangement.spacedBy(TheorycrafterTheme.spacing.medium),
            verticalAlignment = Alignment.Top,
            modifier = Modifier
                .padding(top = TheorycrafterTheme.spacing.small)
        ) {
            SingleLineText("${selectedFits.size} fits selected")
            Spacer(Modifier.weight(1f))
            FlatButtonWithText(
                text = "Select All",
                enabled = remember(filteredFits) {
                    derivedStateOf {
                        (selectedFits.size < allFits.size) && filteredFits.any { it !in selectedFits }
                    }
                }.value,
                onClick = { selectedFits.addAll(filteredFits) },
                modifier = Modifier.alignByBaseline(),
            )
            FlatButtonWithText(
                text = "Deselect All",
                enabled = remember(filteredFits) {
                    derivedStateOf {
                        selectedFits.isNotEmpty() && filteredFits.any { it in selectedFits }
                    }
                }.value,
                onClick = { selectedFits.removeAll(filteredFits.toSet()) },
                modifier = Modifier.alignByBaseline(),
            )
        }
    }
}


/**
 * An item in the list of fits.
 */
@Composable
private fun FitListItem(
    fit: StoredFit,
    selected: Boolean,
    onSelectedChanged: (Boolean) -> Unit,
    modifier: Modifier,
) {
    CheckmarkedRow(
        checked = selected,
        onCheckedChange = onSelectedChanged,
        modifier = modifier
    ) {
        SingleLineText(
            text = fit.name,
            modifier = Modifier
                .padding(vertical = TheorycrafterTheme.spacing.xxsmall)
                .weight(1f)
        )

        val shipType = TheorycrafterContext.eveData.shipType(fit.shipTypeId)
        SingleLineText(
            text = shipType.shortName(),
            style = TheorycrafterTheme.textStyles.caption,
            modifier = Modifier
                .padding(vertical = TheorycrafterTheme.spacing.xxsmall)
                .padding(
                    start = TheorycrafterTheme.spacing.small,
                    end = 12.dp  // Get away from the scrollbar
                )
        )
    }
}
