package theorycrafter.ui

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.rememberWindowState
import compose.input.onMousePress
import compose.widgets.*
import eve.data.Attribute
import eve.data.EveItemType
import eve.data.SkillRequirement
import eve.data.rawAttributeValueToStringWithUnits
import kotlinx.coroutines.runBlocking
import theorycrafter.LocalTheorycrafterWindowManager
import theorycrafter.TheorycrafterContext
import theorycrafter.TheorycrafterWindow
import theorycrafter.TheorycrafterWindowInfo
import theorycrafter.fitting.EveItem
import theorycrafter.ui.widgets.SearchField
import theorycrafter.utils.StringSearch
import theorycrafter.utils.displayString
import theorycrafter.utils.simpleStyleTagsToAnnotatedString
import theorycrafter.utils.thenIf


/**
 * Displays the EVE item info windows.
 */
@Composable
fun EveItemInfoWindows() {
    val windowManager = LocalTheorycrafterWindowManager.current
    for (state in windowManager.itemInfoWindowStates) {
        key(state) {
            val item = state.item
            TheorycrafterWindow(
                title = item.name,
                state = rememberWindowState(size = EveItemInfoWindowSize),
                onCloseRequest = {
                    windowManager.closeItemInfoWindow(state)
                }
            ) {
                EveItemInfo(
                    item = item,
                    drawContentBorder = false,
                    modifier = Modifier
                        .fillMaxSize()
                        .background(TheorycrafterTheme.colors.base().background)
                        .padding(top = TheorycrafterTheme.spacing.verticalEdgeMargin)
                )
                LaunchedEffect(state, window) {
                    state.window = window
                }
            }
        }
    }
}


/**
 * The initial size of the EVE item info window.
 */
private val EveItemInfoWindowSize = DpSize(640.dp, 480.dp)


/**
 * A window state type for EVE item windows.
 */
@Stable
class EveItemInfoWindowState(val item: EveItem<*>): TheorycrafterWindowInfo()


/**
 * Displays information about an [EveItem].
 */
@Composable
fun EveItemInfo(
    item: EveItem<*>?,
    selectedBackground: Color = TheorycrafterTheme.colors.tabbedBoxSelectedBackground(),
    drawContentBorder: Boolean = true,
    modifier: Modifier
) {
    EveItemInfo(
        itemType = item?.type,
        item = item,
        selectedBackground = selectedBackground,
        drawContentBorder = drawContentBorder,
        modifier = modifier
    )
}


/**
 * Displays information about an [EveItemType].
 */
@Composable
fun EveItemTypeInfo(
    itemType: EveItemType?,
    selectedBackground: Color = TheorycrafterTheme.colors.tabbedBoxSelectedBackground(),
    drawContentBorder: Boolean = true,
    modifier: Modifier
) {
    EveItemInfo(
        itemType = itemType,
        item = null,
        selectedBackground = selectedBackground,
        drawContentBorder = drawContentBorder,
        modifier = modifier
    )
}


/**
 * Displays information about an [EveItemType] and optionally the [EveItem] it is the type of.
 */
@Composable
private fun EveItemInfo(
    itemType: EveItemType?,
    item: EveItem<*>?,
    selectedBackground: Color = TheorycrafterTheme.colors.tabbedBoxSelectedBackground(),
    drawContentBorder: Boolean = true,
    modifier: Modifier
) {
    val pages = ItemInfoPage.Pages
    var selectedPage by remember { mutableIntStateOf(0) }

    LaunchedEffect(itemType) {
        if ((pages[selectedPage] == ItemInfoPage.Traits) && (itemType != null) && (itemType.traits == null))
            selectedPage = pages.indexOf(ItemInfoPage.Description)
    }

    TabbedBox(
        modifier = modifier,
        selectedPage = selectedPage,
        onPageSelected = { selectedPage = it },
        selectedBackground = selectedBackground,
        drawContentBorder = drawContentBorder,
        buttonsShape = RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp)
    ) {
        for (pageSpec in pages) {
            page(
                tabContent = { SingleLineText(pageSpec.title) },
                pageContent = {
                    Box(Modifier.fillMaxSize()) {
                        if (itemType == null) {
                            Text(
                                text = "No item selected",
                                modifier = Modifier
                                    .padding(TheorycrafterTheme.spacing.edgeMargins)
                                    .align(Alignment.Center)
                            )
                        }
                        else {
                            pageSpec.content(itemType, item)
                        }
                    }
                }
            )
        }
    }
}


