package com.erwan.revwatch.model

import com.erwan.revwatch.model.face.FaceStore

/**
 * Extra data binding for multi-value gauges.
 * Each binding maps a WidgetType to a set of DSL variables named by key
 * (e.g. $value-oil-temp, $progress-oil-temp).
 */
data class DataBinding(
    val widgetType: WidgetType,
    val unitKey: String = "",
    val range: GaugeRange? = null
) {
    val unitOption: UnitOption get() = UnitOption.fromKey(widgetType, unitKey.ifEmpty { null })
    val resolvedRange: GaugeRange get() = range ?: GaugeRange.defaultFor(widgetType)

    fun serialize(): String = listOf(
        widgetType.key,
        unitKey,
        range?.serialize() ?: ""
    ).joinToString("~")

    companion object {
        fun deserialize(s: String): DataBinding? {
            if (s.isBlank()) return null
            val parts = s.split("~", limit = 3)
            val widget = WidgetType.fromKey(parts[0]) ?: return null
            val unitKey = parts.getOrNull(1) ?: ""
            val range = parts.getOrNull(2)?.let { GaugeRange.deserialize(it) }
            return DataBinding(widget, unitKey, range)
        }
    }
}

data class CellConfig(
    val widgetType: WidgetType,
    val faceId: String,
    val unitKey: String = "",
    val range: GaugeRange? = null,
    val extraBindings: List<DataBinding> = emptyList()
) {
    val unitOption: UnitOption get() = UnitOption.fromKey(widgetType, unitKey.ifEmpty { null })
    val resolvedRange: GaugeRange get() = range ?: GaugeRange.defaultFor(widgetType)

    /** All widget types used by this cell (primary + extras). */
    val allWidgetTypes: List<WidgetType>
        get() = buildList {
            add(widgetType)
            extraBindings.forEach { add(it.widgetType) }
        }

    fun serialize(): String {
        val base = listOf(
            widgetType.key,
            faceId,
            unitKey,
            range?.serialize() ?: ""
        ).joinToString(":")
        if (extraBindings.isEmpty()) return base
        val bindingsStr = extraBindings.joinToString(";") { it.serialize() }
        return "$base:[$bindingsStr]"
    }

    companion object {
        fun deserialize(s: String): CellConfig? {
            if (s.isBlank()) return null
            val parts = s.split(":", limit = 6)
            if (parts.size < 2) return null
            val widget = WidgetType.fromKey(parts[0]) ?: return null
            val secondField = parts[1]

            val faceId: String
            val unitKey: String
            val range: GaugeRange?
            var extraBindings: List<DataBinding> = emptyList()

            if (secondField in FaceStore.STYLE_MIGRATION || secondField == "custom") {
                // Old format: widgetType:gaugeStyle:unitKey:range[:faceId]
                faceId = parts.getOrNull(4)?.ifBlank { null }
                    ?: FaceStore.STYLE_MIGRATION[secondField]
                    ?: FaceStore.defaultFaceFor(widget)
                unitKey = parts.getOrNull(2) ?: ""
                range = parts.getOrNull(3)?.let { GaugeRange.deserialize(it) }
            } else {
                // New format: widgetType:faceId:unitKey:range[:extraBindings]
                faceId = secondField.ifBlank { FaceStore.defaultFaceFor(widget) }
                unitKey = parts.getOrNull(2) ?: ""
                range = parts.getOrNull(3)?.let { GaugeRange.deserialize(it) }

                // Parse extra bindings if present: [binding1;binding2;...]
                val bindingsField = parts.getOrNull(4)
                if (bindingsField != null && bindingsField.startsWith("[") && bindingsField.endsWith("]")) {
                    val inner = bindingsField.substring(1, bindingsField.length - 1)
                    if (inner.isNotBlank()) {
                        extraBindings = inner.split(";").mapNotNull { DataBinding.deserialize(it) }
                    }
                }
            }

            return CellConfig(widget, faceId, unitKey, range, extraBindings)
        }
    }
}

data class DashPage(
    val columns: Int = 1,
    val rows: Int = 1,
    val cells: List<CellConfig?> = List(columns * rows) { null }
) {
    val cellCount: Int get() = columns * rows

    fun withLayout(newCols: Int, newRows: Int): DashPage {
        val newSize = newCols * newRows
        val newCells = MutableList<CellConfig?>(newSize) { i ->
            cells.getOrNull(i)
        }
        return DashPage(newCols, newRows, newCells)
    }

    fun withCell(index: Int, config: CellConfig?): DashPage {
        val newCells = cells.toMutableList()
        if (index in newCells.indices) {
            newCells[index] = config
        }
        return copy(cells = newCells)
    }

    fun serialize(): String {
        val header = "$columns,$rows"
        val cellsStr = cells.joinToString("|") { it?.serialize() ?: "" }
        return "$header|$cellsStr"
    }

    companion object {
        val LAYOUTS = listOf(1 to 1, 2 to 1, 1 to 2, 2 to 2)

        fun nextLayout(cols: Int, rows: Int): Pair<Int, Int> {
            val idx = LAYOUTS.indexOf(cols to rows)
            return LAYOUTS[(idx + 1) % LAYOUTS.size]
        }

        fun layoutLabel(cols: Int, rows: Int): String = "${cols}×${rows}"

        fun deserialize(s: String): DashPage? {
            val parts = s.split("|")
            if (parts.isEmpty()) return null
            val header = parts[0].split(",")
            if (header.size != 2) return null
            val cols = header[0].toIntOrNull() ?: return null
            val rows = header[1].toIntOrNull() ?: return null
            val cells = (0 until cols * rows).map { i ->
                CellConfig.deserialize(parts.getOrElse(i + 1) { "" })
            }
            return DashPage(cols, rows, cells)
        }
    }
}
