PromptPaymentComposable.kt

/*
 * This file is part of GNU Taler
 * (C) 2025 Taler Systems S.A.
 *
 * GNU Taler is free software; you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 3, or (at your option) any later version.
 *
 * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */

package net.taler.wallet.payment

import android.graphics.Bitmap
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.bringIntoViewRequester
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Error
import androidx.compose.material.icons.filled.EventRepeat
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.rememberTooltipState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import net.taler.common.Amount
import net.taler.common.ContractChoice
import net.taler.common.ContractInput
import net.taler.common.ContractOutput
import net.taler.common.ContractProduct
import net.taler.common.ContractTerms
import net.taler.common.ContractTokenDetails
import net.taler.common.ContractTokenFamily
import net.taler.common.Exchange
import net.taler.common.Merchant
import net.taler.common.TalerUtils
import net.taler.lib.android.base64Bitmap
import net.taler.wallet.R
import net.taler.wallet.cleanExchange
import net.taler.wallet.compose.BottomButtonBox
import net.taler.wallet.compose.ExpandableSection
import net.taler.wallet.compose.MerchantAvatar
import net.taler.wallet.compose.TalerSurface
import net.taler.wallet.compose.cardPaddings
import net.taler.wallet.donau.DonauInfo
import net.taler.wallet.donau.DonauSelector
import net.taler.wallet.donau.DonauToggle
import net.taler.wallet.payment.GetChoicesForPaymentResponse.ChoiceSelectionDetail.InsufficientBalance
import net.taler.wallet.payment.GetChoicesForPaymentResponse.ChoiceSelectionDetail.PaymentPossible
import net.taler.wallet.payment.TokenAvailabilityHint.MerchantUnexpected
import net.taler.wallet.payment.TokenAvailabilityHint.MerchantUntrusted
import net.taler.wallet.payment.TokenAvailabilityHint.WalletTokensAvailableInsufficient
import net.taler.wallet.systemBarsPaddingBottom

// TODO: error handling
// TODO: info sheets

