package fn.compose.ui

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import fn.compose.theme.FnColors

// ---------------------------------------------------------------------------
// Data class
// ---------------------------------------------------------------------------

/** A single bar in [FnBarChart]. */
data class FnBarItem(val label: String, val value: Float)

// ---------------------------------------------------------------------------
// FnLineChart
// ---------------------------------------------------------------------------

/**
 * Canvas-based line chart. Draws a polyline connecting [data] points
 * normalised to [min, max], plus a semi-transparent fill below the line.
 *
 * Handles empty lists and single-point lists without crashing.
 */
@Composable
fun FnLineChart(
    data: List<Float>,
    modifier: Modifier = Modifier,
) {
    val primaryColor = FnColors.primary
    val fillColor = FnColors.primary.copy(alpha = 0.15f)

    Canvas(
        modifier = Modifier
            .fillMaxWidth()
            .height(160.dp)
            .then(modifier),
    ) {
        if (data.size < 2) return@Canvas

        val minVal = data.min()
        val maxVal = data.max()
        val range = (maxVal - minVal).takeIf { it > 0f } ?: 1f

        val strokePx = 2.dp.toPx()
        val points = buildOffsets(data, minVal, range, size.width, size.height)

        // --- fill path ---
        val fillPath = Path().apply {
            moveTo(points.first().x, size.height)
            points.forEach { lineTo(it.x, it.y) }
            lineTo(points.last().x, size.height)
            close()
        }
        drawPath(fillPath, color = fillColor, style = Fill)

        // --- polyline ---
        drawPolyline(points, primaryColor, strokePx)
    }
}

// ---------------------------------------------------------------------------
// FnBarChart
// ---------------------------------------------------------------------------

/**
 * Canvas-based vertical bar chart. Bars are drawn proportional to the
 * maximum value in [data]. Labels are rendered below each bar using
 * [Text] (no FnText dependency to keep Charts.kt standalone).
 *
 * Handles empty lists without crashing.
 */
@Composable
fun FnBarChart(
    data: List<FnBarItem>,
    modifier: Modifier = Modifier,
) {
    if (data.isEmpty()) return

    val primaryColor = FnColors.primary
    val labelColor = FnColors.textMuted
    val maxVal = data.maxOf { it.value }.takeIf { it > 0f } ?: 1f
    val barGapDp: Dp = 4.dp

    Column(modifier = modifier) {
        // --- bar canvas ---
        Canvas(
            modifier = Modifier
                .fillMaxWidth()
                .height(160.dp),
        ) {
            val count = data.size
            val totalGapPx = barGapDp.toPx() * (count - 1)
            val barWidth = (size.width - totalGapPx) / count

            data.forEachIndexed { index, item ->
                val barHeight = (item.value / maxVal) * size.height
                val left = index * (barWidth + barGapDp.toPx())
                val top = size.height - barHeight

                drawRect(
                    color = primaryColor,
                    topLeft = Offset(left, top),
                    size = androidx.compose.ui.geometry.Size(barWidth, barHeight),
                )
            }
        }

        // --- labels row ---
        Row(modifier = Modifier.fillMaxWidth()) {
            data.forEach { item ->
                Text(
                    text = item.label,
                    modifier = Modifier.weight(1f),
                    color = labelColor,
                    fontSize = 10.sp,
                    textAlign = androidx.compose.ui.text.style.TextAlign.Center,
                    maxLines = 1,
                )
            }
        }
    }
}

// ---------------------------------------------------------------------------
// FnSparkline
// ---------------------------------------------------------------------------

/**
 * Compact mini line chart — no axes, no fill, no labels.
 * Ideal for embedding in KPI cards or table cells.
 *
 * Handles empty lists and single-point lists without crashing.
 */
@Composable
fun FnSparkline(
    data: List<Float>,
    modifier: Modifier = Modifier,
) {
    val primaryColor = FnColors.primary

    Canvas(
        modifier = Modifier
            .width(80.dp)
            .height(24.dp)
            .then(modifier),
    ) {
        if (data.size < 2) return@Canvas

        val minVal = data.min()
        val maxVal = data.max()
        val range = (maxVal - minVal).takeIf { it > 0f } ?: 1f

        val strokePx = 1.5.dp.toPx()
        val points = buildOffsets(data, minVal, range, size.width, size.height)
        drawPolyline(points, primaryColor, strokePx)
    }
}

// ---------------------------------------------------------------------------
// Private helpers
// ---------------------------------------------------------------------------

/**
 * Converts a list of raw values into [Offset] positions scaled to the
 * given [canvasWidth] x [canvasHeight].
 */
private fun buildOffsets(
    data: List<Float>,
    minVal: Float,
    range: Float,
    canvasWidth: Float,
    canvasHeight: Float,
): List<Offset> {
    val count = data.size
    return data.mapIndexed { index, value ->
        val x = if (count == 1) 0f else index / (count - 1).toFloat() * canvasWidth
        val y = canvasHeight - ((value - minVal) / range) * canvasHeight
        Offset(x, y)
    }
}

/**
 * Draws connected line segments between consecutive [points] using the
 * given [color] and [strokeWidthPx].
 */
private fun DrawScope.drawPolyline(
    points: List<Offset>,
    color: Color,
    strokeWidthPx: Float,
) {
    for (i in 0 until points.lastIndex) {
        drawLine(
            color = color,
            start = points[i],
            end = points[i + 1],
            strokeWidth = strokeWidthPx,
            cap = StrokeCap.Round,
        )
    }
}
