/**
 * Displays a gallery of the widgets.
 */

package compose.widgets

import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.singleWindowApplication
import compose.input.onKeyShortcut
import compose.input.onMousePress
import compose.utils.*
import kotlin.math.absoluteValue
import kotlin.math.roundToInt
import kotlin.math.sin
import kotlin.math.tan


/**
 * The UI for showcasing a single widget.
 */
private data class Page(
    val name: String,
    val content: @Composable () -> Unit
)


/**
 * The UI for showcasing a [DropdownField].
 */
@Composable
private fun DropdownBoxPage(){
    val items = (1..10).map { "Item $it" }
    DropdownField(
        items = items,
        modifier = Modifier.width(200.dp)
    )
}


/**
 * The UI for showcasing an [EditableText].
 */
@Composable
private fun EditableTextPage(){
    EditableText(
        text = "Hello, World",
    )
}


/**
 * The data we show about a billionaire.
 */
private data class Billionaire(
    val position: Int,
    val name: String,
    val netWorthBillions: Int,
    val age: Int,
    val nationality: String,
    val wealthSource: String
) {

    val isPlaceholder: Boolean
        get() = position == 0

    companion object {

        val Placeholder = Billionaire(
            position = 0,
            name = "",
            netWorthBillions = 0,
            age = 0,
            nationality = "",
            wealthSource = ""
        )

    }

}


/**
 * The UI for showcasing a [SimpleGrid].
 */
@Composable
private fun SimpleGridPage(){

    val data = remember {
        mutableStateListOf(
            Billionaire(1, "Jeff Bezos", 177, 57, "United States",	"Amazon"),
            Billionaire(2, "Elon Musk", 151, 49, "United States", "Tesla, SpaceX"),
            Billionaire(3, "Bernard Arnault", 150, 72, "France", "LVMH"),
            Billionaire(4, "Bill Gates", 124, 65, "United States", "Microsoft"),
            Billionaire(5, "Mark Zuckerberg", 97, 36, "United States","Meta Platforms"),
            Billionaire(6, "Warren Buffett", 96, 90, "United States", "Berkshire Hathaway"),
            Billionaire.Placeholder,
            Billionaire.Placeholder,
            Billionaire(7, "Larry Ellison", 93, 76, "United States", "Oracle Corporation"),
            Billionaire(8, "Larry Page", 91, 48, "United States","Alphabet Inc."),
            Billionaire(9, "Sergey Brin", 89, 47, "United States", "Alphabet Inc."),
            Billionaire(10, "Mukesh Ambani", 84, 63, "India", "Reliance Industries")
        )
    }

    Column(
        modifier = Modifier.fillMaxWidth()
    ) {
        val rowSelectionModel = rememberSingleItemSelectionModel(items = data)
        val columnWidths = listOf(40.dp, 140.dp, 60.dp, 100.dp, 120.dp, Dp.Unspecified)
        val columnAlignment = listOf(
            Alignment.CenterEnd,
            Alignment.CenterStart,
            Alignment.CenterEnd,
            Alignment.CenterEnd,
            Alignment.CenterStart,
            Alignment.CenterStart,
        )
        SimpleGridHeaderRow(
            columnWidths = columnWidths,
            modifier = Modifier
                .height(IntrinsicSize.Min)
                .background(Color.White),
            defaultCellModifier = Modifier.padding(6.dp),
            defaultCellContentAlignment = columnAlignment::get
        ) {
            CompositionLocalProvider(LocalTextStyle provides TextStyle(fontWeight = FontWeight.Bold)) {
                TextCell(0, "No.")
                TextCell(1, "Name")
                TextCell(2, "Age")
                TextCell(3, "Net Worth")
                TextCell(4, "Nationality")
                TextCell(5, "Source of wealth")
            }
        }
        ContentWithScrollbar {
            SimpleGrid(
                columnWidths = columnWidths,
                rowSelectionModel = rowSelectionModel,
                defaultCellModifier = Modifier.padding(6.dp),
                defaultCellContentAlignment = columnAlignment::get,
                modifier = Modifier
                    .focusWhenClicked()
                    .moveSelectionWithKeys(rowSelectionModel)
                    .verticalScroll(scrollState)
            ) {
                data.forEachIndexed { index, billionaire ->
                    val focusRequester = remember { FocusRequester() }
                    row(
                        rowIndex = index,
                        modifier = Modifier
                            .height(IntrinsicSize.Min)
                            .focusRequester(focusRequester)
                            .focusable()
                            .requestFocusWhenSelected(focusRequester)
                            .onKeyShortcut(Key.Delete) {
                                data.remove(billionaire)
                            }
                            .then(
                                if (!billionaire.isPlaceholder) {
                                    Modifier.dragRowToReorder(
                                        draggableContent = {
                                            draggedRow(
                                                rowIndex = index,
                                                modifier = Modifier.height(IntrinsicSize.Min).background(Color.White.copy(alpha = 0.7f))
                                            ) {
                                                BillionaireRow(billionaire)
                                            }
                                        },
                                        canMoveToRow = { targetIndex ->
                                            when {
                                                // Allow dragging one past the last item
                                                targetIndex == data.size -> true

                                                // Allow dragging into a non-placeholder
                                                !data[targetIndex].isPlaceholder -> true

                                                // Allow dragging between a non-placeholder and a placeholder
                                                (targetIndex > 0) && !data[targetIndex-1].isPlaceholder -> true

                                                else -> false
                                            }
                                        },
                                        onDrop = { draggedRow, targetRow ->
                                            val targetIndex = if (targetRow > draggedRow) targetRow-1 else targetRow
                                            val item = data.removeAt(draggedRow)
                                            data.add(targetIndex, item)
                                            if (rowSelectionModel.isIndexSelected(draggedRow))
                                                rowSelectionModel.selectIndex(targetIndex)
                                        }
                                    )
                                }
                                else
                                    Modifier
                            )
                            .padding(vertical = 10.dp)
                            .height(30.dp)
                    ) {
                        if (!billionaire.isPlaceholder) {
                            BillionaireRow(billionaire)
                        }
                        else {
                            emptyCell(0)
                        }
                    }
                }
            }
        }
    }
}