@Composable
fun PromptPaymentComposable(
    status: PayStatus.Choices,
    onConfirm: (choiceIndex: Int?, useDonau: Boolean) -> Unit,
    onCancel: () -> Unit,
    onClickImage: (Bitmap) -> Unit,
    onSetupDonau: (donauBaseUrl: String) -> Unit,
    checkDonauStatus: suspend (choiceIndex: Int) -> DonauStatus,
    modifier: Modifier = Modifier,
) {
    val contractTerms = status.contractTerms
    var showCancelDialog by rememberSaveable { mutableStateOf(false) }

    OrderCancelDialog(
        showCancelDialog,
        onDismiss = { showCancelDialog = false },
        onConfirm = { onCancel() },
    )

    Column(
        modifier
            .fillMaxSize()
            .imePadding(),
    ) {
        var orderExpanded by rememberSaveable {
            mutableStateOf(contractTerms is ContractTerms.V0)
        }
        var choicesExpanded by rememberSaveable { mutableStateOf(true) }
        var selectedIndex by rememberSaveable { mutableIntStateOf(status.defaultChoiceIndex ?: 0) }
        var donauStatus: DonauStatus by remember { mutableStateOf(DonauStatus.Unavailable) }

        LazyColumn(
            Modifier
                .weight(1f)
                .fillMaxWidth(),
        ) {
            item { MerchantSection(contractTerms, onClickImage) }

            // REVIEW ORDER SECTION
            item {
                ExpandableSection(
                    expanded = orderExpanded,
                    setExpanded = { orderExpanded = it },
                    header = { Text(stringResource(R.string.payment_section_review)) },
                ) {
                    OrderSection(contractTerms, onClickImage)
                }
            }

            // PAYMENT OPTIONS SECTION
            if (contractTerms is ContractTerms.V1) {
                item {
                    ExpandableSection(
                        expanded = choicesExpanded,
                        setExpanded = { choicesExpanded = it },
                        header = { Text(stringResource(R.string.payment_section_choices)) },
                    ) {
                        ChoicesSection(
                            status = status,
                            tokenFamilies = contractTerms.tokenFamilies,
                            selectedIndex = selectedIndex,
                            merchantBaseUrl = contractTerms.merchantBaseUrl,
                            onSelect = { index -> selectedIndex = index },
                            onConfirm = onConfirm,
                            donauStatus = donauStatus,
                            onSetupDonau = onSetupDonau,
                        )
                    }
                }

                item {
                    LaunchedEffect(selectedIndex) {
                        donauStatus = checkDonauStatus(selectedIndex)
                    }
                }
            }
        }

        BottomButtonBox(Modifier.fillMaxWidth(),
            heading = if (contractTerms is ContractTerms.V0) { ->
                val choice = status.choices.firstOrNull()
                    ?: error("no v0 choice")

                Text(
                    stringResource(
                        R.string.payment_amount_total,
                        choice.details.amountRaw,
                    )
                )

                if (choice.details is PaymentPossible) {
                    PaymentFeeLabel(
                        modifier = Modifier.padding(bottom = 3.dp),
                        amountRaw = choice.details.amountRaw,
                        amountEffective = choice.details.amountEffective,
                    )
                } else if (choice.details is InsufficientBalance) {
                    Text(
                        modifier = Modifier.padding(bottom = 3.dp),
                        text = stringResource(R.string.payment_balance_insufficient),
                        color = MaterialTheme.colorScheme.error,
                    )
                }
            } else null,
            leading = {
                OutlinedButton(
                    modifier = Modifier.systemBarsPaddingBottom(),
                    enabled = true,
                    onClick = { showCancelDialog = true },
                ) {
                    Text(
                        stringResource(R.string.payment_button_cancel),
                        color = MaterialTheme.colorScheme.error
                    )
                }
            },
            trailing = {
                if (contractTerms is ContractTerms.V0) {
                    val choice = status.choices.firstOrNull()
                        ?: error("no v0 choice")
                    Button(
                        modifier = Modifier.systemBarsPaddingBottom(),
                        enabled = choice.details is PaymentPossible,
                        onClick = { onConfirm(null, false) },
                    ) {
                        if (choice.details is PaymentPossible) {
                            Text(stringResource(
                                R.string.payment_button_confirm_amount,
                                choice.details.amountEffective,
                            ))
                        } else {
                            Text(stringResource(
                                R.string.payment_button_confirm_amount,
                                choice.details.amountRaw,
                            ))
                        }
                    }
                }
            },
        )
    }
}

@Composable
fun MerchantSection(
    contractTerms: ContractTerms,
    onClickImage: (Bitmap) -> Unit,
) {
    val merchant = contractTerms.merchant

    Column(
        modifier = Modifier
            .padding(16.dp)
            .fillMaxWidth(),
        horizontalAlignment = CenterHorizontally,
    ) {
        // MERCHANT LOGO
        MerchantAvatar(contractTerms.merchant,  onClickImage = onClickImage)

        // MERCHANT NAME
        Text(
            merchant.name,
            modifier = Modifier.padding(top = 16.dp),
            style = MaterialTheme.typography.titleLarge,
        )

        // MERCHANT INFO BUTTON
        // TextButton(onClick = {}) {
        //     Icon(
        //         Icons.Outlined.Info,
        //         contentDescription = null,
        //         modifier = Modifier.size(ButtonDefaults.IconSize),
        //     )
        //     Spacer(Modifier.size(ButtonDefaults.IconSpacing))
        //     Text("Merchant info")
        // }
    }
}

@Composable
fun MerchantInfoSheet() {}

@Composable
fun OrderCancelDialog(
    show: Boolean,
    onDismiss: () -> Unit,
    onConfirm: () -> Unit,
) {
    if (show) AlertDialog(
        title = { Text(stringResource(R.string.payment_cancel_dialog_title)) },
        text = { Text(stringResource(R.string.payment_cancel_dialog_message)) },
        onDismissRequest = { onDismiss() },
        confirmButton = {
            TextButton(onClick = {
                onDismiss()
            }) {
                Text(stringResource(R.string.button_back))
            }
        },
        dismissButton = {
            TextButton(onClick = {
                onConfirm()
            }) {
                Text(stringResource(R.string.payment_cancel_dialog_title))
            }
        },
    )
}

