// storage_botonera.rs — Persistencia de la botonera de efectos
//
// Estructura en disco:
//   $HOME/.gradio/data/botonera/config.json  (nombres de pestañas y botones)
//
// Formato JSON:
// {
//   "tabs": [
//     { "nombre": "FX1", "botones": [
//         { "ruta": "/ruta/audio.mp3", "nombre": "PISADOR01" },
//         null,  ...
//     ]}
//   ]
// }
//
// Cada pestaña tiene FILAS x COLS botones (algunos pueden ser null/vacíos)

use std::fs;
use std::path::PathBuf;

pub const FILAS: usize = 8;
pub const COLS: usize  = 5;
pub const NUM_TABS: usize = 5;
pub const NOMBRES_TABS_DEFAULT: [&str; 5] = ["FX1", "FX2", "FX3", "FX4", "FX5"];

#[derive(Debug, Clone)]
pub struct Boton {
    pub ruta: String,
    pub nombre: String,  // nombre display (sin extensión por defecto)
}

#[derive(Debug, Clone)]
pub struct TabBotonera {
    pub nombre: String,
    pub botones: Vec<Option<Boton>>,  // FILAS * COLS elementos
}

impl TabBotonera {
    pub fn nuevo(nombre: &str) -> Self {
        TabBotonera {
            nombre: nombre.to_string(),
            botones: vec![None; FILAS * COLS],
        }
    }
    pub fn idx(fila: usize, col: usize) -> usize { fila * COLS + col }
}

// ─── Rutas ─────────────────────────────────────────────────────────────────

fn ruta_config() -> PathBuf {
    dirs::home_dir()
        .unwrap_or_else(std::env::temp_dir)
        .join(".gradio").join("data").join("botonera").join("config.json")
}

pub fn crear_directorios() {
    if let Some(dir) = ruta_config().parent() {
        let _ = fs::create_dir_all(dir);
    }
}

// ─── Serialización manual (sin serde para mantener dependencias mínimas) ───

pub fn cargar() -> Vec<TabBotonera> {
    let ruta = ruta_config();
    if !ruta.exists() {
        return NOMBRES_TABS_DEFAULT.iter()
            .map(|n| TabBotonera::nuevo(n))
            .collect();
    }
    match fs::read_to_string(&ruta) {
        Ok(contenido) => parsear_json(&contenido),
        Err(_) => NOMBRES_TABS_DEFAULT.iter()
            .map(|n| TabBotonera::nuevo(n))
            .collect(),
    }
}

pub fn guardar(tabs: &[TabBotonera]) {
    let ruta = ruta_config();
    if let Some(dir) = ruta.parent() {
        let _ = fs::create_dir_all(dir);
    }
    let json = serializar_json(tabs);
    let _ = fs::write(&ruta, json);
}

fn serializar_json(tabs: &[TabBotonera]) -> String {
    let mut s = String::from("{\n  \"tabs\": [\n");
    for (ti, tab) in tabs.iter().enumerate() {
        s.push_str(&format!("    {{\"nombre\": {}, \"botones\": [\n",
            json_str(&tab.nombre)));
        for (bi, boton) in tab.botones.iter().enumerate() {
            match boton {
                None => s.push_str("      null"),
                Some(b) => s.push_str(&format!("      {{\"ruta\": {}, \"nombre\": {}}}",
                    json_str(&b.ruta), json_str(&b.nombre))),
            }
            if bi + 1 < tab.botones.len() { s.push_str(",\n"); } else { s.push('\n'); }
        }
        s.push_str("    ]}");
        if ti + 1 < tabs.len() { s.push_str(",\n"); } else { s.push('\n'); }
    }
    s.push_str("  ]\n}");
    s
}

fn json_str(s: &str) -> String {
    format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"").replace('\n', "\\n"))
}

fn parsear_json(json: &str) -> Vec<TabBotonera> {
    // Parser minimalista — suficiente para nuestro formato conocido
    let mut tabs: Vec<TabBotonera> = Vec::new();
    // Buscar objetos de tab: {"nombre": "...", "botones": [...]}
    let mut pos = 0;
    let _bytes = json.as_bytes();

    while let Some(tab_start) = find_str(json, "{\"nombre\":", pos) {
        // Extraer nombre del tab
        let nombre = extract_string_after(json, "\"nombre\":", tab_start)
            .unwrap_or_else(|| "FX".to_string());
        // Extraer array de botones
        let arr_start = find_str(json, "\"botones\":", tab_start)
            .and_then(|p| find_char(json, '[', p))
            .unwrap_or(tab_start);

        let mut tab = TabBotonera::nuevo(&nombre);
        let mut bpos = arr_start + 1;
        let mut bi = 0;

        while bi < FILAS * COLS {
            // Saltar espacios y comas
            while bpos < json.len() && (json.as_bytes()[bpos] == b' ' ||
                  json.as_bytes()[bpos] == b'\n' || json.as_bytes()[bpos] == b',' ||
                  json.as_bytes()[bpos] == b'\r') {
                bpos += 1;
            }
            if bpos >= json.len() { break; }
            if json.as_bytes()[bpos] == b']' { break; }

            if json[bpos..].starts_with("null") {
                tab.botones[bi] = None;
                bpos += 4;
            } else if json.as_bytes()[bpos] == b'{' {
                let ruta = extract_string_after(json, "\"ruta\":", bpos)
                    .unwrap_or_default();
                let nombre_b = extract_string_after(json, "\"nombre\":", bpos)
                    .unwrap_or_else(|| nombre_desde_ruta(&ruta));
                tab.botones[bi] = Some(Boton { ruta, nombre: nombre_b });
                // Avanzar hasta el } de cierre
                if let Some(end) = find_char(json, '}', bpos) {
                    bpos = end + 1;
                } else { break; }
            } else {
                bpos += 1;
            }
            bi += 1;
        }
        tabs.push(tab);
        pos = tab_start + 1;
        if tabs.len() >= NUM_TABS { break; }
    }

    // Completar tabs faltantes
    while tabs.len() < NUM_TABS {
        tabs.push(TabBotonera::nuevo(NOMBRES_TABS_DEFAULT[tabs.len()]));
    }
    tabs
}

fn find_str(s: &str, pat: &str, from: usize) -> Option<usize> {
    s[from..].find(pat).map(|p| p + from)
}
fn find_char(s: &str, ch: char, from: usize) -> Option<usize> {
    s[from..].find(ch).map(|p| p + from)
}
fn extract_string_after(s: &str, key: &str, from: usize) -> Option<String> {
    let key_pos = find_str(s, key, from)?;
    let after = &s[key_pos + key.len()..];
    let q1 = after.find('"')? + 1;
    let rest = &after[q1..];
    let mut result = String::new();
    let mut chars = rest.chars();
    while let Some(c) = chars.next() {
        if c == '"' { break; }
        if c == '\\' {
            match chars.next() {
                Some('"') => result.push('"'),
                Some('\\') => result.push('\\'),
                Some('n') => result.push('\n'),
                Some(other) => { result.push('\\'); result.push(other); }
                None => break,
            }
        } else {
            result.push(c);
        }
    }
    Some(result)
}

pub fn nombre_desde_ruta(ruta: &str) -> String {
    std::path::Path::new(ruta)
        .file_stem()
        .and_then(|s| s.to_str())
        .unwrap_or(ruta)
        .to_string()
}
