// SPDX-FileCopyrightText: 2026 Reactorcoremeltdown
// SPDX-License-Identifier: GPL-3.0-or-later

package space.rcmd.android.sizzle.input

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.first

/** The single DataStore file backing all persisted input bindings. */
private val Context.bindingDataStore: DataStore<Preferences> by preferencesDataStore(name = "input_bindings")

/**
 * Persists the three user-editable binding maps — keyboard hotkeys, gamepad
 * buttons/chords, and MIDI CCs — so custom bindings survive an app restart.
 *
 * Each device kind is stored as one small text blob (one `key=actionId` line
 * apiece) in a Preferences [DataStore]. The in-memory maps on the input handlers
 * remain the source of truth at runtime: [loadInto] seeds them once at startup
 * and [save] rewrites the blobs after any edit. Only a device kind that has
 * actually been stored replaces its defaults, so a fresh install keeps the
 * built-in layout untouched.
 */
class BindingStore(private val context: Context) {

    /** Overwrite each handler's binding map with the persisted one, if present.
     *  Call once at startup, before any hardware event can arrive. */
    suspend fun loadInto(keyboard: KeyboardInput, gamepad: GamepadInput, midi: MidiInput) {
        val prefs = context.bindingDataStore.data.first()
        prefs[KEYBOARD_KEY]?.let { keyboard.bindings = decodeIntMap(it) }
        prefs[MIDI_KEY]?.let { midi.ccBindings = decodeIntMap(it) }
        prefs[GAMEPAD_KEY]?.let { gamepad.bindings = decodeChordMap(it) }
    }

    /** Snapshot all three binding maps to disk. Call after any binding change. */
    suspend fun save(keyboard: KeyboardInput, gamepad: GamepadInput, midi: MidiInput) {
        context.bindingDataStore.edit { prefs ->
            prefs[KEYBOARD_KEY] = encodeIntMap(keyboard.bindings)
            prefs[MIDI_KEY] = encodeIntMap(midi.ccBindings)
            prefs[GAMEPAD_KEY] = encodeChordMap(gamepad.bindings)
        }
    }

    // ---- (de)serialization: one "key=actionId" line per entry ----

    private fun encodeIntMap(m: Map<Int, String>): String =
        m.entries.joinToString("\n") { "${it.key}=${it.value}" }

    private fun decodeIntMap(s: String): MutableMap<Int, String> {
        val out = mutableMapOf<Int, String>()
        for (line in s.lineSequence()) {
            val eq = line.indexOf('=')
            if (eq <= 0) continue
            val code = line.substring(0, eq).toIntOrNull() ?: continue
            out[code] = line.substring(eq + 1)
        }
        return out
    }

    // Gamepad keys serialize as "modifier:code=actionId" (modifier 0 == none).
    private fun encodeChordMap(m: Map<GamepadChord, String>): String =
        m.entries.joinToString("\n") { "${it.key.modifier}:${it.key.code}=${it.value}" }

    private fun decodeChordMap(s: String): MutableMap<GamepadChord, String> {
        val out = mutableMapOf<GamepadChord, String>()
        for (line in s.lineSequence()) {
            val eq = line.indexOf('=')
            if (eq <= 0) continue
            val chord = line.substring(0, eq)
            val colon = chord.indexOf(':')
            if (colon <= 0) continue
            val mod = chord.substring(0, colon).toIntOrNull() ?: continue
            val code = chord.substring(colon + 1).toIntOrNull() ?: continue
            out[GamepadChord(mod, code)] = line.substring(eq + 1)
        }
        return out
    }

    private companion object {
        val KEYBOARD_KEY = stringPreferencesKey("keyboard")
        val GAMEPAD_KEY = stringPreferencesKey("gamepad")
        val MIDI_KEY = stringPreferencesKey("midi")
    }
}