@Composable
private fun GridScope.GridRowScope.BillionaireRow(billionaire: Billionaire) {
    @Composable
    fun item(text: String, textAlign: TextAlign){
        Text(text, modifier = Modifier.fillMaxWidth(), textAlign = textAlign)
    }

    cell(0) { item(billionaire.position.toString(), textAlign = TextAlign.End) }
    cell(1) { item(billionaire.name, textAlign = TextAlign.Start) }
    cell(2) { item(billionaire.age.toString(), textAlign = TextAlign.End) }
    cell(3) { item("$${billionaire.netWorthBillions}B", textAlign = TextAlign.End) }
    cell(4) { item(billionaire.nationality, textAlign = TextAlign.Start) }
    cell(5) { item(billionaire.wealthSource, textAlign = TextAlign.Start) }
}


@Composable
private fun TreeListPage() {
    ContentWithScrollbar {
        val treeListState = remember {
            TreeListState<Int, Int>(
                isAncestorOf = {
                    val thisNumber = this.value
                    val itNumber = when (it) {
                        is TreeListNode.Inner -> it.value
                        is TreeListNode.Leaf -> it.value
                    }
                    itNumber.toString().startsWith(thisNumber.toString())
                }
            )
        }
        TreeList(
            modifier = Modifier
                .verticalScroll(scrollState)
                .focusWhenClicked()
                .moveSelectionWithKeys(
                    state = treeListState,
                    itemsInPage = { 9 }
                )
                .expandCollapseWithKeys(treeListState),
            state = treeListState,
            topLevelNodes = (1..9).map { TreeListNode.Inner(it) },
            childrenOf = { parent ->
                (1..9).map {
                    if (parent > 1000) {
                        TreeListNode.Leaf((parent*10) + it)
                    }
                    else {
                        TreeListNode.Inner((parent*10) + it)
                    }
                }
            },
            innerContent = { node, level, isExpanded ->
                TreeListitem(treeListState, node, node.value, level, isExpanded)
            },
            leafContent = { node, level ->
                TreeListitem(treeListState, node, node.value, level)
           },
        )
    }
}