@Composable
fun OrderSection(
    contractTerms: ContractTerms,
    onClickImage: (Bitmap) -> Unit,
) {
    Column(horizontalAlignment = CenterHorizontally) {
        // ORDER SUMMARY
        Text(
            contractTerms.summary,
            modifier = Modifier
                .padding(top = 3.dp, bottom = 12.dp)
                .padding(horizontal = 16.dp)
                .fillMaxWidth(),
        )

        // PRODUCT LIST
        // TODO: LazyColumn would be better, but can't be nested
        val products = contractTerms.products
        products?.forEach { product ->
            ProductItem(product, onClickImage)
        }

        // ORDER INFO BUTTON
        // TextButton(
        //     onClick = {},
        //     modifier = Modifier.padding(bottom = 9.dp),
        // ) {
        //     Icon(
        //         Icons.Outlined.Info,
        //         contentDescription = null,
        //         modifier = Modifier.size(ButtonDefaults.IconSize),
        //     )
        //     Spacer(Modifier.size(ButtonDefaults.IconSpacing))
        //     Text("More details")
        // }
    }
}

@Composable
fun ProductItem(
    product: ContractProduct,
    onClickImage: (Bitmap) -> Unit,
) {
    val image = remember(product.image) {
        product.image?.base64Bitmap
    }

    ListItem(
        leadingContent = {
            // IMAGE
            if (image != null) {
                Image(
                    image.asImageBitmap(),
                    modifier = Modifier
                        .size(30.dp)
                        .clickable { onClickImage(image) },
                    contentDescription = null,
                )
            }
        },

        headlineContent = {
            Column(
                Modifier.fillMaxWidth(),
                horizontalAlignment = Alignment.End,
            ) {
                // NAME
                Text(
                    product.localizedDescription,
                    modifier = Modifier.fillMaxWidth(),
                    style = MaterialTheme.typography.bodyLarge,
                )

                // PRICE
                product.price?.let { price ->
                    Text(
                        if (product.quantity > 1) {
                            stringResource(
                                R.string.payment_product_price_quantity,
                                product.quantity.toString(),
                                price.toString(),
                            )
                        } else {
                            price.toString()
                        },
                        style = MaterialTheme.typography.bodyMedium.copy(
                            color = ListItemDefaults.colors().supportingTextColor,
                        ),
                    )
                }
            }
        },
    )
}

@Composable
fun ChoicesSection(
    status: PayStatus.Choices,
    tokenFamilies: Map<String, ContractTokenFamily>,
    selectedIndex: Int,
    merchantBaseUrl: String,
    onSelect: (choiceIndex: Int) -> Unit,
    onConfirm: (choiceIndex: Int, useDonau: Boolean) -> Unit,
    donauStatus: DonauStatus,
    onSetupDonau: (donauBaseUrl: String) -> Unit,
) {
    // TODO: CURRENCIES

    // CHOICES
    // TODO: LazyColumn would be better, but can't be nested
    status.choices.forEach { choice ->
        PaymentChoice(
            choice = choice,
            tokenFamilies = tokenFamilies,
            merchantBaseUrl = merchantBaseUrl,
            selected = selectedIndex == choice.choiceIndex,
            onSelect = { onSelect(choice.choiceIndex) },
            donauStatus = donauStatus,
            onSetupDonau = onSetupDonau,
            onConfirm = { useDonau -> onConfirm(choice.choiceIndex, useDonau) },
        )
    }
}

