package de.tsschulz.tt_tagebuch.app.ui

import android.content.Context
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Divider
import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedButton
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
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.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import de.tsschulz.tt_tagebuch.app.AppDependencies
import de.tsschulz.tt_tagebuch.app.pdf.sharePdfFile
import de.tsschulz.tt_tagebuch.shared.api.models.BillingCreateRunBody
import de.tsschulz.tt_tagebuch.shared.api.models.BillingRunDto
import de.tsschulz.tt_tagebuch.shared.api.models.BillingTemplateDto
import de.tsschulz.tt_tagebuch.shared.api.models.MemberOrderDto
import de.tsschulz.tt_tagebuch.shared.api.models.MemberOrderPatchBody
import de.tsschulz.tt_tagebuch.shared.api.models.canReadMembers
import de.tsschulz.tt_tagebuch.shared.api.models.canWriteMembers
import de.tsschulz.tt_tagebuch.shared.i18n.MobileStrings
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import kotlin.math.max

private val HubPad = 16.dp
private val TouchMin = 48.dp

internal enum class BillingOrdersSection {
    Orders,
    Billing,
}

@Composable
internal fun BillingOrdersFlowScreen(
    section: BillingOrdersSection,
    dependencies: AppDependencies,
    onBack: () -> Unit,
) {
    androidx.activity.compose.BackHandler(onBack = onBack)
    when (section) {
        BillingOrdersSection.Orders -> GlobalOrdersScreen(dependencies, onBack)
        BillingOrdersSection.Billing -> BillingClubScreen(dependencies, onBack)
    }
}

private fun normalizeAmount(value: String): Double {
    val p = value.replace(',', '.').trim().toDoubleOrNull() ?: return 0.0
    if (!p.isFinite()) return 0.0
    return max(0.0, kotlin.math.round(p * 100.0) / 100.0)
}

private fun normalizeAmount(v: Double): Double {
    if (!v.isFinite()) return 0.0
    return max(0.0, kotlin.math.round(v * 100.0) / 100.0)
}

private data class OrderEditRow(
    val order: MemberOrderDto,
    val draftItem: String,
    val draftStatus: String,
    val draftCost: String,
    val draftPaidAmount: String,
    val draftBudget: String,
    val draftPaidConfirmed: Boolean,
)

private fun MemberOrderDto.toEditRow() = OrderEditRow(
    order = this,
    draftItem = item,
    draftStatus = status,
    draftCost = cost.toString(),
    draftPaidAmount = paidAmount.toString(),
    draftBudget = budget.toString(),
    draftPaidConfirmed = paidConfirmed,
)

private fun OrderEditRow.hasChanges(): Boolean =
    draftItem != (order.item) ||
        draftStatus != order.status ||
        normalizeAmount(draftCost) != normalizeAmount(order.cost) ||
        normalizeAmount(draftPaidAmount) != normalizeAmount(order.paidAmount) ||
        normalizeAmount(draftBudget) != normalizeAmount(order.budget) ||
        draftPaidConfirmed != order.paidConfirmed

private fun OrderEditRow.openAmount(): Double =
    max(0.0, normalizeAmount(draftCost) - normalizeAmount(draftPaidAmount))

private val orderStatuses = listOf(
    "requested" to "orders.statusRequested",
    "ordered" to "orders.statusOrdered",
    "arrived" to "orders.statusArrived",
    "handed_over" to "orders.statusHandedOver",
)