/**
 * Specifies a page in the item info tabbed box.
 */
private class ItemInfoPage(
    val title: String,
    val content: @Composable (EveItemType, EveItem<*>?) -> Unit
) {


    companion object {


        /**
         * The page displaying item traits.
         */
        val Traits = ItemInfoPage("Traits") { itemType, _ -> ItemTypeTraits(itemType) }


        /**
         * The page displaying item description.
         */
        val Description = ItemInfoPage("Description") { itemType, _ -> ItemTypeDescription(itemType) }


        /**
         * The page displaying item attributes.
         */
        val Attributes = ItemInfoPage("Attributes") { itemType, item -> ItemAttributes(itemType, item) }


        /**
         * The page displaying skill requirements.
         */
        val SkillRequirements = ItemInfoPage("Skill Requirements") { itemType, _ -> ItemTypeSkillRequirements(itemType) }


        /**
         * The list of pages, in the order they should appear.
         */
        val Pages = listOf(Traits, Description, Attributes, SkillRequirements)


    }


}


/**
 * The UI for the page displaying the item type traits.
 */
@Composable
private fun ItemTypeTraits(itemType: EveItemType) {
    ContentWithScrollbar(Modifier.fillMaxSize()) {
        Box(
            modifier = Modifier
                .padding(TheorycrafterTheme.spacing.edgeMargins)
                .verticalScroll(scrollState)
        ) {
            val traits = itemType.traits
            if (traits != null) {
                SelectionContainer {
                    Text(traits.displayString())
                }
            }
            else {
                Text("No traits available.")
            }
        }
    }
}


/**
 * The UI for the page displaying the item type description.
 */
@Composable
private fun ItemTypeDescription(itemType: EveItemType) {
    ContentWithScrollbar(Modifier.fillMaxSize()) {
        Box(
            modifier = Modifier
                .padding(TheorycrafterTheme.spacing.edgeMargins)
                .verticalScroll(scrollState)
        ) {
            val description = itemType.description
            if (description != null) {
                SelectionContainer {
                    Text(description.simpleStyleTagsToAnnotatedString())
                }
            }
            else {
                Text("No description available")
            }
        }
    }
}


/**
 * The text we display for the given attribute.
 */
private fun Attribute<*>.displayName() = displayName ?: name


/**
 * The UI for the page displaying the item attributes.
 */
@Composable
private fun ItemAttributes(itemType: EveItemType, item: EveItem<*>?) {
    val eveData = TheorycrafterContext.eveData
    val (attributes, attributeSearch) = remember(itemType) {
        val attrs = itemType.attributeValues
            .map {
                eveData.attributes[it.attributeId]
            }
            .sortedWith(
                compareBy(
                    comparator = String.CASE_INSENSITIVE_ORDER,
                    selector = { it.displayName() }
                )
            )

        val search = StringSearch<Attribute<*>>().also {
            for (attr in attrs) {
                val displayName = attr.displayName
                if (displayName != null) {
                    it.addItem(
                        item = attr,
                        text = displayName
                    )
                }
                it.addItem(
                    item = attr,
                    text = attr.name
                )
            }
        }

        Pair(attrs, search)
    }

    Column {
        var filterText by remember { mutableStateOf("") }
        val filteredAttributes = remember(attributeSearch, filterText) {
            if (filterText.isEmpty()) {
                attributes
            } else {
                runBlocking {
                    attributeSearch.query(filterText) ?: emptyList()
                }
            }
        }

        SearchField(
            modifier = Modifier
                .fillMaxWidth()
                .padding(TheorycrafterTheme.spacing.edgeMargins),
            placeholderText = "Attribute name",
            onSearched = { filterText = it }
        )

        if (filteredAttributes.isEmpty()) {
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .weight(1f)
                    .padding(TheorycrafterTheme.spacing.horizontalEdgeMargin)
            ) {
                Text(
                    text = "No matching attributes",
                    textAlign = TextAlign.Center,
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(top = TheorycrafterTheme.spacing.xxlarge),
                )
            }
        }
        else {
            AttributeValuesTable(
                attributes = filteredAttributes,
                itemType = itemType,
                item = item,
                modifier = Modifier
                    .fillMaxWidth()
                    .weight(1f)
            )
        }
    }
}


