package solutions.tretter.mindmachine.data

import android.content.Context
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import solutions.tretter.mindmachine.session.TimelineCurve
import solutions.tretter.mindmachine.session.TimelineEditorState
import solutions.tretter.mindmachine.session.TimelinePoint
import solutions.tretter.mindmachine.session.TIMELINE_DEFAULT_GRANULARITY_SEC
import solutions.tretter.mindmachine.session.TimelineSelection
import solutions.tretter.mindmachine.session.TimelineViewport
import solutions.tretter.mindmachine.session.FlashPaletteColor
import solutions.tretter.mindmachine.session.FlashSequenceRow
import solutions.tretter.mindmachine.session.defaultFlashSequence
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import org.json.JSONArray
import org.json.JSONObject

private val Context.programDataStore by preferencesDataStore("mindmachine_programs")

class UserProgramRepository(private val context: Context) {
    private object Keys {
        val programsJson = stringPreferencesKey("user_programs_json")
    }

    val userPrograms: Flow<List<UserProgramEntity>> = context.programDataStore.data.map { prefs ->
        parsePrograms(prefs[Keys.programsJson])
    }

    suspend fun upsert(program: UserProgramEntity) {
        context.programDataStore.edit { prefs ->
            val current = parsePrograms(prefs[Keys.programsJson]).toMutableList()
            val idx = current.indexOfFirst { it.id == program.id }
            if (idx >= 0) {
                current[idx] = program
            } else {
                current += program
            }
            prefs[Keys.programsJson] = toJson(current)
        }
    }

    suspend fun delete(id: String) {
        context.programDataStore.edit { prefs ->
            val current = parsePrograms(prefs[Keys.programsJson]).filterNot { it.id == id }
            prefs[Keys.programsJson] = toJson(current)
        }
    }

    private fun parsePrograms(raw: String?): List<UserProgramEntity> {
        if (raw.isNullOrBlank()) return emptyList()
        return runCatching {
            val arr = JSONArray(raw)
            buildList {
                for (i in 0 until arr.length()) {
                    val o = arr.getJSONObject(i)
                    add(
                        UserProgramEntity(
                            id = o.getString("id"),
                            name = o.getString("name"),
                            description = o.optString("description", "Custom program"),
                            timeline = parseTimeline(o.getJSONObject("timeline")),
                            sortOrder = o.optInt("sortOrder", i + 1000),
                        )
                    )
                }
            }
        }.getOrDefault(emptyList())
    }

    private fun parseTimeline(o: JSONObject): TimelineEditorState {
        fun parseCurve(name: String): TimelineCurve {
            val points = o.getJSONArray(name)
            return TimelineCurve(
                points = buildList {
                    for (i in 0 until points.length()) {
                        val p = points.getJSONObject(i)
                        add(TimelinePoint(tSec = p.getInt("t"), value01 = p.getDouble("v").toFloat()))
                    }
                }
            )
        }

        return TimelineEditorState(
            durationSec = o.getInt("durationSec"),
            curveGranularitySec = TimelineEditorState.normalizeGranularitySec(
                o.optInt("curveGranularitySec", TIMELINE_DEFAULT_GRANULARITY_SEC),
                o.getInt("durationSec")
            ),
            flashSequence = parseFlashSequence(o.optJSONArray("flashSequence")),
            selection = TimelineSelection(
                startSec = o.optInt("selectionStartSec", 0),
                endSec = o.optInt("selectionEndSec", o.getInt("durationSec")),
            ),
            viewport = TimelineViewport(
                startSec = o.optDouble("viewportStartSec", 0.0).toFloat(),
                secondsPerScreen = o.optDouble("viewportSecondsPerScreen", 600.0).toFloat(),
            ),
            activeCurve = TimelineEditorState.ActiveCurve.valueOf(o.optString("activeCurve", TimelineEditorState.ActiveCurve.VISUAL_LEFT.name)),
            visualLeft = parseCurve("visualLeft"),
            visualRight = parseCurve("visualRight"),
            audioLeft = parseCurve("audioLeft"),
            audioRight = parseCurve("audioRight"),
            isPlaying = false,
            playheadSec = 0f,
        ).setDurationSec(o.getInt("durationSec"))
    }

    private fun toJson(programs: List<UserProgramEntity>): String {
        val arr = JSONArray()
        programs.sortedBy { it.sortOrder }.forEach { program ->
            val o = JSONObject()
            o.put("id", program.id)
            o.put("name", program.name)
            o.put("description", program.description)
            o.put("sortOrder", program.sortOrder)

            val timeline = JSONObject()
            timeline.put("durationSec", program.timeline.durationSec)
            timeline.put("curveGranularitySec", program.timeline.curveGranularitySec)
            timeline.put("flashSequence", flashSequenceArray(program.timeline.flashSequence))
            timeline.put("selectionStartSec", program.timeline.selection.startSec)
            timeline.put("selectionEndSec", program.timeline.selection.endSec)
            timeline.put("viewportStartSec", program.timeline.viewport.startSec)
            timeline.put("viewportSecondsPerScreen", program.timeline.viewport.secondsPerScreen)
            timeline.put("activeCurve", program.timeline.activeCurve.name)
            timeline.put("visualLeft", pointsArray(program.timeline.visualLeft))
            timeline.put("visualRight", pointsArray(program.timeline.visualRight))
            timeline.put("audioLeft", pointsArray(program.timeline.audioLeft))
            timeline.put("audioRight", pointsArray(program.timeline.audioRight))
            o.put("timeline", timeline)

            arr.put(o)
        }
        return arr.toString()
    }

    private fun pointsArray(curve: TimelineCurve): JSONArray {
        val arr = JSONArray()
        curve.points.forEach { p ->
            arr.put(JSONObject().put("t", p.tSec).put("v", p.value01.toDouble()))
        }
        return arr
    }

    private fun parseFlashSequence(arr: JSONArray?): List<FlashSequenceRow> {
        if (arr == null || arr.length() == 0) return defaultFlashSequence()
        return buildList {
            for (i in 0 until arr.length()) {
                val row = arr.getJSONObject(i)
                add(
                    FlashSequenceRow(
                        splitScreen = row.optBoolean("splitScreen", true),
                        leftColor = row.optString("leftColor", FlashPaletteColor.RED.name).let(FlashPaletteColor::valueOf),
                        rightColor = row.optString("rightColor", FlashPaletteColor.GREEN.name).let(FlashPaletteColor::valueOf),
                    )
                )
            }
        }.ifEmpty { defaultFlashSequence() }
    }

    private fun flashSequenceArray(sequence: List<FlashSequenceRow>): JSONArray {
        val arr = JSONArray()
        sequence.forEach { row ->
            arr.put(
                JSONObject()
                    .put("splitScreen", row.splitScreen)
                    .put("leftColor", row.leftColor.name)
                    .put("rightColor", row.rightColor.name)
            )
        }
        return arr
    }
}

data class UserProgramEntity(
    val id: String,
    val name: String,
    val description: String,
    val timeline: TimelineEditorState,
    val sortOrder: Int,
)