@Composable
fun PaymentChoice(
    choice: PayChoiceDetails,
    tokenFamilies: Map<String, ContractTokenFamily>,
    merchantBaseUrl: String,
    selected: Boolean,
    onSelect: () -> Unit,
    onConfirm: (useDonau: Boolean) -> Unit,
    donauStatus: DonauStatus,
    onSetupDonau: (donauBaseUrl: String) -> Unit,
) {
    val bringIntoViewRequester = remember { BringIntoViewRequester() }
    val coroutineScope = rememberCoroutineScope()

    OutlinedCard(
        modifier = Modifier
            .cardPaddings()
            .fillMaxWidth()
            .bringIntoViewRequester(bringIntoViewRequester)
            .animateContentSize { _, _ ->
                if (selected) {
                    coroutineScope.launch {
                        bringIntoViewRequester.bringIntoView()
                    }
                }
            }
            .clickable { onSelect() },
        border = if (selected) {
            BorderStroke(2.5.dp, MaterialTheme.colorScheme.primary)
        } else {
            CardDefaults.outlinedCardBorder()
        },
    ) {
        Column(Modifier) {
            Column(Modifier.padding(15.dp)) {
                // CHOICE AMOUNT
                Text(
                    text = if (choice.amountRaw.isZero()) {
                        stringResource(R.string.payment_button_confirm_tokens)
                    } else {
                        choice.amountRaw.toString()
                    },
                    style = MaterialTheme.typography.titleLarge,
                )

                if (choice.details is PaymentPossible) {
                    PaymentFeeLabel(
                        amountRaw = choice.details.amountRaw,
                        amountEffective = choice.details.amountEffective,
                    )
                }

                choice.localizedDescription?.let {
                    Text(
                        text = it,
                        modifier = Modifier.padding(top = 9.dp),
                        style = MaterialTheme.typography.bodyLarge,
                    )
                }

                // INPUTS
                if (choice.inputs.isNotEmpty()) {
                    Column {
                        Text(
                            stringResource(R.string.payment_choice_inputs),
                            modifier = Modifier.padding(top = 9.dp, bottom = 3.dp),
                            style = MaterialTheme.typography.labelLarge,
                        )

                        choice.inputs.forEach { input ->
                            PaymentInput(input, merchantBaseUrl, tokenFamilies, choice.details.tokenDetails)
                        }
                    }
                }

                // OUTPUTS
                if (choice.outputs.isNotEmpty()) {
                    Column {
                        Text(
                            stringResource(R.string.payment_choice_outputs),
                            modifier = Modifier.padding(top = 9.dp, bottom = 3.dp),
                            style = MaterialTheme.typography.labelLarge,
                        )

                        choice.outputs.forEach { output ->
                            PaymentOutput(output, tokenFamilies, merchantBaseUrl)
                        }
                    }
                }

                // DONAU TOGGLE/SELECTOR
                var useDonau by rememberSaveable { mutableStateOf(false) }
                var showSelector by remember { mutableStateOf(false) }
                if (choice.details is PaymentPossible) {
                    if (selected) DonauToggle(
                        donauStatus = donauStatus,
                        useDonau = useDonau,
                        onToggleDonau = { useDonau = it },
                    )

                    DonauSelector(
                        donauStatus = donauStatus,
                        showDialog = showSelector,
                        onSetup = { onSetupDonau(it) },
                        onDismiss = { showSelector = false },
                    )
                }

                val shouldSetDonau = choice.details is PaymentPossible
                        && useDonau
                        && donauStatus !is DonauStatus.Available

                // CONFIRM BUTTON
                if (selected) Button(
                    modifier = Modifier
                        .padding(top = 9.dp)
                        .fillMaxWidth(),
                    onClick = {
                        if (shouldSetDonau) {
                            showSelector = true
                        } else {
                            onConfirm(useDonau)
                        }
                    },
                    enabled = choice.details is PaymentPossible,
                ) {
                    val tokenDetails = choice.details.tokenDetails
                    Text(
                        if (choice.details is PaymentPossible) {
                            if (shouldSetDonau) {
                                stringResource(R.string.donau_select_button)
                            } else if (choice.details.amountEffective.isZero()) {
                                stringResource(R.string.payment_button_confirm_tokens)
                            } else {
                                stringResource(
                                    R.string.payment_button_confirm_amount,
                                    choice.details.amountEffective
                                )
                            }
                        } else if (tokenDetails != null &&
                            tokenDetails.tokensAvailable
                            < tokenDetails.tokensRequested) {
                            stringResource(
                                R.string.payment_tokens_insufficient,
                            )
                        } else {
                            stringResource(
                                R.string.payment_balance_insufficient,
                            )
                        },
                    )
                }
            }
        }
    }
}

