package app.voltplan.cable.calc

import app.voltplan.cable.data.UnitSystem
import kotlin.math.ceil

/**
 * Pure wire-sizing math. Direct port of the iOS `ElectricalCalculations` struct.
 * Lengths are always in metres; [UnitSystem] only controls the output format (mm² vs AWG).
 */
object ElectricalCalculations {
    private const val MAX_VOLTAGE_DROP_FRACTION = 0.05
    private const val COPPER_RESISTIVITY = 0.017 // Ω·mm²/m
    const val FEET_PER_METER = 3.28084

    private val standardMetricCrossSections = listOf(
        0.75, 1.0, 1.5, 2.5, 4.0, 6.0, 10.0, 16.0, 25.0, 35.0, 50.0, 70.0, 95.0, 120.0,
        150.0, 185.0, 240.0, 300.0, 400.0, 500.0, 630.0,
    )

    // Convention: 1/0 = -1, 2/0 = -2, 3/0 = -3, 4/0 = -4
    private val standardAWG = listOf(20, 18, 16, 14, 12, 10, 8, 6, 4, 2, 1, -1, -2, -3, -4)
    private val awgCrossSections = listOf(
        0.519, 0.823, 1.31, 2.08, 3.31, 5.26, 8.37, 13.3, 21.2, 33.6, 42.4, 53.5, 67.4, 85.0, 107.0,
    )

    private val standardFuses = listOf(
        1.0, 2.0, 3.0, 5.0, 7.5, 10.0, 15.0, 20.0, 25.0, 30.0, 35.0, 40.0, 50.0,
        60.0, 70.0, 80.0, 100.0, 125.0, 150.0, 175.0, 200.0, 225.0, 250.0,
        300.0, 350.0, 400.0, 450.0, 500.0, 600.0, 700.0, 800.0,
    )

    fun recommendedCrossSection(
        length: Double,
        current: Double,
        voltage: Double,
        unitSystem: UnitSystem,
    ): Double {
        val maxVoltageDrop = voltage * MAX_VOLTAGE_DROP_FRACTION
        val minimumCrossSection = guardAgainstZero(maxVoltageDrop) {
            (2 * current * length * COPPER_RESISTIVITY) / maxVoltageDrop
        }

        return if (unitSystem == UnitSystem.IMPERIAL) {
            for (index in awgCrossSections.indices) {
                if (awgCrossSections[index] >= minimumCrossSection) {
                    return standardAWG[index].toDouble()
                }
            }
            (standardAWG.lastOrNull() ?: 0).toDouble()
        } else {
            val floor = standardMetricCrossSections.first()
            standardMetricCrossSections.firstOrNull { it >= maxOf(floor, minimumCrossSection) }
                ?: standardMetricCrossSections.last()
        }
    }

    fun voltageDrop(
        length: Double,
        current: Double,
        voltage: Double,
        unitSystem: UnitSystem,
        crossSection: Double? = null,
    ): Double {
        val selected = crossSection
            ?: recommendedCrossSection(length, current, voltage, unitSystem)

        val crossSectionMM2 = if (unitSystem == UnitSystem.METRIC) {
            selected
        } else {
            crossSectionFromAWG(selected)
        }

        if (crossSectionMM2 <= 0) return 0.0
        return (2 * current * length * COPPER_RESISTIVITY) / crossSectionMM2
    }

    fun voltageDropPercentage(
        length: Double,
        current: Double,
        voltage: Double,
        unitSystem: UnitSystem,
        crossSection: Double? = null,
    ): Double {
        if (voltage == 0.0) return 0.0
        return voltageDrop(length, current, voltage, unitSystem, crossSection) / voltage * 100
    }

    fun powerLoss(
        length: Double,
        current: Double,
        voltage: Double,
        unitSystem: UnitSystem,
        crossSection: Double? = null,
    ): Double {
        return current * voltageDrop(length, current, voltage, unitSystem, crossSection)
    }

    fun recommendedFuse(forCurrent: Double): Double {
        val target = ceil(forCurrent * 1.25)
        return standardFuses.firstOrNull { it >= target } ?: standardFuses.last()
    }

    private inline fun guardAgainstZero(divisor: Double, calculation: () -> Double): Double {
        if (divisor <= 0) return 0.0
        return calculation()
    }

    private fun crossSectionFromAWG(awg: Double): Double {
        val index = standardAWG.indexOf(awg.toInt())
        return if (index in awgCrossSections.indices) awgCrossSections[index] else 0.75
    }

    /** Formats an AWG value: positive as-is, negative as "X/0" notation. */
    fun formatAWG(awg: Double): String = when (awg.toInt()) {
        -1 -> "1/0"
        -2 -> "2/0"
        -3 -> "3/0"
        -4 -> "4/0"
        else -> "${awg.toInt()}"
    }
}
