package com.atridad.ascently.data.model

import kotlinx.serialization.Serializable

@Serializable
enum class DifficultySystem {
    // Bouldering
    V_SCALE,
    FONT,

    // Rope
    YDS,
    CUSTOM,
    ;

    val displayName: String
        get() =
            when (this) {
                V_SCALE -> "V Scale"
                FONT -> "Font Scale"
                YDS -> "YDS (Yosemite)"
                CUSTOM -> "Custom"
            }

    val isBoulderingSystem: Boolean
        get() =
            when (this) {
                V_SCALE, FONT -> true
                YDS -> false
                CUSTOM -> true
            }

    val isRopeSystem: Boolean
        get() =
            when (this) {
                YDS -> true
                V_SCALE, FONT -> false
                CUSTOM -> true
            }

    val availableGrades: List<String>
        get() =
            when (this) {
                V_SCALE ->
                    listOf(
                        "VB",
                        "V0",
                        "V1",
                        "V2",
                        "V3",
                        "V4",
                        "V5",
                        "V6",
                        "V7",
                        "V8",
                        "V9",
                        "V10",
                        "V11",
                        "V12",
                        "V13",
                        "V14",
                        "V15",
                        "V16",
                        "V17",
                    )
                FONT ->
                    listOf(
                        "3",
                        "4A",
                        "4B",
                        "4C",
                        "5A",
                        "5B",
                        "5C",
                        "6A",
                        "6A+",
                        "6B",
                        "6B+",
                        "6C",
                        "6C+",
                        "7A",
                        "7A+",
                        "7B",
                        "7B+",
                        "7C",
                        "7C+",
                        "8A",
                        "8A+",
                        "8B",
                        "8B+",
                        "8C",
                        "8C+",
                    )
                YDS ->
                    listOf(
                        "5.0",
                        "5.1",
                        "5.2",
                        "5.3",
                        "5.4",
                        "5.5",
                        "5.6",
                        "5.7",
                        "5.8",
                        "5.9",
                        "5.10a",
                        "5.10b",
                        "5.10c",
                        "5.10d",
                        "5.11a",
                        "5.11b",
                        "5.11c",
                        "5.11d",
                        "5.12a",
                        "5.12b",
                        "5.12c",
                        "5.12d",
                        "5.13a",
                        "5.13b",
                        "5.13c",
                        "5.13d",
                        "5.14a",
                        "5.14b",
                        "5.14c",
                        "5.14d",
                        "5.15a",
                        "5.15b",
                        "5.15c",
                        "5.15d",
                    )
                CUSTOM -> emptyList()
            }

    companion object {
        fun systemsForClimbType(climbType: ClimbType): List<DifficultySystem> =
            when (climbType) {
                ClimbType.BOULDER -> entries.filter { it.isBoulderingSystem }
                ClimbType.ROPE -> entries.filter { it.isRopeSystem }
            }
    }
}

@Serializable
data class DifficultyGrade(val system: DifficultySystem, val grade: String, val numericValue: Int) {

    constructor(
        system: DifficultySystem,
        grade: String,
    ) : this(system = system, grade = grade, numericValue = calculateNumericValue(system, grade))

    companion object {
        private fun calculateNumericValue(system: DifficultySystem, grade: String): Int {
            return when (system) {
                DifficultySystem.V_SCALE -> {
                    if (grade == "VB") 0 else grade.removePrefix("V").toIntOrNull() ?: 0
                }
                DifficultySystem.FONT -> {
                    val fontMapping: Map<String, Int> =
                        mapOf(
                            "3" to 3,
                            "4A" to 4,
                            "4B" to 5,
                            "4C" to 6,
                            "5A" to 7,
                            "5B" to 8,
                            "5C" to 9,
                            "6A" to 10,
                            "6A+" to 11,
                            "6B" to 12,
                            "6B+" to 13,
                            "6C" to 14,
                            "6C+" to 15,
                            "7A" to 16,
                            "7A+" to 17,
                            "7B" to 18,
                            "7B+" to 19,
                            "7C" to 20,
                            "7C+" to 21,
                            "8A" to 22,
                            "8A+" to 23,
                            "8B" to 24,
                            "8B+" to 25,
                            "8C" to 26,
                            "8C+" to 27,
                        )
                    fontMapping[grade] ?: 0
                }
                DifficultySystem.YDS -> {
                    val ydsMapping: Map<String, Int> =
                        mapOf(
                            "5.0" to 50,
                            "5.1" to 51,
                            "5.2" to 52,
                            "5.3" to 53,
                            "5.4" to 54,
                            "5.5" to 55,
                            "5.6" to 56,
                            "5.7" to 57,
                            "5.8" to 58,
                            "5.9" to 59,
                            "5.10a" to 60,
                            "5.10b" to 61,
                            "5.10c" to 62,
                            "5.10d" to 63,
                            "5.11a" to 64,
                            "5.11b" to 65,
                            "5.11c" to 66,
                            "5.11d" to 67,
                            "5.12a" to 68,
                            "5.12b" to 69,
                            "5.12c" to 70,
                            "5.12d" to 71,
                            "5.13a" to 72,
                            "5.13b" to 73,
                            "5.13c" to 74,
                            "5.13d" to 75,
                            "5.14a" to 76,
                            "5.14b" to 77,
                            "5.14c" to 78,
                            "5.14d" to 79,
                            "5.15a" to 80,
                            "5.15b" to 81,
                            "5.15c" to 82,
                            "5.15d" to 83,
                        )
                    ydsMapping[grade] ?: 0
                }
                DifficultySystem.CUSTOM -> grade.toIntOrNull() ?: 0
            }
        }
    }

    /**
     * Compare this grade with another grade of the same system Returns negative if this grade is
     * easier, positive if harder, 0 if equal
     */
    fun compareTo(other: DifficultyGrade): Int {
        if (system != other.system) return 0

        return when (system) {
            DifficultySystem.V_SCALE -> compareVScaleGrades(grade, other.grade)
            DifficultySystem.FONT -> compareFontGrades(grade, other.grade)
            DifficultySystem.YDS -> compareYDSGrades(grade, other.grade)
            DifficultySystem.CUSTOM -> grade.compareTo(other.grade)
        }
    }

    private fun compareVScaleGrades(grade1: String, grade2: String): Int {
        if (grade1 == "VB" && grade2 != "VB") return -1
        if (grade2 == "VB" && grade1 != "VB") return 1
        if (grade1 == "VB") return 0

        val num1 = grade1.removePrefix("V").toIntOrNull() ?: 0
        val num2 = grade2.removePrefix("V").toIntOrNull() ?: 0
        return num1.compareTo(num2)
    }

    private fun compareFontGrades(grade1: String, grade2: String): Int {
        return grade1.compareTo(grade2)
    }

    private fun compareYDSGrades(grade1: String, grade2: String): Int {
        return grade1.compareTo(grade2)
    }
}