@Composable
private fun TreeListitem(
    state: TreeListState<Int, Int>,
    node: TreeListNode<Int, Int>,
    value: Int,
    level: Int,
    isExpanded: Boolean = false
) {
    VerticallyCenteredRow(
        horizontalArrangement = Arrangement.spacedBy(6.dp),
        modifier = Modifier
            .onMousePress {
                state.selectedNode = node
            }
            .fillMaxWidth()
            .bringIntoViewWhenSelectedWithMargins()
            .padding(8.dp)
            .padding(start = 20.dp * level + if (node is TreeListNode.Leaf) 20.dp else 0.dp)
    ) {
        if (node is TreeListNode.Inner) {
            Icon(
                imageVector = if (isExpanded)
                    Icons.Default.KeyboardArrowDown
                else
                    Icons.AutoMirrored.Default.KeyboardArrowRight,
                contentDescription = if (isExpanded) "Expanded" else "Collapsed",
                modifier = Modifier
                    .onMousePress {
                        state.toggleExpanded(node)
                    }
            )
        }

        Text(
            text = value.toString()
        )
    }
}


/**
 * The UI for showcasing an [AutoSuggestTextField].
 */
@Composable
private fun AutoSuggestTextFieldPage(){
    AutoSuggestTextField(
        autoSuggest = {
            if (it.isBlank())
                null
            else
                WORDS.filter { word -> word.startsWith(it) }
        },
        itemToString = { it },
        modifier = Modifier.width(200.dp),
        dropdownMenuDecorator = { _, content ->  content(Modifier.heightIn(max = 280.dp)) }
    ) { value, onValueChange, modifier ->
        TextField(
            value = value,
            onValueChange = onValueChange,
            modifier = modifier,
            singleLine = true,
            keyboardOptions = KeyboardOptions(
                autoCorrectEnabled = false
            )
        )
    }
}


/**
 * The UI for showcasing easy tooltips.
 */
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun EasyTooltipsPage() {
    TooltipHost(
        properties = TooltipHostProperties(
            decorator = { content -> TooltipDecorator(content = content) }
        )
    ) {
        Column(Modifier.fillMaxSize().padding(top = 60.dp)) {
            Text(
                text = "TooltipArea",
                style = TextStyle(fontSize = 16.sp, fontWeight = FontWeight.Bold),
                modifier = Modifier.align(Alignment.CenterHorizontally).padding(bottom = 24.dp)
            )
            Row(Modifier.fillMaxWidth()) {
                HSpacer(100.dp)
                TooltipArea(
                    tooltip = { TextTooltip("Red Box") },
                    tooltipPlacement = TooltipPlacement.ComponentRect(),
                    modifier = Modifier
                        .size(100.dp)
                ) {
                    Box(Modifier.fillMaxSize().background(Color.Red))
                }

                HSpacer(20.dp)
                TooltipArea(
                    tooltip = { TextTooltip("Green Box") },
                    tooltipPlacement = TooltipPlacement.ComponentRect(),
                    modifier = Modifier
                        .size(100.dp)
                ) {
                    Box(Modifier.fillMaxSize().background(Color.Green))
                }

                TooltipArea(
                    tooltip = { TextTooltip("Blue Box") },
                    Modifier
                        .size(100.dp)
                ) {
                    Box(Modifier.fillMaxSize().background(Color.Blue))
                }

                TooltipArea(
                    tooltip = { TextTooltip("Yellow Box") },
                    Modifier
                        .size(100.dp)
                        .offset(x = -(25).dp)
                ) {
                    Box(Modifier.fillMaxSize().background(Color.Yellow.copy(alpha = 0.8f)))
                }
            }

            VSpacer(100.dp)

            Text(
                text = "Easy Tooltip",
                style = TextStyle(fontSize = 16.sp, fontWeight = FontWeight.Bold),
                modifier = Modifier.align(Alignment.CenterHorizontally).padding(bottom = 24.dp)
            )
            Row(Modifier.fillMaxWidth()) {
                HSpacer(100.dp)
                Box(Modifier
                    .size(100.dp)
                    .background(Color.Red)
                    .tooltip(
                        text = "Red Box",
                        placement = EasyTooltipPlacement.ElementBottomCenter
                    )
                )

                HSpacer(20.dp)
                Box(Modifier
                    .size(100.dp)
                    .background(Color.Green)
                    .tooltip(
                        text = "Green Box",
                        placement = EasyTooltipPlacement.ElementBottomCenter
                    )
                )

                Box(Modifier
                    .size(100.dp)
                    .background(Color.Blue)
                    .tooltip("Blue Box")
                )

                Box(Modifier
                    .size(100.dp)
                    .offset(x = -(25).dp)
                    .background(Color.Yellow.copy(alpha = 0.8f))
                    .tooltip("Yellow Box")
                )
            }
        }
    }
}


