package compose.widgets

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
import compose.utils.enabled
import compose.utils.withEnabledAlpha


/**
 * A dropdown box, allowing to select from a list of items.
 */
@Composable
fun <T> DropdownField(
    items: List<T>,
    selectedItem: T = items.first(),
    onItemSelected: ((Int, T) -> Unit)? = null,
    itemToString: ((T) -> String) = Any?::toString,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    shape: Shape = MaterialTheme.shapes.small,
) {
    val selectedIndex = items.indexOf(selectedItem)
    if (selectedIndex == -1)
        throw IllegalArgumentException("selectedItem ($selectedItem) not in list: $items")
    DropdownField(
        items = items,
        selectedIndex = selectedIndex,
        onItemSelected = { index, item ->
            onItemSelected?.invoke(index, item)
        },
        itemToString = itemToString,
        modifier = modifier,
        enabled = enabled,
        shape = shape,
    )
}


/**
 * The padding of dropdown items.
 */
private val DropdownItemPadding = PaddingValues(
    horizontal = 12.dp,
    vertical = 0.dp
)


/**
 * The padding of the selected dropdown item.
 */
private val SelectedDropdownItemPadding = PaddingValues(
    horizontal = 12.dp,
    vertical = 8.dp
)


/**
 * A dropdown box, allowing to select from a list of items.
 */
@Composable
fun <T> DropdownField(
    items: List<T>,
    selectedIndex: Int,
    onItemSelected: ((Int, T) -> Unit)? = null,
    itemToString: ((T) -> String) = Any?::toString,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    shape: Shape = MaterialTheme.shapes.small,
) {
    var expanded by remember { mutableStateOf(false) }
    var size by remember { mutableStateOf(Size.Zero) }

    CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.enabled(enabled)) {
        Box(modifier) {
            Surface(
                shape = shape,
                color = if (enabled)
                    MaterialTheme.colors.primary
                else
                    MaterialTheme.colors.onSurface.withEnabledAlpha(enabled = false),
            ) {
                Text(
                    text = itemToString(items[selectedIndex]),
                    color = if (enabled) MaterialTheme.colors.onPrimary else MaterialTheme.colors.onSurface,
                    modifier = Modifier
                        .fillMaxWidth()
                        .onGloballyPositioned {
                            size = it.size.toSize()
                        }
                        .clickable(enabled = enabled) { expanded = true }
                        .padding(SelectedDropdownItemPadding),
                )
            }
            DropdownMenu(
                expanded = expanded,
                onDismissRequest = { expanded = false },
                modifier = Modifier
                    .width(with(LocalDensity.current){ size.width.toDp() }),
            ) {
                items.forEachIndexed{ index, item ->
                    DropdownMenuItem(
                        contentPadding = DropdownItemPadding,
                        onClick = {
                            expanded = false
                            onItemSelected?.invoke(index, items[index])
                        },
                    ) {
                        Text(itemToString(item))
                    }
                }
            }
        }
    }
}


/**
 * A dropdown box, allowing to select from a list of items.
 *
 * In this version, the selected item is displayed via user-provided UI.
 */
@Composable
fun <T> ExposedDropdownField(
    items: List<T>,
    selectedItem: T = items.first(),
    onItemSelected: ((Int, T) -> Unit)? = null,
    itemToString: ((T) -> String) = Any?::toString,
    selectedItemContent: @Composable (text: String, expanded: Boolean) -> Unit,
    modifier: Modifier = Modifier,
) {
    val selectedIndex = items.indexOf(selectedItem)
    if (selectedIndex == -1)
        throw IllegalArgumentException("selectedItem not in list")
    ExposedDropdownField(
        items = items,
        selectedIndex = selectedIndex,
        onItemSelected = { index, item ->
            onItemSelected?.invoke(index, item)
        },
        itemToString = itemToString,
        selectedItemContent = selectedItemContent,
        modifier = modifier,
    )
}


/**
 * A dropdown box, allowing to select from a list of items.
 *
 * In this version, the selected item is displayed via user-provided UI.
 */
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun <T> ExposedDropdownField(
    items: List<T>,
    selectedIndex: Int,
    onItemSelected: ((Int, T) -> Unit)? = null,
    itemToString: ((T) -> String) = Any?::toString,
    selectedItemContent: @Composable (text: String, expanded: Boolean) -> Unit,
    modifier: Modifier = Modifier,
) {
    var expanded by remember { mutableStateOf(false) }
    ExposedDropdownMenuBox(
        expanded = expanded,
        onExpandedChange = { expanded = it },
        modifier = modifier
    ) {
        selectedItemContent(itemToString(items[selectedIndex]), expanded)
        ExposedDropdownMenu(
            expanded = expanded,
            onDismissRequest = { expanded = false },
            modifier = Modifier.exposedDropdownSize(),
        ) {
            items.forEachIndexed{ index, item ->
                DropdownMenuItem(
                    contentPadding = DropdownItemPadding,
                    onClick = {
                        expanded = false
                        onItemSelected?.invoke(index, items[index])
                    },
                ) {
                    Text(itemToString(item))
                }
            }
        }
    }
}