package com.laseroptek.raman.utils.slider

import android.content.res.Configuration
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import com.laseroptek.raman.utils.ext.px
import timber.log.Timber
import kotlin.math.abs

@Composable
fun <T : Number> LabeledRangeSlider(
    selectedUpperBound: T,
    steps: List<T>,
    onRangeChanged: (upper: T) -> Unit, // Called during drag for live updates
    onRangeChangeFinished: ((upper: T) -> Unit)? = null, // Called when touch is released
    modifier: Modifier = Modifier,
    sliderConfig: SliderConfiguration = SliderConfiguration()
) {
    require(steps.size > 0) { "List of steps has to be at least of size 1" }

    var touchInteractionState by remember { mutableStateOf<TouchInteraction>(TouchInteraction.NoInteraction) }
    var moveSelector by remember { mutableStateOf(false) }
    var composableSize by remember { mutableStateOf(IntSize(0, 0)) }
    val currentDensity = LocalDensity.current

    val heightInDp = remember(key1 = sliderConfig, key2 = currentDensity) {
        // Calculate height in Dp for the Modifier.height
        with(currentDensity) {
            (sliderConfig.touchCircleRadius.toPx() * 2 + sliderConfig.textSize.toPx() + sliderConfig.textOffset.toPx()).toDp()
        }
    }

    val barYCenter = remember(composableSize, sliderConfig, currentDensity) {
        composableSize.height - sliderConfig.touchCircleRadiusPx(currentDensity) // Px value
    }
    val barXStart = 0f
    val barYStart = remember(barYCenter, sliderConfig, currentDensity) {
        barYCenter - sliderConfig.barHeightPx(currentDensity) / 2 // Px value
    }
    val barWidth = remember(composableSize) {
        composableSize.width.toFloat() - 2 * barXStart // Px value
    }
    val barWidthDummy = 30.0f   // draw larger than real barWidth
    val barCornerRadius = remember(sliderConfig, currentDensity) {
        // CornerRadius expects Px values
        CornerRadius(sliderConfig.barCornerRadiusPx(currentDensity), sliderConfig.barCornerRadiusPx(currentDensity))
    }

    val (stepXCoordinates, stepSpacing) = remember(composableSize, steps, sliderConfig, currentDensity, barWidth) {
        if (composableSize.width == 0) {
            Pair(FloatArray(0), 0f)
        } else {
            calculateStepCoordinatesAndSpacing(
                numberOfSteps = steps.size,
                barXStart = barXStart,
                barWidth = barWidth,
                sliderConfig = sliderConfig,
                density = currentDensity
            )
        }
    }

    var rightCirclePosition by remember { mutableStateOf(Offset(0f, 0f)) }

    LaunchedEffect(selectedUpperBound, composableSize, steps, stepXCoordinates, barYCenter) {
        if (composableSize != IntSize(0,0) && steps.isNotEmpty() && stepXCoordinates.isNotEmpty()) {
            val upperBoundIdx = steps.indexOf(selectedUpperBound)
            if (upperBoundIdx != -1 && upperBoundIdx < stepXCoordinates.size) {
                rightCirclePosition = Offset(stepXCoordinates[upperBoundIdx], barYCenter)
            } else {
                val closestStepIndex = steps.indices.minByOrNull { abs(steps[it].toFloat() - selectedUpperBound.toFloat()) } ?: 0
                if (closestStepIndex < stepXCoordinates.size) {
                    rightCirclePosition = Offset(stepXCoordinates[closestStepIndex], barYCenter)
                }
            }
        }
    }

    Canvas(
        modifier = modifier
            .height(heightInDp) // Use Dp for layout height
            .onSizeChanged {
                composableSize = it
            }
            .touchInteraction(remember { MutableInteractionSource() }) {
                touchInteractionState = it
            }
    ) {
        if (stepXCoordinates.isEmpty() || (rightCirclePosition == Offset(0f,0f) && composableSize == IntSize(0,0))) {
            return@Canvas
        }

        drawRoundRect(
            color = sliderConfig.barColor,
            topLeft = Offset(barXStart, barYStart),
            size = Size(barWidth + barWidthDummy, sliderConfig.barHeightPx(this)), // 'this' is DrawScope (Density)
            cornerRadius = barCornerRadius // Already in Px
        )

        drawRect(
            color = sliderConfig.barColorInRange,
            topLeft = Offset(barXStart, barYStart),
            size = Size(rightCirclePosition.x - barXStart, sliderConfig.barHeightPx(this)) // 'this' is DrawScope
        )

        drawStepMarkersAndLabels(
            steps = steps,
            stepXCoordinates = stepXCoordinates,
            rightCirclePosition = rightCirclePosition,
            barYCenter = barYCenter,
            sliderConfig = sliderConfig,
            density = this // Pass DrawScope's density
        )

        drawCircleWithShadow(
            position = rightCirclePosition,
            touched = moveSelector,
            sliderConfig = sliderConfig,
            density = this // Pass DrawScope's density
        )
    }

    handleTouch(
        leftCirclePosition = barXStart,
        rightCirclePosition = rightCirclePosition,
        moveSelector = moveSelector,
        stepXCoordinates = stepXCoordinates,
        stepSpacing = stepSpacing,
        touchInteraction = touchInteractionState,
        updateSelector = { position, move ->
            val (_, closestIndex) = stepXCoordinates.getClosestNumber(position.x)
            if (closestIndex >= 0 && closestIndex < steps.size) {
                onRangeChanged(steps[closestIndex])
            }
            moveSelector = move
        },
        onTouchInteractionChanged = { touchInteractionState = it },
        onRangeIdxChanged = { upperBoundIdx ->
            if (upperBoundIdx >= 0 && upperBoundIdx < steps.size) {
                onRangeChanged(steps[upperBoundIdx])
                onRangeChangeFinished?.invoke(steps[upperBoundIdx])
            }
        }
    )
}

