package de.arxsilex.maptools.converter.desktop

import androidx.compose.runtime.staticCompositionLocalOf
import java.text.DecimalFormat
import java.text.NumberFormat
import kotlin.math.abs

internal class MeasurementFormatter(
    val system: MeasurementSystem,
    language: AppLanguage,
) {
    private val locale = language.locale

    val radiusUnit: String get() = if (system == MeasurementSystem.METRIC) "km" else "mi"

    fun formatDistance(kilometers: Double): String = when (system) {
        MeasurementSystem.METRIC -> if (abs(kilometers) < 1.0) {
            "${number(kilometers * 1_000.0, 0)} m"
        } else {
            "${number(kilometers, 1)} km"
        }
        MeasurementSystem.IMPERIAL -> {
            val miles = kilometers * KILOMETERS_TO_MILES
            if (abs(miles) < 0.1) "${number(miles * FEET_PER_MILE, 0)} ft" else "${number(miles, 1)} mi"
        }
    }

    fun formatArea(squareKilometers: Double): String = when (system) {
        MeasurementSystem.METRIC -> when {
            abs(squareKilometers) < 0.01 -> "${number(squareKilometers * 100.0, 2)} ha"
            abs(squareKilometers) < 100.0 -> "${number(squareKilometers, 2)} km²"
            else -> "${number(squareKilometers, 0)} km²"
        }
        MeasurementSystem.IMPERIAL -> {
            val squareMiles = squareKilometers * SQUARE_KILOMETERS_TO_SQUARE_MILES
            if (abs(squareMiles) < 1.0) {
                "${number(squareMiles * ACRES_PER_SQUARE_MILE, 2)} ac"
            } else {
                "${number(squareMiles, if (abs(squareMiles) < 100.0) 2 else 0)} mi²"
            }
        }
    }

    fun formatElevation(meters: Double): String = when (system) {
        MeasurementSystem.METRIC -> "${number(meters, 0)} m"
        MeasurementSystem.IMPERIAL -> "${number(meters * FEET_PER_METER, 0)} ft"
    }

    fun formatEditableRadius(kilometers: Double): String = editableNumber(toDisplayRadius(kilometers), 3)

    fun parseRadius(text: String, minimumKilometers: Double, maximumKilometers: Double, strings: AppStrings): Result<Double> =
        runCatching {
            val displayValue = parseLocalizedNumber(text) ?: error(strings["error.radius.number"])
            val kilometers = fromDisplayRadius(displayValue)
            require(kilometers in minimumKilometers..maximumKilometers) {
                strings(
                    "error.radius.range",
                    formatEditableRadius(minimumKilometers),
                    formatEditableRadius(maximumKilometers),
                    radiusUnit,
                )
            }
            kilometers
        }

    fun formatCount(value: Long): String = NumberFormat.getIntegerInstance(locale).format(value)

    private fun toDisplayRadius(kilometers: Double): Double = when (system) {
        MeasurementSystem.METRIC -> kilometers
        MeasurementSystem.IMPERIAL -> kilometers * KILOMETERS_TO_MILES
    }

    private fun fromDisplayRadius(value: Double): Double = when (system) {
        MeasurementSystem.METRIC -> value
        MeasurementSystem.IMPERIAL -> value / KILOMETERS_TO_MILES
    }

    private fun number(value: Double, decimals: Int): String = NumberFormat.getNumberInstance(locale).apply {
        minimumFractionDigits = decimals
        maximumFractionDigits = decimals
        isGroupingUsed = true
    }.format(value)

    private fun editableNumber(value: Double, decimals: Int): String =
        (NumberFormat.getNumberInstance(locale) as DecimalFormat).apply {
            minimumFractionDigits = 0
            maximumFractionDigits = decimals
            isGroupingUsed = false
        }.format(value)

    private fun parseLocalizedNumber(value: String): Double? {
        val trimmed = value.trim().replace("\u00a0", "").replace(" ", "")
        if (trimmed.isEmpty()) return null
        if (trimmed.count { it == '.' || it == ',' } > 1) return null
        return trimmed.replace(',', '.').toDoubleOrNull()
    }

    private companion object {
        const val KILOMETERS_TO_MILES = 0.621371192237334
        const val SQUARE_KILOMETERS_TO_SQUARE_MILES = 0.386102158542446
        const val FEET_PER_MILE = 5_280.0
        const val FEET_PER_METER = 3.280839895013123
        const val ACRES_PER_SQUARE_MILE = 640.0
    }
}

internal val LocalMeasurements = staticCompositionLocalOf {
    MeasurementFormatter(MeasurementSystem.METRIC, AppLanguage.ENGLISH)
}
