package de.arxsilex.maptools.converter.desktop

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import de.arxsilex.maptools.converter.core.ContentSelection
import de.arxsilex.maptools.converter.core.ExportSelection
import de.arxsilex.maptools.converter.core.GeoBounds
import de.arxsilex.maptools.converter.core.InputFileInfo
import de.arxsilex.maptools.converter.core.InputFormat
import de.arxsilex.maptools.converter.core.MapSelectionShape
import de.arxsilex.maptools.converter.core.GeoRectangleRegion
import de.arxsilex.maptools.converter.core.toGeoRegions
import de.arxsilex.maptools.converter.core.unionAreaSquareKilometers
import de.arxsilex.maptools.converter.core.unionOrNull
import java.nio.file.Path
import kotlin.math.roundToInt

internal sealed interface ExportUiState {
    data object Idle : ExportUiState
    data class Running(
        val message: String,
        val progress: Float? = null,
        val phase: String? = null,
    ) : ExportUiState
    data class Completed(val outputs: List<ExportedOutput>, val logs: List<Path>) : ExportUiState
    data class Failed(val message: String, val logs: List<Path> = emptyList()) : ExportUiState
}

internal data class ExportedOutput(val path: Path, val sizeBytes: Long)

@Composable
internal fun ExportStep(
    inputFiles: List<InputFileInfo>,
    sourceBounds: GeoBounds,
    selectedAreas: List<MapSelectionShape>,
    contentSelection: ContentSelection,
    exportSelection: ExportSelection,
    exportState: ExportUiState,
    onBack: () -> Unit,
    onStartExport: () -> Unit,
    onCancelExport: () -> Unit,
    onOpenContainingDirectory: (Path) -> Unit,
) {
    val strings = LocalAppStrings.current
    val regions = selectedAreas.toGeoRegions(sourceBounds)
    val selectionBounds = requireNotNull(regions.map { it.bounds }.unionOrNull())
    val selectedArea = regions.unionAreaSquareKilometers(sourceBounds)
    val sourceArea = listOf(GeoRectangleRegion(sourceBounds)).unionAreaSquareKilometers()
    val sourceSet = inputFiles.toInputSourceSet()
    ScrollableWorkflowPage(
        footer = {
            WorkflowFooter(
                status = when (exportState) {
                    ExportUiState.Idle -> strings["export.status_ready"]
                    is ExportUiState.Running -> exportState.message
                    is ExportUiState.Completed -> strings["export.status_completed"]
                    is ExportUiState.Failed -> strings["export.status_failed"]
                },
                onBack = onBack,
            )
        },
    ) {
        ScreenTitle(
            title = strings["step.export"],
            description = strings["export.description"],
        )
        CardContainer(modifier = Modifier.fillMaxWidth()) {
            Text(strings["export.summary"], style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold)
            Spacer(Modifier.height(18.dp))
            SummaryRow(
                strings["export.sources"],
                if (inputFiles.size == 1) inputFiles.single().name else strings("area.combined_sources", inputFiles.size),
            )
            SummaryRow(
                strings["export.file_format"],
                inputFiles.map(InputFileInfo::format).distinct().joinToString(" + ") { format ->
                    when (format) {
                        InputFormat.OSM_PBF -> "OSM-PBF"
                        InputFormat.MAPSFORGE_MAP -> "Mapsforge .map"
                        else -> format.displayName
                    }
                },
            )
            SummaryRow(strings["export.source_size"], formatFileSize(inputFiles.sumOf(InputFileInfo::sizeBytes)))
            if (sourceSet.hasPoiCompanion) {
                SummaryRow(strings["export.map_output"], sourceSet.mapSources.joinToString { it.name })
                SummaryRow(
                    strings["export.poi_output"],
                    strings("export.original_from", sourceSet.poiSources.joinToString { it.name }),
                )
            }
            if (sourceSet.isPoiOnly) {
                SummaryRow(strings["export.source_data"], strings["export.all_poi_categories"])
                val schemas = sourceSet.poiSources.mapNotNull { it.mapsforgePoiMetadata?.schemaVersion }.distinct()
                SummaryRow(strings["export.source_schema"], schemas.joinToString { "POI v$it" })
                SummaryRow(
                    strings["export.poi_entries"],
                    sourceSet.poiSources.sumOf { it.mapsforgePoiMetadata?.poiCount ?: 0L }.toString(),
                )
            } else {
                SummaryRow(strings["area.selection_areas"], selectedAreas.size.toString())
                SummaryRow(strings["export.selected_area"], formatPercent((selectedArea / sourceArea).toFloat().coerceIn(0f, 1f)))
                selectionBounds.let { bounds ->
                    SummaryRow(
                        strings["export.coordinates"],
                        "${formatCoordinate(bounds.west)}, ${formatCoordinate(bounds.south)} – " +
                            "${formatCoordinate(bounds.east)}, ${formatCoordinate(bounds.north)}",
                    )
                }
                if (sourceSet.hasMapData) {
                    SummaryRow(strings["export.content_groups"], contentSelection.selectedCount().toString())
                }
            }
            Spacer(Modifier.height(18.dp))
            Text(strings["export.output_files"], color = MaterialTheme.colorScheme.onSurfaceVariant)
            Spacer(Modifier.height(8.dp))
            Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
                if (exportSelection.mapsforgeMap) {
                    SummaryChip("Mapsforge .map v${exportSelection.mapFormatVersion.metadataVersion}")
                }
                if (exportSelection.mapsforgePoi) {
                    SummaryChip("Mapsforge .poi v${exportSelection.poiSchemaVersion.metadataVersion}")
                }
                if (exportSelection.filteredPbf) SummaryChip(strings["export.filtered_pbf"])
                if (exportSelection.elevationData) {
                    SummaryChip(exportSelection.elevationOutputFormat.displayName)
                }
            }
        }
        CardContainer(modifier = Modifier.fillMaxWidth()) {
            Row(verticalAlignment = Alignment.CenterVertically) {
                Column(modifier = Modifier.weight(1f)) {
                    Text(strings["export.engine"], fontWeight = FontWeight.SemiBold)
                    Text(
                        when (exportState) {
                            ExportUiState.Idle ->
                                strings["export.idle_hint"]
                            is ExportUiState.Running -> exportState.message
                            is ExportUiState.Completed ->
                                strings["export.completed_message"] + "\n" +
                                    exportState.outputs.joinToString(separator = "\n") {
                                        "${it.path.fileName} (${formatFileSize(it.sizeBytes)})"
                                    }
                            is ExportUiState.Failed -> strings("export.error", exportState.message)
                        },
                        color = MaterialTheme.colorScheme.onSurfaceVariant,
                    )
                    if (exportState is ExportUiState.Running) {
                        Spacer(Modifier.height(10.dp))
                        Row(
                            modifier = Modifier.fillMaxWidth(),
                            verticalAlignment = Alignment.CenterVertically,
                            horizontalArrangement = Arrangement.spacedBy(12.dp),
                        ) {
                            if (exportState.progress == null) {
                                LinearProgressIndicator(modifier = Modifier.weight(1f))
                            } else {
                                LinearProgressIndicator(
                                    progress = { exportState.progress.coerceIn(0f, 1f) },
                                    modifier = Modifier.weight(1f),
                                )
                                Text(
                                    "${(exportState.progress * 100).roundToInt()} %",
                                    color = MaterialTheme.colorScheme.onSurfaceVariant,
                                    fontWeight = FontWeight.Medium,
                                )
                            }
                        }
                    }
                }
                Spacer(Modifier.width(20.dp))
                if (exportState is ExportUiState.Running) {
                    OutlinedButton(onClick = onCancelExport) { Text(strings["common.cancel"]) }
                } else {
                    if (exportState is ExportUiState.Completed) {
                        exportState.outputs.firstOrNull()?.let { output ->
                            OutlinedButton(onClick = { onOpenContainingDirectory(output.path) }) {
                                Text(strings["export.output_folder"])
                            }
                            Spacer(Modifier.width(8.dp))
                        }
                    }
                    val log = when (exportState) {
                        is ExportUiState.Completed -> exportState.logs.lastOrNull()
                        is ExportUiState.Failed -> exportState.logs.lastOrNull()
                        else -> null
                    }
                    if (log != null) {
                        OutlinedButton(onClick = { onOpenContainingDirectory(log) }) { Text(strings["export.log_folder"]) }
                        Spacer(Modifier.width(8.dp))
                    }
                    Button(onClick = onStartExport, enabled = exportSelection.hasOutput) {
                        Text(strings["export.start"])
                    }
                }
            }
        }
        if (sourceSet.hasMapData || sourceSet.hasPoiData) {
            Text(
                strings["about.map_data"],
                color = MaterialTheme.colorScheme.onSurfaceVariant,
                style = MaterialTheme.typography.bodySmall,
            )
        }
    }
}

@Composable
private fun SummaryRow(label: String, value: String) {
    Row(
        modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
        horizontalArrangement = Arrangement.SpaceBetween,
    ) {
        Text(label, color = MaterialTheme.colorScheme.onSurfaceVariant)
        Text(value, fontWeight = FontWeight.Medium)
    }
}

@Composable
private fun SummaryChip(label: String) {
    Surface(
        color = MaterialTheme.colorScheme.tertiaryContainer,
        contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
        shape = RoundedCornerShape(50),
    ) {
        Text(label, modifier = Modifier.padding(horizontal = 12.dp, vertical = 7.dp))
    }
}

private fun ContentSelection.selectedCount(): Int = listOf(
    roads,
    pathsAndTrails,
    buildings,
    landAndWater,
    pointsOfInterest,
    sourceMetadata,
).count { it }