private fun calculateStepCoordinatesAndSpacing(
    numberOfSteps: Int,
    barXStart: Float,
    barWidth: Float,
    sliderConfig: SliderConfiguration,
    density: Density
): Pair<FloatArray, Float> {
    if (numberOfSteps <= 0) return Pair(FloatArray(0), 0f)
    if (numberOfSteps == 1) {
        val singleStepX = barXStart + barWidth / 2
        return floatArrayOf(singleStepX) to 0f
    }
    // val stepMarkerRadius = sliderConfig.stepMarkerRadiusPx(density) // Not directly used for spacing here
    val stepSpacing = barWidth / (numberOfSteps - 1)

    val stepXCoordinates = generateSequence(barXStart) { it + stepSpacing }
        .take(numberOfSteps)
        .toList()

    return stepXCoordinates.toFloatArray() to stepSpacing
}

private fun DrawScope.drawCircleWithShadow(
    position: Offset,
    touched: Boolean,
    sliderConfig: SliderConfiguration,
    density: Density // Parameter name matches, but DrawScope is already a Density
) {
    val touchAddition = if (touched) {
        sliderConfig.touchCircleShadowTouchedSizeAdditionPx(density)
    } else {
        0f
    }

    drawIntoCanvas {
        val paint = androidx.compose.ui.graphics.Paint()
        val frameworkPaint = paint.asFrameworkPaint()
        frameworkPaint.color = sliderConfig.touchCircleColor.toArgb()
        frameworkPaint.setShadowLayer(
            sliderConfig.touchCircleShadowSizePx(density) + touchAddition,
            0f,
            0f,
            Color.DarkGray.toArgb()
        )
        it.drawCircle(
            position,
            sliderConfig.touchCircleRadiusPx(density),
            paint
        )
    }
}