@Composable
private fun TooltipDecorator(content: @Composable () -> Unit) {
    Box(
        modifier = Modifier
            .padding(6.dp)  // For the shadow
            .shadow(elevation = 6.dp, RectangleShape, clip = false)
            .border(Dp.Hairline, Color.Black)
            .background(color = Color.White)
            .clip(RectangleShape)
            .padding(horizontal = 8.dp)
            .height(24.dp),
        contentAlignment = Alignment.Center
    ) {
        CompositionLocalProvider(LocalTextStyle provides TextStyle(fontSize = 12.sp)) {
            content()
        }
    }
}

@Composable
private fun TextTooltip(text: String) {
    TooltipDecorator {
        Text(text)
    }
}


@Composable
private fun SegmentedButtonsPage() {
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(24.dp),
        modifier = Modifier.padding(24.dp)
    ) {
        var selectedIndex1: Int by remember { mutableIntStateOf(1) }
        SegmentedButtons(
            selectedButton = selectedIndex1,
            onButtonSelected = { selectedIndex1 = it },
            shape = RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp),
        ) {
            button { Text("Item 1") }
            button { Text("Item 2") }
            button { Text("Item 3") }
        }

        var selectedIndex2: Int by remember { mutableIntStateOf(1) }
        SegmentedButtons(
            selectedButton = selectedIndex2,
            onButtonSelected = { selectedIndex2 = it },
            shape = RoundedCornerShape(8.dp),
        ) {
            button { Text("Item 1") }
            button { Text("Item 2") }
            button { Text("Item 3") }
        }

        var selectedIndex3: Int by remember { mutableIntStateOf(1) }
        SegmentedButtons(
            selectedButton = selectedIndex3,
            onButtonSelected = { selectedIndex3 = it },
            shape = RoundedCornerShape(bottomStart = 8.dp, bottomEnd = 8.dp),
        ) {
            button { Text("Item 1") }
            button { Text("Item 2") }
            button { Text("Item 3") }
        }
    }
}


@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun TabbedPanePage() {
    CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) {
        var selectedPage: Int by remember { mutableIntStateOf(0) }
        TabbedBox(
            modifier = Modifier.padding(24.dp),
            boxModifier = Modifier.size(400.dp, 300.dp),
            selectedPage = selectedPage,
            selectedBackground = Color.LightGray,
            onPageSelected = { selectedPage = it },
            buttonsShape = RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp),
        ) {
            page(
                tabContent = { SingleLineText("Page 1") },
                pageContent = { Text("Page 1", modifier = Modifier.align(Alignment.Center)) }
            )
            page(
                tabContent = { SingleLineText("Page 2") },
                pageContent = { Text("Page 2", modifier = Modifier.align(Alignment.Center)) }
            )
            page(
                tabContent = { SingleLineText("Page 3") },
                pageContent = { Text("Page 3", modifier = Modifier.align(Alignment.Center)) }
            )
        }
    }
}