@Composable
fun PaymentInput(
    input: ContractInput,
    merchantBaseUrl: String,
    tokenFamilies: Map<String, ContractTokenFamily>,
    tokenAvailabilityDetails: PaymentTokenAvailabilityDetails?,
) {
    when (input) {
        is ContractInput.Token -> {
            // TODO: calculate from outside?
            // TODO: better error handling
            val family = tokenFamilies[input.tokenFamilySlug]
                ?: error("no token family ${input.tokenFamilySlug}")

            val availability = tokenAvailabilityDetails?.perTokenFamily
                ?.get(input.tokenFamilySlug)

            TokenCard(
                name = family.name,
                description = TalerUtils.getLocalizedString(
                    family.descriptionI18n,
                    family.description,
                ),
                count = input.count,
                details = family.details,
                merchantBaseUrl = merchantBaseUrl,
                availabilityHint = availability?.causeHint,
            )
        }
    }
}

@Composable
fun PaymentOutput(
    output: ContractOutput,
    tokenFamilies: Map<String, ContractTokenFamily>,
    merchantBaseUrl: String,
) {
    when (output) {
        is ContractOutput.Token -> {
            // TODO: calculate from outside?
            // TODO: better error handling
            val family = tokenFamilies[output.tokenFamilySlug]
                ?: error("no token family for ${output.tokenFamilySlug}")

            TokenCard(
                name = family.name,
                description = TalerUtils.getLocalizedString(
                    family.descriptionI18n,
                    family.description,
                ),
                count = output.count,
                details = family.details,
                merchantBaseUrl = merchantBaseUrl,
            )
        }

        // TODO: ContractOutput.TaxReceipt

        else -> {}
    }
}