private fun <T> DrawScope.drawStepMarkersAndLabels(
    steps: List<T>,
    stepXCoordinates: FloatArray,
    rightCirclePosition: Offset,
    barYCenter: Float,
    sliderConfig: SliderConfiguration,
    density: Density // Parameter name matches, but DrawScope is already a Density
) {
    if (steps.size != stepXCoordinates.size) {
        Timber.e("Step value size and step coordinate size do not match. Value size: ${steps.size}, Coordinate size: ${stepXCoordinates.size}")
        return
    }
    val stepMarkerRadius = sliderConfig.stepMarkerRadiusPx(density)
    sliderConfig.textSizePx(density)
    val textOffsetY = with(density) { sliderConfig.textOffset.toPx() }

    steps.forEachIndexed { index, step ->
        if (index >= stepXCoordinates.size) return@forEachIndexed
        val stepMarkerCenter = Offset(stepXCoordinates[index], barYCenter)

        val isCurrentlySelectedBySelectorCircle =
            (rightCirclePosition.x >= (stepMarkerCenter.x - stepMarkerRadius / 2)) &&
                    (rightCirclePosition.x < (stepMarkerCenter.x + stepMarkerRadius / 2))

        val paint = when {
            isCurrentlySelectedBySelectorCircle -> sliderConfig.textSelectedPaint(density)
            stepMarkerCenter.x > rightCirclePosition.x -> sliderConfig.textOutOfRangePaint(density)
            else -> sliderConfig.textInRangePaint(density)
        }

        drawCircle(
            color = sliderConfig.stepMarkerColor,
            radius = stepMarkerRadius,
            alpha = .1f,
            center = stepMarkerCenter
        )

        drawIntoCanvas {
            /*
            val stepText = step.toString().let { text ->
                if (text.length > 3) {
                    text.substring(0, text.length.coerceAtMost(2))
                } else {
                    text
                }
            }
            Timber.d("stepText: $stepText")
            */

            val stepText = "•"  // •    // ●

            // Adjust text Y position to be above the slider bar center
            val textYPosition = barYCenter - stepMarkerRadius - textOffsetY // Adjust based on needs
            it.nativeCanvas.drawText(
                stepText,
                stepMarkerCenter.x - (paint.measureText(stepText) / 2), // Center text horizontally
                textYPosition,
                paint
            )
        }
    }
}

private fun handleTouch(
    leftCirclePosition: Float,
    rightCirclePosition: Offset,
    moveSelector: Boolean,
    stepXCoordinates: FloatArray,
    stepSpacing: Float,
    touchInteraction: TouchInteraction,
    updateSelector: (Offset, Boolean) -> Unit,
    onTouchInteractionChanged: (TouchInteraction) -> Unit,
    onRangeIdxChanged: (Int) -> Unit
) {
    if (stepXCoordinates.isEmpty()) return

    when (touchInteraction) {
        is TouchInteraction.Move -> {
            val touchPositionX = touchInteraction.position.x
            val newSelectorX = touchPositionX.coerceIn(stepXCoordinates.first(), stepXCoordinates.last())
            val newPosition = rightCirclePosition.copy(x = newSelectorX)
            updateSelector(newPosition, true)
        }
        is TouchInteraction.Up   -> {
            val (closestSelectorValue, closestSelectorIndex) = stepXCoordinates.getClosestNumber(rightCirclePosition.x)
            val finalPosition = rightCirclePosition.copy(x = closestSelectorValue)
            updateSelector(finalPosition, false)
            if (closestSelectorIndex != -1) {
                 onRangeIdxChanged(closestSelectorIndex)
            }
            onTouchInteractionChanged(TouchInteraction.NoInteraction)
        }
        else -> {
            // NoInteraction or Down.
        }
    }
}

private fun FloatArray.getClosestNumber(input: Float): Pair<Float, Int> {
    if (this.isEmpty()) return 0f to -1

    var minElem = this[0]
    var minValue = abs(minElem - input)
    var minIdx = 0

    for (i in 0..lastIndex) {
        val e = this[i]
        val v = abs(e - input)
        if (v < minValue) {
            minElem = e
            minValue = v
            minIdx = i
        } else if (v == minValue) {
            if (abs(e - input) < abs(minElem - input)) {
                minElem = e
                minIdx = i
            }
        }
    }
    return minElem to minIdx
}

@Preview(
    apiLevel = 34,
    uiMode = Configuration.UI_MODE_NIGHT_NO,
    showBackground = false,
    device = "spec:width=640dp,height=240dp,dpi=150,isRound=false,chinSize=0dp,orientation=landscape"
)
@Composable
fun CustomSliderPreview() {
    Row(
        modifier = Modifier
            .fillMaxSize()
            .padding(34.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        LabeledRangeSlider(
            selectedUpperBound = 5,
            steps = (0..10).step(1).toList(),
            onRangeChanged = {},
            onRangeChangeFinished = {},
            modifier = Modifier
                .fillMaxHeight()
                .width(260.px.dp)
                .padding(start = 16.dp, end = 16.dp)
        )
    }
}