@Composable
private fun LineGraphPage() {
    FunctionLineGraph(
        modifier = Modifier.fillMaxSize(),
        xRange = 0.0..20.0,
        yRange = -5.0 .. 5.0,
        properties = DefaultGraphProperties.copy(
            xLabelFormatter = { it.roundToInt().toString() },
            yLabelFormatter = { it.roundToInt().toString() },
            hoverLineProperties = DefaultGraphProperties.hoverLineProperties.copy(
                yFormatter = { _, _, y -> "%.3f".format(y) },
                xFormatter = { "%.3f".format(it) },
            )
        ),
        lines = listOf(
            GraphLine(
                name = "Sine",
                function = ::sin,
                lineStyleAtPoint = {
                    val style1 = LineStyle(Color.Blue, Stroke(1.dp.toPx()))
                    val style2 = LineStyle(
                        color = Color.Blue,
                        drawStyle = Stroke(1.dp.toPx(), pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 5f)))
                    )
                    return@GraphLine { x, _ -> if (x < 10) style1 else style2 }
                }
            ),
            GraphLine(
                name = "Tangent",
                function = ::tan,
                lineStyleAtPoint = {
                    val style = LineStyle(Color.Red, Stroke(1.dp.toPx()))
                    return@GraphLine { x, stepX ->
                        // Don't connect the tangent's +INF and -INF with a line
                        if ((x.mod(Math.PI) - Math.PI/2).absoluteValue <= stepX)
                            null
                        else
                            style
                    }
                }
            )
        ),
    )
}


@Composable
private fun WidgetsGallery() {
    Row(
        modifier = Modifier.fillMaxSize()
    ) {
        val pages = listOf(
            Page("Dropdown box") { DropdownBoxPage() },
            Page("Editable Text") { EditableTextPage() },
            Page("Grid") { SimpleGridPage() },
            Page("TreeList") { TreeListPage() },
            Page("AutoSuggestTextField") { AutoSuggestTextFieldPage() },
            Page("EasyTooltips") { EasyTooltipsPage() },
            Page("SegmentedButtons") { SegmentedButtonsPage() },
            Page("TabbedBox") { TabbedPanePage() },
            Page("Function Graph") { LineGraphPage() }
        )

        val selection = rememberSingleItemSelectionModel(
            items = pages,
            initialSelectedIndex = pages.lastIndex
        )

        Surface(
            elevation = 2.dp,
            modifier = Modifier.width(200.dp).fillMaxHeight()
        ) {
            val listState = rememberLazyListState()
            LazyColumnExt(
                state = listState,
                modifier = Modifier
                    .fillMaxSize()
                    .focusWhenClicked()
                    .moveSelectionWithKeys(
                        selectionModel = selection,
                        itemsInPage = listState::itemsInPage
                    ),
                selection = selection,
            ){
                items(pages) { page ->
                    Text(
                        text = page.name,
                        modifier = Modifier.fillMaxWidth().padding(12.dp)
                    )
                }
            }
        }

        Box(
            modifier = Modifier.fillMaxHeight().weight(1f).padding(12.dp)
        ){
            Box(
                modifier = Modifier.align(Alignment.TopCenter)
            ){
                val page = pages[selection.selectedIndex!!]
                page.content.invoke()
            }
        }
    }
}


private fun main() = singleWindowApplication(
    title = "Compose Widgets Gallery",
    state = WindowState(
        width = 900.dp,
        height = 600.dp,
    )
) {
    WidgetsGallery()
}