/**
 * The width of the attribute value column.
 */
private val AttributeValueColumnWidth = 100.dp


/**
 * The table of attribute names and their values.
 */
@Composable
private fun AttributeValuesTable(
    attributes: List<Attribute<*>>,
    itemType: EveItemType,
    item: EveItem<*>?,
    modifier: Modifier
) {
    Column(modifier) {
        val columnWidths = remember(item) {
            if (item == null)
                listOf(Dp.Unspecified, AttributeValueColumnWidth)
            else
                listOf(Dp.Unspecified, AttributeValueColumnWidth, AttributeValueColumnWidth)
        }
        val cellContentAlignment = remember { { _: Int -> Alignment.CenterStart } }
        SimpleGridHeaderRow(
            columnWidths = columnWidths,
            defaultCellContentAlignment = cellContentAlignment,
            modifier = Modifier
                .fillMaxWidth()
                .background(TheorycrafterTheme.colors.smallHeaderBackground())
                .padding(
                    horizontal = TheorycrafterTheme.spacing.horizontalEdgeMargin,
                    vertical = TheorycrafterTheme.spacing.xxxsmall
                ),
        ) {
            TextCell(0, "Attribute")
            TextCell(1, "Base value")
            if (item != null)
                TextCell(2, "Current value")
        }

        ContentWithScrollbar {
            ScrollShadow(scrollState, top = true)
            SelectionContainer {
                SimpleGrid(
                    columnWidths = columnWidths,
                    defaultRowModifier = Modifier
                        .height(TheorycrafterTheme.sizes.itemAttributesRowHeight)
                        .padding(horizontal = TheorycrafterTheme.spacing.horizontalEdgeMargin),
                    defaultCellContentAlignment = cellContentAlignment,
                    modifier = Modifier
                        .fillMaxWidth()
                        .verticalScroll(scrollState)
                ) {
                    for ((index, attribute) in attributes.withIndex()) {
                        row(
                            rowIndex = index,
                            modifier = Modifier
                                .background(TheorycrafterTheme.colors.alternatingRowBackground(index))
                                .then(defaultRowModifier)
                        ) {
                            cell(0) {
                                SingleLineText(
                                    // Append a space so that when this row is selected & copied, there's a space between
                                    // the attribute name and value
                                    text = attribute.displayName() + " ",
                                    modifier = Modifier.fillMaxWidth()  // To make selecting easier
                                )
                            }
                            cell(1) {
                                val value = itemType.attributeValues.getDoubleValue(attribute)
                                SingleLineText(
                                    // Append a newline so that when several rows are selected & copied, there's a
                                    // newline between them.
                                    text = value.rawAttributeValueToStringWithUnits(attribute) + '\n',
                                    modifier = Modifier.fillMaxWidth()  // To make selecting easier
                                )
                            }
                            if (item != null) {
                                cell(1) {
                                    val value = item.propertyOrNull(attribute)?.doubleValue
                                    if (value != null) {
                                        SingleLineText(
                                            // Append a newline so that when several rows are selected & copied, there's a
                                            // newline between them.
                                            text = value.rawAttributeValueToStringWithUnits(attribute) + '\n',
                                            modifier = Modifier.fillMaxWidth()  // To make selecting easier
                                        )
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}


/**
 * The UI for the page displaying the skill requirements for an [EveItemType].
 */
@Composable
private fun ItemTypeSkillRequirements(itemType: EveItemType) {
    if (itemType.requiredSkills.isEmpty()) {
        Box(
            modifier = Modifier
                .fillMaxSize()
                .padding(TheorycrafterTheme.spacing.edgeMargins),
        ) {
            Text("No skills required.")
        }
    }

    val treeState = remember(itemType) {
        TreeListState<SkillReqNode, SkillReqNode>(
            isAncestorOf = {
                it.value.isRequiredFor(this.value)
            }
        )
    }

    val eveData = TheorycrafterContext.eveData
    fun SkillRequirement.toTreeListNode(parent: SkillReqNode?): TreeListNode<SkillReqNode, SkillReqNode> {
        val skillReqNode = SkillReqNode(this, parent)
        val isLeaf = eveData.skillType(skillId).requiredSkills.isEmpty()
        return if (isLeaf)
            TreeListNode.Leaf(skillReqNode)
        else {
            TreeListNode.Inner(skillReqNode).also { node ->
                treeState.expand(node)  // Expand it immediately
            }
        }
    }

    ContentWithScrollbar(Modifier.fillMaxSize()) {
        TreeList(
            modifier = Modifier
                .verticalScroll(scrollState)
                .padding(vertical = TheorycrafterTheme.spacing.verticalEdgeMargin),
            state = treeState,
            topLevelNodes = itemType.requiredSkills.map {it.toTreeListNode(null) },
            childrenOf = {
                eveData.skillType(it.skillRequirement.skillId).requiredSkills.map { child ->
                    child.toTreeListNode(it)
                }
            },
            innerContent = { node, level, isExpanded ->
                SkillRequirementTreeListItem(treeState, node, level = level, expanded = isExpanded)
            },
            leafContent = { node, level ->
                SkillRequirementTreeListItem(treeState, node, level = level, expanded = false)
            }
        )
    }
}


/**
 * The data we store in each [TreeListNode] of the skill requirements tree.
 */
private data class SkillReqNode(
    val skillRequirement: SkillRequirement,
    val parent: SkillReqNode?
) {


    /**
     * Returns whether `this` node expresses a requirement for [skillReqNode].
     */
    fun isRequiredFor(skillReqNode: SkillReqNode): Boolean {
        var node: SkillReqNode? = this
        while (node != null) {
            if (node == skillReqNode)
                return true

            node = node.parent
        }

        return false
    }


}


/**
 * An item in the skill requirements tree.
 */
@Composable
private fun SkillRequirementTreeListItem(
    state: TreeListState<SkillReqNode, SkillReqNode>,
    treeNode: TreeListNode<SkillReqNode, SkillReqNode>,
    level: Int,
    expanded: Boolean
) {
    val skillRequirement = treeNode.value.skillRequirement
    val skillType = TheorycrafterContext.eveData.skillType(skillRequirement.skillId)
    Row(
        modifier = Modifier
            .thenIf(treeNode is TreeListNode.Inner) {
                onMousePress {
                    state.toggleExpanded(treeNode as TreeListNode.Inner)
                }
            }
            .padding(
                start = TheorycrafterTheme.spacing.horizontalEdgeMargin + 20.dp * level,
                end = TheorycrafterTheme.spacing.horizontalEdgeMargin,
            )
            .padding(
                vertical = TheorycrafterTheme.spacing.xxsmall
            )
            .fillMaxWidth()
    ) {
        Box(Modifier.size(16.dp)) {
            if (treeNode is TreeListNode.Inner) {
                Icons.TreeListExpandCollapse(
                    expanded = expanded,
                    modifier = Modifier.fillMaxSize(),
                )
            }
        }
        Text("${skillType.name} ${skillRequirement.level}")
    }
}