@Composable
fun TokenCard(
    name: String,
    description: String,
    count: Int,
    details: ContractTokenDetails,
    merchantBaseUrl: String,
    availabilityHint: TokenAvailabilityHint? = null,
) {
    Card (
        modifier = Modifier
            .padding(vertical = 5.dp)
            .fillMaxWidth(),
    ) {
        Row(
            modifier = Modifier.padding(vertical = 8.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Box(
                Modifier.padding(horizontal = 12.dp),
                contentAlignment = Alignment.Center,
            ) {
                when (details) {
                    is ContractTokenDetails.Discount -> Icon(
                        painterResource(R.drawable.ic_discounts),
                        contentDescription = stringResource(R.string.payment_token_discount),
                        tint = MaterialTheme.colorScheme.onSurfaceVariant,
                    )

                    is ContractTokenDetails.Subscription -> Icon(
                        Icons.Default.EventRepeat,
                        contentDescription = stringResource(R.string.payment_token_subscription),
                        tint = MaterialTheme.colorScheme.onSurfaceVariant,
                    )
                }
            }

            Column(Modifier.weight(1f)) {
                Text(
                    if (count > 1) {
                        stringResource(R.string.payment_product_price_quantity, count, name)
                    } else {
                        name
                    },
                    modifier = Modifier.padding(bottom = 4.dp),
                    style = MaterialTheme.typography.bodyMedium.copy(
                        color = MaterialTheme.colorScheme.onSurface,
                    ),
                )

                Text(
                    description,
                    style = MaterialTheme.typography.bodySmall.copy(
                        color = MaterialTheme.colorScheme.onSurfaceVariant
                    ),
                )
            }

            Row(verticalAlignment = Alignment.CenterVertically) {
                if (availabilityHint != null) {
                    TokenWarningTooltip(merchantBaseUrl, availabilityHint)
                }
            }
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TokenWarningTooltip(
    merchantBaseUrl: String,
    availabilityHint: TokenAvailabilityHint,
) {
    val icon = when(availabilityHint) {
        WalletTokensAvailableInsufficient -> Icons.Default.Error
        MerchantUntrusted -> Icons.Default.Error
        MerchantUnexpected -> Icons.Default.Warning
        else -> return
    }

    val tint = when(availabilityHint) {
        WalletTokensAvailableInsufficient -> MaterialTheme.colorScheme.error
        MerchantUntrusted -> MaterialTheme.colorScheme.error
        MerchantUnexpected -> MaterialTheme.colorScheme.onSurfaceVariant
    }

    val text = when(availabilityHint) {
        WalletTokensAvailableInsufficient -> stringResource(R.string.payment_tokens_insufficient)
        MerchantUntrusted -> stringResource(R.string.payment_tokens_untrusted, cleanExchange(merchantBaseUrl))
        MerchantUnexpected -> stringResource(R.string.payment_tokens_unexpected, cleanExchange(merchantBaseUrl))
    }

    val tooltipState = rememberTooltipState()
    val coroutineScope = rememberCoroutineScope()

    TooltipBox(
        modifier = Modifier,
        positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
        tooltip = { PlainTooltip { Text(text) } },
        state = tooltipState,
    ) {
        IconButton(onClick = {
            coroutineScope.launch {
                tooltipState.show()
            }
        }) {
            Icon(
                icon,
                tint = tint,
                contentDescription = when(availabilityHint) {
                    WalletTokensAvailableInsufficient -> stringResource(R.string.error)
                    MerchantUntrusted -> stringResource(R.string.error)
                    MerchantUnexpected -> stringResource(R.string.warning)
                },
            )
        }
    }
}

@Composable
fun PaymentFeeLabel(
    modifier: Modifier = Modifier,
    amountRaw: Amount,
    amountEffective: Amount,
) {
    if (amountEffective > amountRaw) {
        val fee = amountEffective - amountRaw
        Text(
            modifier = modifier,
            text = stringResource(
                R.string.payment_fee,
                fee.toString(showSymbol = false),
            ),
            style = MaterialTheme.typography.labelLarge.copy(
                color = MaterialTheme.colorScheme.error,
            ),
        )
    }
}

private val contractTermsV0 = ContractTerms.V0(
    merchant = Merchant(
        name = "Demo Shop",
        logo = "data:image/png;base64,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",
    ),
    summary = "order summary",
    orderId = "102910291029",
    merchantBaseUrl = "https://backend.demo.taler.net/",
    amount = Amount.fromJSONString("KUDOS:1"),
    maxFee = Amount.fromJSONString("KUDOS:0"),
    products = listOf(
        ContractProduct(
            description = "something",
            price = Amount.fromJSONString("KUDOS:1"),
            image = "data:image/png;base64,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",
            quantity = 2,
        ),

        ContractProduct(
            description = "something",
            price = Amount.fromJSONString("KUDOS:1"),
            image = "data:image/png;base64,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",
            quantity = 1,
        ),
    ),
    exchanges = listOf(
        Exchange(url = "https://exchange.demo.taler.net/"),
        Exchange(url = "https://exchange.test.taler.net/"),
    ),
)

private val contractTermsV1 = ContractTerms.V1(
    merchant = contractTermsV0.merchant,
    summary = contractTermsV0.summary,
    orderId = contractTermsV0.orderId,
    merchantBaseUrl =  contractTermsV0.merchantBaseUrl,
    products = contractTermsV0.products,
    exchanges = contractTermsV0.exchanges,

    choices = listOf(
        ContractChoice(
            amount = Amount.fromJSONString("KUDOS:10"),
            description = "Tax-deductible movie pass discount",
            maxFee = Amount.fromJSONString("KUDOS:0"),
            inputs = listOf(
                ContractInput.Token(tokenFamilySlug = "half-tax", count = 2),
                ContractInput.Token(tokenFamilySlug = "movie-pass"),
            ),
            outputs = listOf(
                ContractOutput.Token(tokenFamilySlug = "movie-pass"),
                ContractOutput.TaxReceipt(
                    amount = Amount.fromJSONString("KUDOS:10"),
                    donauUrls = listOf("https://donau.test.taler.net/"),
                )
            ),
        ),

        ContractChoice(
            amount = Amount.fromJSONString("KUDOS:200"),
            description = "Movie pass access renewal",
            maxFee = Amount.fromJSONString("KUDOS:0"),
            inputs = listOf(
                ContractInput.Token(tokenFamilySlug = "movie-pass"),
            ),
            outputs = listOf(
                ContractOutput.Token(tokenFamilySlug = "movie-pass"),
            ),
        ),

        ContractChoice(
            amount = Amount.fromJSONString("KUDOS:0"),
            description = "Movie pass access renewal",
            maxFee = Amount.fromJSONString("KUDOS:0"),
            inputs = listOf(
                ContractInput.Token(tokenFamilySlug = "movie-pass"),
            ),
            outputs = listOf(
                ContractOutput.Token(tokenFamilySlug = "movie-pass"),
            ),
        ),
    ),

    tokenFamilies = mapOf(
        "half-tax" to ContractTokenFamily(
            name = "half-tax",
            description = "50% discount",
            details = ContractTokenDetails.Discount,
            critical = true,
        ),

        "movie-pass" to ContractTokenFamily(
            name = "movie-pass",
            description = "Monthly movie pass",
            details = ContractTokenDetails.Subscription,
            critical = true,
        ),
    ),
)

@Preview
@Composable
fun PromptPaymentV0Preview() {
    TalerSurface {
        PromptPaymentComposable(PayStatus.Choices(
            transactionId = "txn:payment:2309203920",
            contractTerms = contractTermsV0,
            choices = listOf(
                PayChoiceDetails(
                    choiceIndex = 0,
                    amountRaw = Amount.fromJSONString("KUDOS:10"),
                    inputs = listOf(),
                    outputs = listOf(),
                    details = PaymentPossible(
                        amountRaw = Amount.fromJSONString("KUDOS:10"),
                        amountEffective = Amount.fromJSONString("KUDOS:10.2"),
                    ),
                )
            )
        ), { _, _ -> }, {}, {}, {}, { DonauStatus.Unavailable })
    }
}

@Preview
@Composable
fun PromptPaymentV1Preview() {
    TalerSurface {
        PromptPaymentComposable(PayStatus.Choices(
            transactionId = "txn:payment:2309203920",
            contractTerms = contractTermsV1,
            defaultChoiceIndex = 2,
            choices = listOf(
                PayChoiceDetails(
                    choiceIndex = 0,
                    amountRaw = contractTermsV1.choices[0].amount,
                    description = "Tax-deductible movie pass discount",
                    inputs = contractTermsV1.choices[0].inputs,
                    outputs = contractTermsV1.choices[0].outputs,
                    details = PaymentPossible(
                        amountRaw = contractTermsV1.choices[0].amount,
                        amountEffective = contractTermsV1.choices[0].amount
                            .plus(Amount.fromJSONString("KUDOS:0.1")),
                        tokenDetails = PaymentTokenAvailabilityDetails(
                            tokensRequested = 3,
                            tokensAvailable = 2,
                            tokensUntrusted = 1,
                            tokensUnexpected = 1,
                            perTokenFamily = mapOf(
                                "half-tax" to PaymentTokenAvailabilityDetails.PerTokenFamily(
                                    causeHint = MerchantUntrusted,
                                    requested = 2,
                                    available = 1,
                                    untrusted = 1,
                                    unexpected = 0
                                ),
                                "movie-pass" to PaymentTokenAvailabilityDetails.PerTokenFamily(
                                    causeHint = MerchantUnexpected,
                                    requested = 1,
                                    available = 1,
                                    untrusted = 0,
                                    unexpected = 1,
                                ),
                            ),
                        ),
                    ),
                ),
                PayChoiceDetails(
                    choiceIndex = 1,
                    amountRaw = contractTermsV1.choices[1].amount,
                    description = "Movie pass access renewal",
                    inputs = contractTermsV1.choices[1].inputs,
                    outputs = contractTermsV1.choices[1].outputs,
                    details = InsufficientBalance(
                        amountRaw = contractTermsV1.choices[1].amount,
                    ),
                ),
                PayChoiceDetails(
                    choiceIndex = 2,
                    amountRaw = contractTermsV1.choices[2].amount,
                    description = "Movie pass access renewal",
                    inputs = contractTermsV1.choices[2].inputs,
                    outputs = contractTermsV1.choices[2].outputs,
                    details = PaymentPossible(
                        amountRaw = contractTermsV1.choices[2].amount,
                        amountEffective = contractTermsV1.choices[2].amount,
                    ),
                ),
            )
        ), { _, _ -> }, {}, {}, {}, {
            DonauStatus.Mismatch(
                donauInfo = DonauInfo("https://donau.test.taler.net/", "123"),
                donauUrls = listOf(
                    "https://donau.demo.taler.net/",
                    "https://donau.head.taler.net/",
                ),
            )
        })
    }
}