@Composable
private fun GlobalOrdersScreen(dependencies: AppDependencies, onBack: () -> Unit) {
    val languageCode = LocalLanguageCode.current
    fun tr(key: String, fb: String) = MobileStrings.get(languageCode, key, fb)
    val scope = rememberCoroutineScope()
    var loading by remember { mutableStateOf(true) }
    var err by remember { mutableStateOf<String?>(null) }
    var refresh by remember { mutableIntStateOf(0) }
    var rows by remember { mutableStateOf<List<OrderEditRow>>(emptyList()) }
    var showCompleted by remember { mutableStateOf(false) }
    var search by remember { mutableStateOf("") }
    var statusFilter by remember { mutableStateOf("") }
    var clubFilter by remember { mutableStateOf("") }
    var completionFilter by remember { mutableStateOf("") }
    var statusMenu by remember { mutableStateOf(false) }
    var clubMenu by remember { mutableStateOf(false) }
    var completionMenu by remember { mutableStateOf(false) }
    var savingIds by remember { mutableStateOf(setOf<Int>()) }

    LaunchedEffect(refresh) {
        loading = true
        err = null
        runCatching { dependencies.memberOrdersApi.listGlobal() }
            .onSuccess { env ->
                rows = env.orders.map { it.toEditRow() }
            }
            .onFailure { err = it.message }
        loading = false
    }

    val clubs = remember(rows) {
        rows.mapNotNull { it.order.clubId to (it.order.club?.name ?: "") }
            .distinctBy { it.first }
            .sortedBy { it.second }
    }

    fun statusLabel(code: String): String {
        val key = orderStatuses.find { it.first == code }?.second ?: "orders.statusRequested"
        return tr(key, code)
    }

    val filtered = remember(rows, showCompleted, search, statusFilter, clubFilter, completionFilter) {
        rows.filter { r ->
            val o = r.order
            if (statusFilter.isNotBlank() && o.status != statusFilter) return@filter false
            if (clubFilter.isNotBlank() && o.clubId?.toString() != clubFilter) return@filter false
            val paid = r.draftPaidConfirmed
            val handed = r.draftStatus == "handed_over"
            val completed = paid && handed
            if (!showCompleted && completed) return@filter false
            when (completionFilter) {
                "paid" -> if (!paid) return@filter false
                "handed_over" -> if (!handed) return@filter false
            }
            if (search.isBlank()) return@filter true
            val q = search.trim().lowercase()
            listOf(
                r.draftItem,
                o.club?.name,
                o.member?.firstName,
                o.member?.lastName,
            ).filterNotNull().joinToString(" ").lowercase().contains(q)
        }
    }

    fun quickSaveFlags(orderId: Int, nextStatus: String? = null, nextPaidConfirmed: Boolean? = null) {
        val snapshot = rows.firstOrNull { it.order.id == orderId } ?: return
        val patchedStatus = nextStatus ?: snapshot.draftStatus
        val patchedPaidConfirmed = nextPaidConfirmed ?: snapshot.draftPaidConfirmed
        val memberId = snapshot.order.memberId ?: return
        val orderClubId = snapshot.order.clubId ?: return

        rows = rows.map {
            if (it.order.id == orderId) {
                it.copy(
                    draftStatus = patchedStatus,
                    draftPaidConfirmed = patchedPaidConfirmed,
                )
            } else {
                it
            }
        }

        scope.launch {
            savingIds = savingIds + orderId
            runCatching {
                dependencies.memberOrdersApi.update(
                    clubId = orderClubId,
                    memberId = memberId,
                    orderId = orderId,
                    body = MemberOrderPatchBody(
                        // Nur Status-Flags sofort speichern; restliche Felder bleiben Draft bis "Speichern".
                        item = snapshot.order.item,
                        status = patchedStatus,
                        cost = normalizeAmount(snapshot.order.cost),
                        paidAmount = normalizeAmount(snapshot.order.paidAmount),
                        budget = normalizeAmount(snapshot.order.budget),
                        paidConfirmed = patchedPaidConfirmed,
                    ),
                ).order
            }.onSuccess { updated ->
                if (updated != null) {
                    rows = rows.map { row ->
                        if (row.order.id == orderId) {
                            row.copy(
                                order = updated,
                                draftStatus = updated.status,
                                draftPaidConfirmed = updated.paidConfirmed,
                            )
                        } else {
                            row
                        }
                    }
                }
            }.onFailure { e ->
                rows = rows.map {
                    if (it.order.id == orderId) snapshot else it
                }
                err = e.message
            }
            savingIds = savingIds - orderId
        }
    }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .verticalScroll(rememberScrollState())
            .padding(HubPad),
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) {
            IconButton(onClick = onBack) {
                Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = tr("common.back", "Zurück"))
            }
            Column(Modifier.weight(1f)) {
                Text(tr("orders.globalTitle", "Bestellungen"), style = MaterialTheme.typography.h6)
                Text(
                    tr("orders.globalSubtitle", "Übersicht über Bestellungen in deinen Vereinen."),
                    style = MaterialTheme.typography.caption,
                    color = MaterialTheme.colors.onSurface.copy(alpha = 0.7f),
                )
            }
            TextButton(onClick = { refresh++ }, enabled = !loading) {
                Text(tr("common.refresh", "Aktualisieren"))
            }
        }
        Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 8.dp)) {
            Switch(checked = showCompleted, onCheckedChange = { showCompleted = it })
            Text(tr("orders.showCompleted", "Erledigte anzeigen"), modifier = Modifier.padding(start = 8.dp))
        }
        OutlinedTextField(
            value = search,
            onValueChange = { search = it },
            label = { Text(tr("orders.searchPlaceholder", "Suche")) },
            modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
            singleLine = true,
        )
        Row(Modifier.fillMaxWidth().padding(top = 8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
            Column(Modifier.weight(1f)) {
                Text(tr("orders.status", "Status"), style = MaterialTheme.typography.caption)
                Box {
                    TextButton(onClick = { statusMenu = true }, modifier = Modifier.fillMaxWidth()) {
                        Text(
                            if (statusFilter.isBlank()) tr("orders.filterAllStatuses", "Alle Status") else statusLabel(statusFilter),
                        )
                    }
                    DropdownMenu(expanded = statusMenu, onDismissRequest = { statusMenu = false }) {
                        DropdownMenuItem(onClick = { statusFilter = ""; statusMenu = false }) {
                            Text(tr("orders.filterAllStatuses", "Alle Status"))
                        }
                        orderStatuses.forEach { (v, k) ->
                            DropdownMenuItem(onClick = { statusFilter = v; statusMenu = false }) {
                                Text(tr(k, v))
                            }
                        }
                    }
                }
            }
            Column(Modifier.weight(1f)) {
                Text(tr("orders.club", "Verein"), style = MaterialTheme.typography.caption)
                Box {
                    TextButton(onClick = { clubMenu = true }, modifier = Modifier.fillMaxWidth()) {
                        Text(
                            if (clubFilter.isBlank()) tr("orders.filterAllClubs", "Alle Vereine")
                            else clubs.find { it.first.toString() == clubFilter }?.second ?: clubFilter,
                        )
                    }
                    DropdownMenu(expanded = clubMenu, onDismissRequest = { clubMenu = false }) {
                        DropdownMenuItem(onClick = { clubFilter = ""; clubMenu = false }) {
                            Text(tr("orders.filterAllClubs", "Alle Vereine"))
                        }
                        clubs.forEach { (id, name) ->
                            DropdownMenuItem(onClick = { clubFilter = id.toString(); clubMenu = false }) {
                                Text(name.ifBlank { id.toString() })
                            }
                        }
                    }
                }
            }
        }
        Row(Modifier.fillMaxWidth().padding(top = 4.dp)) {
            Column(Modifier.weight(1f)) {
                Text(tr("mobile.ordersCompletionFilter", "Abschluss"), style = MaterialTheme.typography.caption)
                Box {
                    TextButton(onClick = { completionMenu = true }, modifier = Modifier.fillMaxWidth()) {
                        Text(
                            when (completionFilter) {
                                "paid" -> tr("orders.filterPaid", "Bezahlt")
                                "handed_over" -> tr("orders.filterHandedOver", "Übergeben")
                                else -> tr("orders.filterAllCompletionStates", "Alle")
                            },
                        )
                    }
                    DropdownMenu(expanded = completionMenu, onDismissRequest = { completionMenu = false }) {
                        DropdownMenuItem(onClick = { completionFilter = ""; completionMenu = false }) {
                            Text(tr("orders.filterAllCompletionStates", "Alle"))
                        }
                        DropdownMenuItem(onClick = { completionFilter = "paid"; completionMenu = false }) {
                            Text(tr("orders.filterPaid", "Bezahlt"))
                        }
                        DropdownMenuItem(onClick = { completionFilter = "handed_over"; completionMenu = false }) {
                            Text(tr("orders.filterHandedOver", "Übergeben"))
                        }
                    }
                }
            }
        }
        err?.let { Text(it, color = MaterialTheme.colors.error, modifier = Modifier.padding(top = 8.dp)) }
        when {
            loading -> CircularProgressIndicator(Modifier.padding(top = 24.dp))
            filtered.isEmpty() -> Text(
                tr("orders.noOrdersGlobal", "Keine Bestellungen."),
                modifier = Modifier.padding(top = 16.dp),
                color = MaterialTheme.colors.onSurface.copy(alpha = 0.7f),
            )
            else -> filtered.forEach { row ->
                val o = row.order
                val mid = o.memberId ?: return@forEach
                val cid = o.clubId ?: return@forEach
                Card(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(vertical = 6.dp),
                    elevation = 1.dp,
                ) {
                    Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
                        Text(
                            listOf(o.club?.name, o.member?.firstName, o.member?.lastName)
                                .filterNotNull().joinToString(" · "),
                            fontWeight = FontWeight.SemiBold,
                        )
                        OutlinedTextField(
                            value = row.draftItem,
                            onValueChange = { v ->
                                rows = rows.map { if (it.order.id == o.id) it.copy(draftItem = v) else it }
                            },
                            label = { Text(tr("orders.item", "Artikel")) },
                            modifier = Modifier.fillMaxWidth(),
                            singleLine = true,
                        )
                        Text(tr("orders.status", "Status"), style = MaterialTheme.typography.caption)
                        Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
                            orderStatuses.forEach { (v, k) ->
                                TextButton(
                                    onClick = {
                                        quickSaveFlags(orderId = o.id, nextStatus = v)
                                    },
                                    enabled = !savingIds.contains(o.id),
                                ) {
                                    Text(
                                        tr(k, v),
                                        style = if (row.draftStatus == v) MaterialTheme.typography.body2 else MaterialTheme.typography.caption,
                                        fontWeight = if (row.draftStatus == v) FontWeight.Bold else FontWeight.Normal,
                                    )
                                }
                            }
                        }
                        Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
                            OutlinedTextField(
                                value = row.draftCost,
                                onValueChange = { v ->
                                    rows = rows.map { if (it.order.id == o.id) it.copy(draftCost = v) else it }
                                },
                                label = { Text(tr("orders.cost", "Kosten")) },
                                modifier = Modifier.weight(1f),
                                singleLine = true,
                            )
                            OutlinedTextField(
                                value = row.draftPaidAmount,
                                onValueChange = { v ->
                                    rows = rows.map { if (it.order.id == o.id) it.copy(draftPaidAmount = v) else it }
                                },
                                label = { Text(tr("orders.paid", "Bezahlt")) },
                                modifier = Modifier.weight(1f),
                                singleLine = true,
                            )
                        }
                        Row(verticalAlignment = Alignment.CenterVertically) {
                            Switch(
                                checked = row.draftPaidConfirmed,
                                onCheckedChange = { v ->
                                    quickSaveFlags(orderId = o.id, nextPaidConfirmed = v)
                                },
                                enabled = !savingIds.contains(o.id),
                            )
                            Text(tr("orders.paidConfirmed", "Bezahlt bestätigt"), modifier = Modifier.padding(start = 8.dp))
                        }
                        OutlinedTextField(
                            value = row.draftBudget,
                            onValueChange = { v ->
                                rows = rows.map { if (it.order.id == o.id) it.copy(draftBudget = v) else it }
                            },
                            label = { Text(tr("orders.budget", "Budget")) },
                            modifier = Modifier.fillMaxWidth(),
                            singleLine = true,
                        )
                        Text(
                            "${tr("orders.open", "Offen")}: ${String.format(java.util.Locale.GERMANY, "%.2f €", row.openAmount())}",
                            style = MaterialTheme.typography.caption,
                        )
                        if (o.historyEntries.isNotEmpty()) {
                            Text(tr("orders.history", "Verlauf") + " (${o.historyEntries.size})", fontWeight = FontWeight.Medium)
                            o.historyEntries.take(5).forEach { h ->
                                Text(
                                    "${h.changedAt?.take(16) ?: "–"} · ${statusLabel(h.status ?: "")} · " +
                                        "${h.cost} / ${h.paidAmount}",
                                    style = MaterialTheme.typography.caption,
                                )
                            }
                        }
                        val busy = savingIds.contains(o.id)
                        Button(
                            onClick = {
                                scope.launch {
                                    savingIds = savingIds + o.id
                                    runCatching {
                                        dependencies.memberOrdersApi.update(
                                            clubId = cid,
                                            memberId = mid,
                                            orderId = o.id,
                                            body = MemberOrderPatchBody(
                                                item = row.draftItem.trim(),
                                                status = row.draftStatus,
                                                cost = normalizeAmount(row.draftCost),
                                                paidAmount = normalizeAmount(row.draftPaidAmount),
                                                budget = normalizeAmount(row.draftBudget),
                                                paidConfirmed = row.draftPaidConfirmed,
                                            ),
                                        ).order?.let { updated ->
                                            rows = rows.map {
                                                if (it.order.id == o.id) updated.toEditRow() else it
                                            }
                                        }
                                    }.onFailure { e -> err = e.message }
                                    savingIds = savingIds - o.id
                                }
                            },
                            enabled = !busy && row.hasChanges(),
                            modifier = Modifier.fillMaxWidth().heightIn(min = TouchMin),
                        ) { Text(if (busy) "…" else tr("common.save", "Speichern")) }
                    }
                }
            }
        }
    }
}

private fun monthYyyyMmOrNull(s: String): Boolean =
    s.matches(Regex("\\d{4}-\\d{2}"))

private fun billingLocaleTag(languageCode: String): String =
    when (languageCode.lowercase().take(2)) {
        "en" -> "en-GB"
        else -> "de-DE"
    }

private fun runDeletable(status: String?): Boolean {
    val s = status?.lowercase() ?: ""
    return s == "generated" || s == "draft" || s == "error" || s == "cancelled"
}

@Composable
private fun BillingClubScreen(dependencies: AppDependencies, onBack: () -> Unit) {
    val languageCode = LocalLanguageCode.current
    fun tr(key: String, fb: String) = MobileStrings.get(languageCode, key, fb)
    val context = LocalContext.current
    val androidContext = context as Context
    val scope = rememberCoroutineScope()
    val clubState by dependencies.clubManager.state.collectAsState()
    val authState by dependencies.authManager.state.collectAsState()
    val clubId = clubState.currentClubId ?: return
    val perms = clubState.currentPermissions
    if (perms?.canReadMembers() != true) {
        Column(Modifier.fillMaxSize().padding(HubPad)) {
            Text(tr("mobile.billingNoPermission", "Keine Berechtigung für Abrechnung."), color = MaterialTheme.colors.error)
            TextButton(onClick = onBack) { Text(tr("common.back", "Zurück")) }
        }
        return
    }
    val canWrite = perms.canWriteMembers()

    var loading by remember { mutableStateOf(true) }
    var err by remember { mutableStateOf<String?>(null) }
    var info by remember { mutableStateOf<String?>(null) }
    var refresh by remember { mutableIntStateOf(0) }
    var templates by remember { mutableStateOf<List<BillingTemplateDto>>(emptyList()) }
    var runs by remember { mutableStateOf<List<BillingRunDto>>(emptyList()) }

    var templateName by remember { mutableStateOf("") }
    var templateDescription by remember { mutableStateOf("") }
    var templatePdfBytes by remember { mutableStateOf<ByteArray?>(null) }
    var templateBusy by remember { mutableStateOf(false) }

    val thisMonth = remember {
        java.time.LocalDate.now().toString().take(7)
    }
    var monthFrom by remember { mutableStateOf(thisMonth) }
    var monthTo by remember { mutableStateOf(thisMonth) }
    var selectedTemplateId by remember { mutableStateOf<Int?>(null) }
    var selfName by remember { mutableStateOf("") }
    var iban by remember { mutableStateOf("") }
    var ibanWithoutCountry by remember { mutableStateOf(false) }
    var hourlyRate by remember { mutableStateOf("") }
    var sessionLabel by remember { mutableStateOf("") }
    var locationText by remember { mutableStateOf("") }
    var documentDate by remember {
        mutableStateOf(java.time.LocalDate.now().toString())
    }
    var sameAccount by remember { mutableStateOf(false) }
    var omitSelf by remember { mutableStateOf(false) }
    var omitIban by remember { mutableStateOf(false) }
    var omitLocation by remember { mutableStateOf(false) }
    var omitDocDate by remember { mutableStateOf(false) }
    var omitSession by remember { mutableStateOf(false) }

    var previewTotal by remember { mutableStateOf<Double?>(null) }
    var previewSessions by remember { mutableStateOf(0) }
    var runBusy by remember { mutableStateOf(false) }
    var generatingIds by remember { mutableStateOf(setOf<Int>()) }
    var deleteRunTarget by remember { mutableStateOf<BillingRunDto?>(null) }
    var deleteTemplateTarget by remember { mutableStateOf<BillingTemplateDto?>(null) }

    val pickPdf = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
        if (uri == null) return@rememberLauncherForActivityResult
        scope.launch {
            runCatching {
                withContext(Dispatchers.IO) {
                    androidContext.contentResolver.openInputStream(uri)?.use { it.readBytes() }
                }
            }.onSuccess { bytes ->
                templatePdfBytes = bytes
                info = tr("mobile.billingPdfPicked", "PDF ausgewählt")
            }.onFailure { err = it.message }
        }
    }

    LaunchedEffect(refresh, clubId) {
        loading = true
        err = null
        runCatching {
            val t = dependencies.billingApi.listTemplates(clubId)
            val r = dependencies.billingApi.listRuns(clubId)
            val s = dependencies.billingApi.getSettings(clubId)
            templates = t.templates
            runs = r.runs
            s.settings?.let { set ->
                if (hourlyRate.isBlank() && set.lastHourlyRate != null) {
                    hourlyRate = set.lastHourlyRate.toString()
                }
                if (selfName.isBlank()) {
                    selfName = set.lastSelfRecipientName?.trim().orEmpty()
                        .ifBlank { authState.username.orEmpty() }
                }
                if (locationText.isBlank()) {
                    locationText = set.lastLocationText.orEmpty()
                }
            }
            if (selfName.isBlank()) {
                selfName = authState.username.orEmpty()
            }
            if (selectedTemplateId == null && templates.isNotEmpty()) {
                selectedTemplateId = templates.first().id
            }
        }.onFailure { e -> err = e.message }
        loading = false
    }

    LaunchedEffect(clubId, monthFrom, monthTo) {
        if (!monthYyyyMmOrNull(monthFrom) || !monthYyyyMmOrNull(monthTo)) {
            previewTotal = null
            previewSessions = 0
            return@LaunchedEffect
        }
        runCatching {
            val p = dependencies.billingApi.hoursPreview(clubId, monthFrom, monthTo)
            previewTotal = p.computedHoursTotal
            previewSessions = p.sessions.size
        }.onFailure {
            previewTotal = null
            previewSessions = 0
        }
    }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .verticalScroll(rememberScrollState())
            .padding(HubPad),
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) {
            IconButton(onClick = onBack) {
                Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = tr("common.back", "Zurück"))
            }
            Column(Modifier.weight(1f)) {
                Text(tr("billing.title", "Abrechnung"), style = MaterialTheme.typography.h6)
                Text(
                    tr("billing.subtitle", "PDF-Abrechnung aus Tagebuch-Zeiten."),
                    style = MaterialTheme.typography.caption,
                    color = MaterialTheme.colors.onSurface.copy(alpha = 0.7f),
                )
            }
        }
        info?.let { Text(it, color = MaterialTheme.colors.primary, modifier = Modifier.padding(top = 8.dp)) }
        err?.let { Text(it, color = MaterialTheme.colors.error, modifier = Modifier.padding(top = 8.dp)) }

        if (loading) {
            CircularProgressIndicator(Modifier.padding(top = 24.dp))
            return@Column
        }

        Text(tr("billing.templateSection", "Vorlagen"), fontWeight = FontWeight.SemiBold, modifier = Modifier.padding(top = 16.dp))
        if (canWrite) {
            OutlinedTextField(
                value = templateName,
                onValueChange = { templateName = it },
                label = { Text(tr("billing.templateName", "Name")) },
                modifier = Modifier.fillMaxWidth(),
                singleLine = true,
            )
            OutlinedTextField(
                value = templateDescription,
                onValueChange = { templateDescription = it },
                label = { Text(tr("billing.templateDescription", "Beschreibung")) },
                modifier = Modifier.fillMaxWidth(),
                singleLine = true,
            )
            OutlinedButton(
                onClick = { pickPdf.launch("application/pdf") },
                modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
            ) { Text(tr("billing.templatePdf", "PDF wählen")) }
            Button(
                onClick = {
                    val bytes = templatePdfBytes ?: return@Button
                    val name = templateName.trim()
                    if (name.isEmpty()) return@Button
                    scope.launch {
                        templateBusy = true
                        err = null
                        runCatching {
                            dependencies.billingApi.uploadTemplate(
                                clubId = clubId,
                                name = name,
                                description = templateDescription.trim(),
                                pdfBytes = bytes,
                            )
                            templateName = ""
                            templateDescription = ""
                            templatePdfBytes = null
                            refresh++
                            info = tr("mobile.billingTemplateUploaded", "Vorlage hochgeladen")
                        }.onFailure { e -> err = e.message }
                        templateBusy = false
                    }
                },
                enabled = !templateBusy && templatePdfBytes != null && templateName.isNotBlank(),
                modifier = Modifier.fillMaxWidth().padding(top = 8.dp).heightIn(min = TouchMin),
            ) { Text(if (templateBusy) "…" else tr("billing.uploadTemplate", "Vorlage hochladen")) }
        }
        templates.forEach { tpl ->
            Card(Modifier.fillMaxWidth().padding(vertical = 4.dp), elevation = 1.dp) {
                Row(
                    Modifier
                        .fillMaxWidth()
                        .padding(12.dp),
                    horizontalArrangement = Arrangement.SpaceBetween,
                    verticalAlignment = Alignment.CenterVertically,
                ) {
                    Column(Modifier.weight(1f)) {
                        Text("${tpl.name} (v${tpl.version})", fontWeight = FontWeight.Medium)
                        tpl.description?.takeIf { it.isNotBlank() }?.let { Text(it, style = MaterialTheme.typography.caption) }
                    }
                    if (canWrite) {
                        TextButton(onClick = { deleteTemplateTarget = tpl }) {
                            Text(tr("billing.deleteTemplate", "Löschen"), color = MaterialTheme.colors.error)
                        }
                    }
                }
            }
        }
        if (templates.isEmpty()) {
            Text(tr("billing.noTemplates", "Keine Vorlagen."), style = MaterialTheme.typography.caption)
        }

        Text(tr("billing.runSection", "Abrechnung erstellen"), fontWeight = FontWeight.SemiBold, modifier = Modifier.padding(top = 20.dp))
        Text(tr("billing.step2", "Schritt 2"), style = MaterialTheme.typography.caption, color = MaterialTheme.colors.onSurface.copy(alpha = 0.65f))
        OutlinedTextField(
            value = monthFrom,
            onValueChange = { monthFrom = it },
            label = { Text(tr("billing.monthFrom", "Monat von (YYYY-MM)")) },
            modifier = Modifier.fillMaxWidth(),
            singleLine = true,
            enabled = canWrite,
        )
        OutlinedTextField(
            value = monthTo,
            onValueChange = { monthTo = it },
            label = { Text(tr("billing.monthTo", "Monat bis (YYYY-MM)")) },
            modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
            singleLine = true,
            enabled = canWrite,
        )
        Text(
            tr("billing.hoursAutoHint", "Stunden aus Tagebuch: {hours} h ({count} Termine)")
                .replace("{hours}", previewTotal?.let { String.format(java.util.Locale.GERMANY, "%.2f", it) } ?: "–")
                .replace("{count}", previewSessions.toString()),
            style = MaterialTheme.typography.caption,
            modifier = Modifier.padding(top = 4.dp),
        )
        var tplMenu by remember { mutableStateOf(false) }
        Text(tr("billing.template", "Vorlage"), style = MaterialTheme.typography.caption)
        Box {
            TextButton(
                onClick = { if (canWrite) tplMenu = true },
                enabled = canWrite && templates.isNotEmpty(),
                modifier = Modifier.fillMaxWidth(),
            ) {
                val id = selectedTemplateId
                Text(templates.find { it.id == id }?.let { "${it.name} (v${it.version})" } ?: tr("common.choose", "Wählen"))
            }
            DropdownMenu(expanded = tplMenu, onDismissRequest = { tplMenu = false }) {
                templates.forEach { tpl ->
                    DropdownMenuItem(onClick = { selectedTemplateId = tpl.id; tplMenu = false }) {
                        Text("${tpl.name} (v${tpl.version})")
                    }
                }
            }
        }
        OutlinedTextField(
            value = selfName,
            onValueChange = { selfName = it },
            label = { Text(tr("billing.selfRecipientName", "Name / Empfänger")) },
            modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
            enabled = canWrite && !omitSelf,
            singleLine = true,
        )
        Row(verticalAlignment = Alignment.CenterVertically) {
            Switch(checked = omitSelf, onCheckedChange = { omitSelf = it }, enabled = canWrite)
            Text(tr("billing.omitField", "Feld auslassen"), modifier = Modifier.padding(start = 8.dp))
        }
        OutlinedTextField(
            value = iban,
            onValueChange = { iban = it },
            label = { Text(tr("billing.iban", "IBAN")) },
            modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
            enabled = canWrite && !omitIban,
            singleLine = true,
        )
        Row(verticalAlignment = Alignment.CenterVertically) {
            Switch(checked = ibanWithoutCountry, onCheckedChange = { ibanWithoutCountry = it }, enabled = canWrite && !omitIban)
            Text(tr("billing.ibanWithoutCountry", "IBAN ohne Ländercode"), modifier = Modifier.padding(start = 8.dp))
        }
        Row(verticalAlignment = Alignment.CenterVertically) {
            Switch(
                checked = omitIban,
                onCheckedChange = { v ->
                    if (v) iban = ""
                    omitIban = v
                },
                enabled = canWrite,
            )
            Text(tr("billing.omitField", "IBAN auslassen"), modifier = Modifier.padding(start = 8.dp))
        }
        OutlinedTextField(
            value = hourlyRate,
            onValueChange = { hourlyRate = it },
            label = { Text(tr("billing.hourlyRate", "Stundenlohn")) },
            modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
            enabled = canWrite,
            singleLine = true,
        )
        OutlinedTextField(
            value = sessionLabel,
            onValueChange = { sessionLabel = it },
            label = { Text(tr("billing.sessionLabel", "Bezeichnung Termine")) },
            modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
            enabled = canWrite && !omitSession,
            singleLine = true,
        )
        Row(verticalAlignment = Alignment.CenterVertically) {
            Switch(checked = omitSession, onCheckedChange = { omitSession = it }, enabled = canWrite)
            Text(tr("billing.omitField", "Feld auslassen"), modifier = Modifier.padding(start = 8.dp))
        }
        OutlinedTextField(
            value = locationText,
            onValueChange = { locationText = it },
            label = { Text(tr("billing.locationText", "Ort")) },
            modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
            enabled = canWrite && !omitLocation,
            singleLine = true,
        )
        Row(verticalAlignment = Alignment.CenterVertically) {
            Switch(checked = omitLocation, onCheckedChange = { omitLocation = it }, enabled = canWrite)
            Text(tr("billing.omitField", "Feld auslassen"), modifier = Modifier.padding(start = 8.dp))
        }
        LocalizedDatePickerField(
            value = documentDate,
            onValueChange = { documentDate = it },
            label = tr("billing.documentDate", "Datum Dokument"),
            modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
            enabled = canWrite && !omitDocDate,
        )
        Row(verticalAlignment = Alignment.CenterVertically) {
            Switch(checked = omitDocDate, onCheckedChange = { omitDocDate = it }, enabled = canWrite)
            Text(tr("billing.omitField", "Feld auslassen"), modifier = Modifier.padding(start = 8.dp))
        }
        Row(verticalAlignment = Alignment.CenterVertically) {
            Switch(checked = sameAccount, onCheckedChange = { sameAccount = it }, enabled = canWrite)
            Text(tr("billing.sameAccountCheckbox", "Gleiches Konto"), modifier = Modifier.padding(start = 8.dp))
        }
        Button(
            onClick = {
                val tid = selectedTemplateId ?: return@Button
                val rate = hourlyRate.replace(',', '.').toDoubleOrNull() ?: return@Button
                if (!monthYyyyMmOrNull(monthFrom) || !monthYyyyMmOrNull(monthTo)) return@Button
                scope.launch {
                    runBusy = true
                    err = null
                    info = null
                    runCatching {
                        val create = dependencies.billingApi.createRun(
                            clubId,
                            BillingCreateRunBody(
                                templateId = tid,
                                monthFrom = monthFrom,
                                monthTo = monthTo,
                                selfRecipientName = selfName.ifBlank { null },
                                iban = iban.ifBlank { null },
                                ibanWithoutCountry = ibanWithoutCountry,
                                hourlyRate = rate,
                                sessionLabel = sessionLabel.ifBlank { null },
                                sameAccountCheckbox = sameAccount,
                                omitSelfRecipientName = omitSelf,
                                omitIban = omitIban,
                                omitLocationText = omitLocation,
                                omitDocumentDate = omitDocDate,
                                omitSessionLabel = omitSession,
                                locationText = locationText.ifBlank { null },
                                documentDate = documentDate.ifBlank { null },
                            ),
                        )
                        val runId = create.run?.id ?: error(create.error ?: "create failed")
                        val gen = dependencies.billingApi.generateRun(runId, billingLocaleTag(languageCode))
                        if (!gen.success) error(gen.error ?: "generate failed")
                        val pdf = dependencies.billingApi.downloadRunPdf(runId)
                        val f = File(androidContext.cacheDir, "abrechnung-$runId.pdf")
                        f.writeBytes(pdf)
                        sharePdfFile(androidContext, f, tr("billing.generateAndDownloadPdf", "PDF teilen"))
                        refresh++
                        info = tr("billing.generateSuccess", "Erfolgreich")
                    }.onFailure { e -> err = e.message }
                    runBusy = false
                }
            },
            enabled = canWrite && !runBusy && selectedTemplateId != null && hourlyRate.isNotBlank() &&
                monthYyyyMmOrNull(monthFrom) && monthYyyyMmOrNull(monthTo),
            modifier = Modifier.fillMaxWidth().padding(top = 12.dp).heightIn(min = TouchMin),
        ) {
            Text(if (runBusy) "…" else tr("billing.generateOwnBilling", "Abrechnung erzeugen und PDF teilen"))
        }

        Text(tr("billing.runsTitle", "Läufe"), fontWeight = FontWeight.SemiBold, modifier = Modifier.padding(top = 20.dp))
        if (runs.isEmpty()) {
            Text(tr("billing.noRuns", "Keine Läufe."), style = MaterialTheme.typography.caption)
        }
        runs.forEach { run ->
            Card(Modifier.fillMaxWidth().padding(vertical = 4.dp), elevation = 1.dp) {
                Column(Modifier.padding(12.dp)) {
                    Text(run.selfRecipientName ?: "–", fontWeight = FontWeight.Medium)
                    Text("${run.periodStart ?: "–"} – ${run.periodEnd ?: "–"}", style = MaterialTheme.typography.caption)
                    Text(
                        "${tr("billing.hourlyRate", "Stundenlohn")}: ${run.hourlyRate} · ${tr("common.status", "Status")}: ${run.status ?: "–"}",
                        style = MaterialTheme.typography.caption,
                    )
                    Row(Modifier.padding(top = 8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
                        val busy = generatingIds.contains(run.id)
                        Button(
                            onClick = {
                                scope.launch {
                                    generatingIds = generatingIds + run.id
                                    err = null
                                    runCatching {
                                        val gen = dependencies.billingApi.generateRun(run.id, billingLocaleTag(languageCode))
                                        if (!gen.success) error(gen.error ?: "generate failed")
                                        val pdf = dependencies.billingApi.downloadRunPdf(run.id)
                                        val f = File(androidContext.cacheDir, "abrechnung-${run.id}.pdf")
                                        f.writeBytes(pdf)
                                        sharePdfFile(androidContext, f, tr("billing.generateAndDownloadPdf", "PDF teilen"))
                                        refresh++
                                    }.onFailure { e -> err = e.message }
                                    generatingIds = generatingIds - run.id
                                }
                            },
                            enabled = !busy,
                        ) { Text(if (busy) "…" else tr("billing.generateAndDownloadPdf", "PDF")) }
                        if (canWrite && runDeletable(run.status)) {
                            TextButton(onClick = { deleteRunTarget = run }) {
                                Text(tr("billing.deleteBilling", "Löschen"), color = MaterialTheme.colors.error)
                            }
                        }
                    }
                }
            }
        }
        Spacer(Modifier.height(24.dp))
        Divider(Modifier.padding(vertical = 8.dp))
        Text(
            tr("mobile.billingWebOptionalHint", "PDF-Vorlagen mit interaktiver Feldzuordnung bearbeiten (Webbrowser)."),
            style = MaterialTheme.typography.caption,
            color = MaterialTheme.colors.onSurface.copy(alpha = 0.65f),
        )
        // Web-UI wird in der Produktiv-App nicht aufgerufen.
    }

    deleteRunTarget?.let { target ->
        StableAlertDialog(
            onDismissRequest = { deleteRunTarget = null },
            title = { Text(tr("billing.deleteBilling", "Löschen")) },
            text = { Text(tr("billing.deleteConfirm", "Abrechnungslauf wirklich löschen?")) },
            confirmButton = {
                TextButton(
                    onClick = {
                        scope.launch {
                            runCatching { dependencies.billingApi.deleteRun(target.id) }
                                .onSuccess { refresh++; deleteRunTarget = null; info = tr("billing.deleteSuccess", "Gelöscht") }
                                .onFailure { err = it.message }
                        }
                    },
                ) { Text(tr("common.delete", "Löschen")) }
            },
            dismissButton = { TextButton(onClick = { deleteRunTarget = null }) { Text(tr("common.cancel", "Abbrechen")) } },
        )
    }
    deleteTemplateTarget?.let { target ->
        StableAlertDialog(
            onDismissRequest = { deleteTemplateTarget = null },
            title = { Text(tr("billing.deleteTemplate", "Vorlage löschen")) },
            text = { Text(tr("billing.deleteTemplateConfirm", "Vorlage wirklich löschen?")) },
            confirmButton = {
                TextButton(
                    onClick = {
                        scope.launch {
                            runCatching { dependencies.billingApi.deleteTemplate(target.id) }
                                .onSuccess {
                                    deleteTemplateTarget = null
                                    if (selectedTemplateId == target.id) selectedTemplateId = null
                                    refresh++
                                    info = tr("billing.deleteTemplateSuccess", "Vorlage gelöscht")
                                }
                                .onFailure { err = it.message }
                        }
                    },
                ) { Text(tr("common.delete", "Löschen")) }
            },
            dismissButton = { TextButton(onClick = { deleteTemplateTarget = null }) { Text(tr("common.cancel", "Abbrechen")) } },
        )
    }
}
