//! gr_reportes — G Radio Reporting Tool
//! Genera reportes PDF y CSV de audios emitidos (comerciales, parrilla, eventos).

use gtk4::prelude::*;

const ICONO_REPORTES: &[u8] = include_bytes!("../assets/reportes.png");
use gtk4::{
    Application, ApplicationWindow, Align, Box as GtkBox, Button, Calendar, CheckButton,
    Entry, Label, ListBox, ListBoxRow, Orientation, Popover, ScrolledWindow,
    CssProvider, STYLE_PROVIDER_PRIORITY_APPLICATION, Separator,
};
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::fs;
use chrono::{Datelike, Local, NaiveDate, Duration as ChronoDuration};
use grpautaje::i18n::tr;
use grpautaje::skin;

const LOGO_BYTES:  &[u8] = include_bytes!("../assets/gradio.png");
const APP_CSS: &str = r#"
/* ── Forzar tema oscuro en todos los widgets ── */
window, .background {
    background-color: #12121e;
    color: #d8e0f0;
}
box, scrolledwindow, viewport {
    background-color: #12121e;
    color: #d8e0f0;
}
listbox {
    background-color: #12121e;
    color: #d8e0f0;
}
listbox row {
    background-color: #12121e;
    color: #d8e0f0;
}
listbox row:hover {
    background-color: #1a1a2e;
}
separator {
    background-color: #2a3060;
    min-width: 1px;
    min-height: 1px;
}
label { color: #d8e0f0; }
checkbutton { color: #d8e0f0; }
popover contents {
    background-color: #1a1e38;
    color: #d8e0f0;
}
calendar {
    background-color: #1a1e38;
    color: #d8e0f0;
    border: 1px solid #3a55aa;
    border-radius: 4px;
}
calendar:selected {
    background-color: #2a4a8a;
    color: white;
}
.header-bar {
    background-color: #0e1428;
    padding: 10px 16px;
    border-bottom: 2px solid #1e3a70;
}
.title-lbl  { font-size: 17px; font-weight: bold; color: #e0eeff; }
.sub-lbl    { font-size: 11px; color: #7090c0; }
.sec-lbl    { font-size: 10px; font-weight: bold; color: #8099cc; letter-spacing: 1px; }
.date-entry {
    background-color: #1e2240;
    color: #ccd8ff;
    border: 1px solid #3a55aa;
    border-radius: 4px;
    padding: 4px 8px;
    font-size: 13px;
    min-width: 108px;
}
.date-entry:focus { border-color: #5577dd; }
checkbutton.cat { padding: 5px 10px; }
checkbutton.cat label { font-size: 13px; color: #b0c8ee; }
checkbutton.cat:checked label { color: #55aaff; font-weight: bold; }
.list-hdr {
    background-color: #1a2040;
    border-bottom: 1px solid #2a3a6a;
    padding: 4px 8px;
}
.list-hdr label { font-size: 10px; font-weight: bold; color: #7090b0; letter-spacing: 1px; }
.res-name  { font-size: 12px; color: #c8daff; }
.res-count { font-size: 12px; color: #55cc88; }
.res-dur   { font-size: 12px; color: #aabb66; }
.status-lbl { font-size: 11px; color: #778899; padding: 4px 16px; }
.btn-load {
    background-color: #1a4a80;
    color: white;
    font-weight: bold;
    border-radius: 4px;
    padding: 6px 14px;
    font-size: 12px;
}
.btn-load:hover  { background-color: #2060a0; }
.btn-pdf {
    background-color: #8b1a1a;
    color: white;
    font-weight: bold;
    border-radius: 4px;
    padding: 7px 16px;
    font-size: 12px;
}
.btn-pdf:hover { background-color: #b02020; }
.btn-pdf:disabled { background-color: #3a2020; color: #886666; }
.btn-csv {
    background-color: #1a5c30;
    color: white;
    font-weight: bold;
    border-radius: 4px;
    padding: 7px 16px;
    font-size: 12px;
}
.btn-csv:hover { background-color: #22793e; }
.btn-csv:disabled { background-color: #1a2c20; color: #557755; }
.btn-close { background-color: #383840; color: #c0c0d0; border-radius: 4px; padding: 7px 14px; font-size: 12px; }
.btn-close:hover { background-color: #484850; }
.dia-fecha  { font-size: 12px; font-weight: bold; color: #88bbff; }
.dia-total  { font-size: 12px; color: #d8e0f0; }
.dia-nac    { font-size: 12px; color: #66dd88; }
.dia-int    { font-size: 12px; color: #ff9944; }
"#;

// ── Estructura de entrada de log ──────────────────────────────────────────────

#[derive(Debug, Clone)]
struct Entrada {
    fecha:    String,   // YYYY-MM-DD
    hora:     String,   // HH:MM:SS
    nombre:   String,   // solo nombre de archivo (con extensión)
    ruta:     String,   // ruta absoluta completa (para clasificación)
    duracion: u32,      // segundos
}

// ── Parseo de archivos de log ─────────────────────────────────────────────────

fn parsear_archivo(path: &std::path::Path) -> Vec<Entrada> {
    let contenido = match fs::read_to_string(path) {
        Ok(c) => c,
        Err(_) => return vec![],
    };
    let mut entradas = vec![];
    for linea in contenido.lines() {
        let linea = linea.trim();
        if linea.is_empty() { continue; }
        // Formato: {fecha},{hora},{ruta},{dur},seg,{tipo}
        let campos: Vec<&str> = linea.splitn(6, ',').collect();
        if campos.len() < 4 { continue; }
        let fecha    = campos[0].to_string();
        let hora     = campos[1].to_string();
        let ruta     = campos[2].to_string();
        let duracion: u32 = campos[3].parse().unwrap_or(0);
        let nombre = std::path::Path::new(&ruta)
            .file_name()
            .and_then(|s| s.to_str())
            .unwrap_or(&ruta)
            .to_string();
        entradas.push(Entrada { fecha, hora, nombre, ruta, duracion });
    }
    entradas
}

// Devuelve (carpetas_nacionales, carpetas_intercultural)
fn leer_carpetas_clasificacion() -> (Vec<String>, Vec<String>) {
    let cfg_path = dirs::home_dir()
        .unwrap_or_else(|| PathBuf::from("/"))
        .join(".gradio/data/tmp/gradio.config");
    let text = fs::read_to_string(&cfg_path).unwrap_or_default();
    let mut lines = text.lines();
    // saltar líneas 1-9 (main_dev, cue_dev, station_name, crossfade, pisador x4, silence)
    for _ in 0..9 { lines.next(); }
    let nacionales = lines.next()
        .map(|l| parse_cfg_path_list(l.trim()))
        .unwrap_or_default();
    let intercultural = lines.next()
        .map(|l| parse_cfg_path_list(l.trim()))
        .unwrap_or_default();
    (nacionales, intercultural)
}

fn parse_cfg_path_list(s: &str) -> Vec<String> {
    let mut result = Vec::new();
    let mut rest = s;
    while !rest.is_empty() {
        rest = rest.trim_start();
        if rest.starts_with('"') {
            rest = &rest[1..];
            if let Some(end) = rest.find('"') {
                result.push(rest[..end].to_string());
                rest = &rest[end + 1..];
                rest = rest.trim_start_matches(';');
            } else {
                result.push(rest.to_string());
                break;
            }
        } else if let Some(end) = rest.find(';') {
            let item = rest[..end].trim().to_string();
            if !item.is_empty() { result.push(item); }
            rest = &rest[end + 1..];
        } else {
            let item = rest.trim().to_string();
            if !item.is_empty() { result.push(item); }
            break;
        }
    }
    result
}

// Clasifica una ruta: true si su directorio padre empieza por alguno de los prefijos
fn es_de_carpetas(ruta: &str, carpetas: &[String]) -> bool {
    if carpetas.is_empty() { return false; }
    let dir = std::path::Path::new(ruta)
        .parent()
        .map(|p| p.to_string_lossy().to_string())
        .unwrap_or_default();
    carpetas.iter().any(|c| dir.starts_with(c.as_str()) || ruta.starts_with(c.as_str()))
}

#[derive(Debug, Clone)]
struct ResumenDia {
    fecha:            String,
    total_seg:        u64,
    nacional_seg:     u64,
    intercultural_seg:u64,
    total_emisiones:  usize,
}

fn calcular_resumen_por_dia(
    entradas: &[Entrada],
    nacionales: &[String],
    intercultural: &[String],
) -> Vec<ResumenDia> {
    let mut mapa: BTreeMap<String, ResumenDia> = BTreeMap::new();
    for e in entradas {
        let r = mapa.entry(e.fecha.clone()).or_insert(ResumenDia {
            fecha: e.fecha.clone(),
            total_seg: 0,
            nacional_seg: 0,
            intercultural_seg: 0,
            total_emisiones: 0,
        });
        r.total_seg        += e.duracion as u64;
        r.total_emisiones  += 1;
        if es_de_carpetas(&e.ruta, nacionales) {
            r.nacional_seg += e.duracion as u64;
        }
        if es_de_carpetas(&e.ruta, intercultural) {
            r.intercultural_seg += e.duracion as u64;
        }
    }
    mapa.into_values().collect()
}

fn leer_nombre_radio() -> String {
    let cfg = dirs::home_dir()
        .unwrap_or_else(|| PathBuf::from("/"))
        .join(".gradio/data/tmp/gradio.config");
    fs::read_to_string(&cfg)
        .ok()
        .and_then(|s| {
            // Línea 1: main_dev, Línea 2: cue_dev, Línea 3: station_name
            s.lines().nth(2).map(|l| l.trim().to_string())
        })
        .filter(|s| !s.is_empty())
        .unwrap_or_else(|| "G Radio".to_string())
}

fn cargar_entradas(categoria: &str, desde: &str, hasta: &str) -> Vec<Entrada> {
    let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("/"));
    let dir  = home.join(".gradio/data/reporte");

    let prefijo = match categoria {
        "Comerciales" => "GR6-comercial-",
        "Parrilla"    => "GR6-parrilla-",
        "Eventos"     => "GR6-evento-",
        _             => return vec![],
    };

    let desde_d = NaiveDate::parse_from_str(desde, "%Y-%m-%d").ok();
    let hasta_d = NaiveDate::parse_from_str(hasta, "%Y-%m-%d").ok();

    let mut todas = vec![];
    if let Ok(iter) = fs::read_dir(&dir) {
        let mut archivos: Vec<_> = iter.flatten().collect();
        archivos.sort_by_key(|e| e.file_name());
        for ent in archivos {
            let fname = ent.file_name();
            let fname = fname.to_string_lossy();
            if !fname.starts_with(prefijo) || !fname.ends_with(".txt") { continue; }
            let fecha_str = &fname[prefijo.len()..fname.len()-4];
            if let Ok(fd) = NaiveDate::parse_from_str(fecha_str, "%Y-%m-%d") {
                if desde_d.map(|d| fd < d).unwrap_or(false) { continue; }
                if hasta_d.map(|h| fd > h).unwrap_or(false) { continue; }
            }
            let mut e = parsear_archivo(&ent.path());
            todas.append(&mut e);
        }
    }
    todas
}

// ── Agrupación: nombre → (dur_secs, fecha → [HH:MM, ...]) ─────────────────────

type Agrupado = BTreeMap<String, (u32, BTreeMap<String, Vec<String>>)>;

fn agrupar(entradas: &[Entrada]) -> Agrupado {
    let mut mapa: Agrupado = BTreeMap::new();
    for e in entradas {
        let hhmm = e.hora.chars().take(5).collect::<String>();
        let ent  = mapa.entry(e.nombre.clone()).or_insert((e.duracion, BTreeMap::new()));
        ent.1.entry(e.fecha.clone()).or_default().push(hhmm);
    }
    mapa
}

fn dur_hms(seg: u64) -> String {
    let h = seg / 3600;
    let m = (seg % 3600) / 60;
    let s = seg % 60;
    if h > 0 { format!("{}h {:02}m {:02}s", h, m, s) }
    else      { format!("{}m {:02}s", m, s) }
}

// ── Generación de PDF con Cairo ───────────────────────────────────────────────

fn generar_pdf(
    entradas: &[Entrada],
    categoria: &str,
    desde: &str,
    hasta: &str,
    destino: &std::path::Path,
) -> Result<(), Box<dyn std::error::Error>> {
    let nombre_radio = leer_nombre_radio();
    use cairo::{Context, FontSlant, FontWeight, PdfSurface};

    // A4 en puntos (72 dpi)
    let pw = 595.0_f64;
    let ph = 842.0_f64;
    let ml = 48.0_f64;   // margen izquierdo
    let mr = 48.0_f64;   // margen derecho
    let cw = pw - ml - mr;
    let mt = 84.0_f64;   // altura del header
    let mb = 44.0_f64;   // margen inferior

    let surface = PdfSurface::new(pw, ph, destino)?;
    let cr = Context::new(&surface)?;

    let agrupado  = agrupar(entradas);
    let tot_tit   = agrupado.len();
    let tot_emis  = entradas.len();

    let mut pagina = 1u32;
    let mut y      = mt;

    // ── Dibuja la cabecera de página ──────────────────────────────────────────
    fn cabecera(
        cr: &Context,
        pw: f64, ml: f64, mr: f64, mt: f64,
        pagina: u32, categoria: &str, desde: &str, hasta: &str,
        nombre_radio: &str,
    ) {
        // Fondo del header
        cr.set_source_rgb(0.07, 0.10, 0.22);
        cr.rectangle(0.0, 0.0, pw, mt - 8.0);
        let _ = cr.fill();

        // Franja azul lateral izquierda
        cr.set_source_rgb(0.15, 0.42, 0.78);
        cr.rectangle(ml - 6.0, 6.0, 4.0, mt - 16.0);
        let _ = cr.fill();

        // Logo G-Radio (PNG embebido)
        if let Ok(logo) = cairo::ImageSurface::create_from_png(
            &mut std::io::Cursor::new(skin::icono("gradio.png", LOGO_BYTES))
        ) {
            let lh = logo.height() as f64;
            let target = (mt - 16.0).min(60.0);
            let scale  = target / lh;
            cr.save().ok();
            cr.translate(ml + 2.0, (mt - 8.0 - target) / 2.0);
            cr.scale(scale, scale);
            cr.set_source_surface(&logo, 0.0, 0.0).ok();
            cr.paint().ok();
            cr.restore().ok();
        }

        // Título
        cr.set_source_rgb(0.88, 0.94, 1.0);
        cr.select_font_face("Sans", FontSlant::Normal, FontWeight::Bold);
        cr.set_font_size(14.5);
        cr.move_to(ml + 70.0, 32.0);
        let _ = cr.show_text("G Radio — Reporting Tool");

        // Subtítulo
        cr.set_source_rgb(0.55, 0.70, 0.92);
        cr.select_font_face("Sans", FontSlant::Normal, FontWeight::Normal);
        cr.set_font_size(9.0);
        cr.move_to(ml + 70.0, 46.0);
        let _ = cr.show_text(&format!(
            "Audios Emitidos  ·  {}  ·  {} al {}", categoria, desde, hasta
        ));

        // Nombre de la radio
        cr.set_source_rgb(0.80, 0.55, 1.0);
        cr.select_font_face("Sans", FontSlant::Normal, FontWeight::Bold);
        cr.set_font_size(8.0);
        cr.move_to(ml + 70.0, 58.0);
        let _ = cr.show_text(nombre_radio);

        // Número de página
        cr.set_source_rgb(0.55, 0.65, 0.82);
        cr.set_font_size(8.5);
        let pg = format!("Pág. {}", pagina);
        if let Ok(te) = cr.text_extents(&pg) {
            cr.move_to(pw - mr - te.width(), 38.0);
        }
        let _ = cr.show_text(&format!("Pág. {}", pagina));

        // Línea divisora
        cr.set_source_rgb(0.20, 0.46, 0.80);
        cr.set_line_width(1.0);
        cr.move_to(ml, mt - 8.0);
        cr.line_to(pw - mr, mt - 8.0);
        let _ = cr.stroke();
    }

    // Dibuja la primera página
    cabecera(&cr, pw, ml, mr, mt, pagina, categoria, desde, hasta, &nombre_radio);

    let mut fila_idx = 0usize;
    for (nombre, (dur_secs, fechas)) in &agrupado {
        let tot_plays: usize = fechas.values().map(|v| v.len()).sum();
        let dur_total = *dur_secs as u64 * tot_plays as u64;

        // Espacio mínimo: banda título (18) + al menos 1 fila fecha (13) + separador (8)
        let min_needed = 18.0 + 13.0 + 8.0;
        if y + min_needed > ph - mb {
            cr.show_page().ok();
            pagina += 1;
            cabecera(&cr, pw, ml, mr, mt, pagina, categoria, desde, hasta, &nombre_radio);
            y = mt;
        }

        // ── Banda de nombre del audio ──────────────────────────────────────
        let bg_h = if fila_idx % 2 == 0 { 0.11 } else { 0.13 };
        cr.set_source_rgb(bg_h, bg_h + 0.03, bg_h + 0.12);
        cr.rectangle(ml, y, cw, 18.0);
        cr.fill().ok();

        // Nombre (negrita, blanco)
        cr.set_source_rgb(0.92, 0.96, 1.0);
        cr.select_font_face("Sans", FontSlant::Normal, FontWeight::Bold);
        cr.set_font_size(10.0);
        cr.move_to(ml + 4.0, y + 13.0);
        let max_chars = 68;
        let nombre_display = if nombre.len() > max_chars {
            format!("{}…", &nombre[..max_chars])
        } else {
            nombre.clone()
        };
        cr.show_text(&nombre_display).ok();

        // Derecha: resumen
        cr.set_source_rgb(0.45, 0.82, 0.55);
        cr.select_font_face("Sans", FontSlant::Normal, FontWeight::Normal);
        cr.set_font_size(8.5);
        let resumen = format!(
            "{}  emisiones  ·  {}s c/u  ·  {}",
            tot_plays, dur_secs, dur_hms(dur_total)
        );
        if let Ok(te) = cr.text_extents(&resumen) {
            cr.move_to(pw - mr - te.width() - 4.0, y + 12.5);
        }
        cr.show_text(&resumen).ok();
        y += 18.0;

        // ── Filas de fechas ────────────────────────────────────────────────
        for (fi, (fecha, horas)) in fechas.iter().enumerate() {
            if y + 13.0 > ph - mb {
                cr.show_page().ok();
                pagina += 1;
                cabecera(&cr, pw, ml, mr, mt, pagina, categoria, desde, hasta, &nombre_radio);
                y = mt;
                // Banner de continuación
                cr.set_source_rgb(0.08, 0.12, 0.28);
                cr.rectangle(ml, y, cw, 13.0);
                cr.fill().ok();
                cr.set_source_rgb(0.55, 0.72, 0.95);
                cr.select_font_face("Sans", FontSlant::Normal, FontWeight::Bold);
                cr.set_font_size(8.0);
                cr.move_to(ml + 4.0, y + 9.5);
                cr.show_text(&format!("{} (cont.)", nombre_display)).ok();
                y += 14.0;
            }

            // Fondo alternado (blanco / gris muy claro)
            let rbg = if fi % 2 == 0 { 0.975_f64 } else { 0.950_f64 };
            cr.set_source_rgb(rbg, rbg, rbg);
            cr.rectangle(ml, y, cw, 13.0);
            cr.fill().ok();

            // Fecha DD/MM/YYYY (bold, azul)
            let fecha_fmt = if let Ok(d) = NaiveDate::parse_from_str(fecha, "%Y-%m-%d") {
                d.format("%d/%m/%Y").to_string()
            } else {
                fecha.clone()
            };
            cr.set_source_rgb(0.10, 0.18, 0.55);
            cr.select_font_face("Sans", FontSlant::Normal, FontWeight::Bold);
            cr.set_font_size(9.5);
            cr.move_to(ml + 4.0, y + 10.0);
            cr.show_text(&fecha_fmt).ok();

            // Horas separadas por comas
            cr.set_source_rgb(0.15, 0.15, 0.15);
            cr.select_font_face("Sans", FontSlant::Normal, FontWeight::Normal);
            cr.set_font_size(9.0);
            cr.move_to(ml + 74.0, y + 10.0);
            cr.show_text(&horas.join(", ")).ok();

            y += 13.0;
        }

        // Línea divisora entre audios
        cr.set_source_rgb(0.65, 0.65, 0.70);
        cr.set_line_width(0.4);
        cr.move_to(ml, y + 1.0);
        cr.line_to(pw - mr, y + 1.0);
        cr.stroke().ok();
        y += 7.0;

        fila_idx += 1;
    }

    // ── Pie de reporte ─────────────────────────────────────────────────────────
    if y + 22.0 > ph - mb {
        cr.show_page().ok();
        pagina += 1;
        cabecera(&cr, pw, ml, mr, mt, pagina, categoria, desde, hasta, &nombre_radio);
        y = mt;
    }
    y += 6.0;
    cr.set_source_rgb(0.50, 0.50, 0.55);
    cr.set_line_width(0.7);
    cr.move_to(ml, y);
    cr.line_to(pw - mr, y);
    cr.stroke().ok();
    y += 9.0;

    cr.set_source_rgb(0.42, 0.42, 0.48);
    cr.select_font_face("Sans", FontSlant::Normal, FontWeight::Normal);
    cr.set_font_size(7.5);
    cr.move_to(ml, y);
    let ahora = Local::now();
    cr.show_text(&format!(
        "Generado: {}  ·  Títulos únicos: {}  ·  Total emisiones: {}",
        ahora.format("%d/%m/%Y %H:%M"),
        tot_tit,
        tot_emis,
    )).ok();

    // ── Página de Resumen por Día ─────────────────────────────────────────────
    let (nacionales, intercultural) = leer_carpetas_clasificacion();
    let resumen_dias = calcular_resumen_por_dia(entradas, &nacionales, &intercultural);
    if !resumen_dias.is_empty() {
        cr.show_page().ok();
        pagina += 1;
        cabecera(&cr, pw, ml, mr, mt, pagina, categoria, desde, hasta, &nombre_radio);
        let mut ry = mt + 8.0;

        // Título de sección
        cr.set_source_rgb(0.88, 0.94, 1.0);
        cr.select_font_face("Sans", FontSlant::Normal, FontWeight::Bold);
        cr.set_font_size(11.0);
        cr.move_to(ml, ry);
        cr.show_text("Resumen por Día — Nacional e Intercultural").ok();
        ry += 16.0;

        // Cabecera de tabla
        cr.set_source_rgb(0.13, 0.17, 0.38);
        cr.rectangle(ml, ry, cw, 14.0);
        cr.fill().ok();
        cr.set_source_rgb(0.65, 0.78, 1.0);
        cr.select_font_face("Sans", FontSlant::Normal, FontWeight::Bold);
        cr.set_font_size(8.5);
        let col_w = cw / 4.0;
        cr.move_to(ml + 4.0,             ry + 10.0); cr.show_text("FECHA").ok();
        cr.move_to(ml + col_w,           ry + 10.0); cr.show_text("TOTAL").ok();
        cr.move_to(ml + col_w * 2.0,     ry + 10.0); cr.show_text("NACIONAL").ok();
        cr.move_to(ml + col_w * 3.0,     ry + 10.0); cr.show_text("INTERCULTURAL").ok();
        ry += 14.0;

        for (idx, rd) in resumen_dias.iter().enumerate() {
            if ry + 13.0 > ph - mb {
                cr.show_page().ok();
                pagina += 1;
                cabecera(&cr, pw, ml, mr, mt, pagina, categoria, desde, hasta, &nombre_radio);
                ry = mt;
            }
            let bg = if idx % 2 == 0 { 0.975_f64 } else { 0.950_f64 };
            cr.set_source_rgb(bg, bg, bg);
            cr.rectangle(ml, ry, cw, 13.0);
            cr.fill().ok();

            let fecha_fmt = if let Ok(d) = NaiveDate::parse_from_str(&rd.fecha, "%Y-%m-%d") {
                d.format("%d/%m/%Y").to_string()
            } else { rd.fecha.clone() };

            let pct_nac = if rd.total_seg > 0 { rd.nacional_seg * 100 / rd.total_seg } else { 0 };
            let pct_int = if rd.total_seg > 0 { rd.intercultural_seg * 100 / rd.total_seg } else { 0 };

            cr.set_source_rgb(0.10, 0.10, 0.15);
            cr.select_font_face("Sans", FontSlant::Normal, FontWeight::Normal);
            cr.set_font_size(9.0);
            cr.move_to(ml + 4.0,         ry + 9.5); cr.show_text(&fecha_fmt).ok();
            cr.move_to(ml + col_w,        ry + 9.5); cr.show_text(&dur_hms(rd.total_seg)).ok();

            cr.set_source_rgb(0.10, 0.48, 0.22);
            cr.move_to(ml + col_w * 2.0,  ry + 9.5);
            cr.show_text(&format!("{}  ({} %)", dur_hms(rd.nacional_seg), pct_nac)).ok();

            cr.set_source_rgb(0.60, 0.32, 0.05);
            cr.move_to(ml + col_w * 3.0,  ry + 9.5);
            cr.show_text(&format!("{}  ({} %)", dur_hms(rd.intercultural_seg), pct_int)).ok();

            ry += 13.0;
        }
    }

    drop(cr);
    surface.finish();
    Ok(())
}

// ── Exportación CSV ───────────────────────────────────────────────────────────

fn exportar_csv(entradas: &[Entrada], destino: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {
    let mut txt = String::from("Archivo,Fecha,Hora,Duracion_seg\n");
    for e in entradas {
        // Sin ruta absoluta, sin "seg", sin tipo
        txt.push_str(&format!("{},{},{},{}\n",
            e.nombre, e.fecha, e.hora, e.duracion));
    }
    fs::write(destino, &txt)?;
    Ok(())
}

// ── Interfaz GTK4 ─────────────────────────────────────────────────────────────

fn construir_ui(app: &Application) {
    let win = ApplicationWindow::builder()
        .application(app)
        .title(tr("rep.win_title"))
        .default_width(860)
        .default_height(660)
        .build();

    // Solicitar tema oscuro al sistema de settings de GTK
    if let Some(settings) = gtk4::Settings::default() {
        settings.set_gtk_application_prefer_dark_theme(true);
    }

    // CSS con prioridad máxima para pisar el tema del sistema
    let css = CssProvider::new();
    css.load_from_data(APP_CSS);
    if let Some(display) = gdk4::Display::default() {
        gtk4::style_context_add_provider_for_display(
            &display, &css, gtk4::STYLE_PROVIDER_PRIORITY_USER,
        );
        skin::aplicar_css_extra(&display);
    }

    let root = GtkBox::new(Orientation::Vertical, 0);

    // ── Header ───────────────────────────────────────────────────────────────
    let hbar = GtkBox::new(Orientation::Horizontal, 12);
    hbar.add_css_class("header-bar");
    hbar.set_margin_bottom(10);

    // Logo
    {
        let loader = gdk4::gdk_pixbuf::PixbufLoader::with_type("png")
            .unwrap_or_else(|_| gdk4::gdk_pixbuf::PixbufLoader::new());
        loader.write(&skin::icono("gradio.png", LOGO_BYTES)).ok();
        loader.close().ok();
        if let Some(pb) = loader.pixbuf() {
            if let Some(pb2) = pb.scale_simple(76, 76, gdk4::gdk_pixbuf::InterpType::Bilinear) {
                let tex = gdk4::Texture::for_pixbuf(&pb2);
                let img = gtk4::Image::from_paintable(Some(&tex));
                img.set_size_request(76, 76);
                img.set_margin_end(8);
                hbar.append(&img);
            }
        }
    }

    let title_col = GtkBox::new(Orientation::Vertical, 2);
    title_col.set_hexpand(true);
    let t_lbl = Label::new(Some(tr("rep.title")));
    t_lbl.add_css_class("title-lbl");
    t_lbl.set_halign(Align::Start);
    let s_lbl = Label::new(Some(tr("rep.subtitle")));
    s_lbl.add_css_class("sub-lbl");
    s_lbl.set_halign(Align::Start);
    title_col.append(&t_lbl);
    title_col.append(&s_lbl);
    hbar.append(&title_col);
    root.append(&hbar);

    // ── Controles ─────────────────────────────────────────────────────────────
    let ctrl = GtkBox::new(Orientation::Horizontal, 20);
    ctrl.set_margin_start(16);
    ctrl.set_margin_end(16);
    ctrl.set_margin_bottom(10);

    // Categoría
    let cat_col = GtkBox::new(Orientation::Vertical, 5);
    let cat_lbl = Label::new(Some(tr("rep.categoria")));
    cat_lbl.add_css_class("sec-lbl");
    cat_lbl.set_halign(Align::Start);
    let btn_com = CheckButton::with_label(tr("rep.cat_com"));
    let btn_par = CheckButton::with_label(tr("rep.cat_par"));
    let btn_ev  = CheckButton::with_label(tr("rep.cat_ev"));
    btn_par.set_group(Some(&btn_com));
    btn_ev.set_group(Some(&btn_com));
    btn_com.set_active(true);
    for b in [&btn_com, &btn_par, &btn_ev] { b.add_css_class("cat"); }
    cat_col.append(&cat_lbl);
    cat_col.append(&btn_com);
    cat_col.append(&btn_par);
    cat_col.append(&btn_ev);

    // Fechas
    let date_col = GtkBox::new(Orientation::Vertical, 6);
    let dl = Label::new(Some(tr("rep.rango_fechas")));
    dl.add_css_class("sec-lbl");
    dl.set_halign(Align::Start);

    let hoy       = Local::now().date_naive();
    let hace30    = hoy - ChronoDuration::days(30);

    // Selector gráfico de fecha: botón con etiqueta de fecha + popover con Calendar
    let mk_date_picker = |label: &str, fecha: NaiveDate| -> (GtkBox, Entry) {
        let row = GtkBox::new(Orientation::Horizontal, 6);
        let l = Label::new(Some(label));
        l.add_css_class("sec-lbl");
        l.set_width_chars(6);

        // Entry oculto que guarda la fecha seleccionada (AAAA-MM-DD)
        let entry = Entry::builder()
            .text(fecha.format("%Y-%m-%d").to_string().as_str())
            .max_length(10)
            .build();
        entry.set_visible(false);   // no visible — solo almacenamiento

        // Botón que muestra la fecha formateada y abre el calendario
        let btn_lbl = fecha.format("%d/%m/%Y").to_string();
        let btn_fecha = Button::with_label(&btn_lbl);
        btn_fecha.add_css_class("date-entry");
        btn_fecha.set_hexpand(false);

        // Popover con Calendar
        let popover = Popover::new();
        let cal = Calendar::new();
        // Posicionar al día correcto
        cal.set_year(fecha.year());
        cal.set_month(fecha.month0() as i32);
        cal.set_day(fecha.day() as i32);
        popover.set_child(Some(&cal));
        popover.set_parent(&btn_fecha);

        // Al hacer clic en el botón, abrir/cerrar el popover
        {
            let pop = popover.clone();
            btn_fecha.connect_clicked(move |_| pop.popup());
        }
        // Al seleccionar un día, actualizar el Entry y el botón
        {
            let e  = entry.clone();
            let bf = btn_fecha.clone();
            let pop = popover.clone();
            cal.connect_day_selected(move |c| {
                let y = c.year();
                let m = c.month() + 1;   // gtk Calendar: month0 (0-11)
                let d = c.day();
                if let Some(nd) = NaiveDate::from_ymd_opt(y, m as u32, d as u32) {
                    e.set_text(&nd.format("%Y-%m-%d").to_string());
                    bf.set_label(&nd.format("%d/%m/%Y").to_string());
                    pop.popdown();
                }
            });
        }

        row.append(&l);
        row.append(&btn_fecha);
        (row, entry)
    };

    let (desde_row, entry_desde) = mk_date_picker(tr("rep.desde"), hace30);
    let (hasta_row,  entry_hasta)  = mk_date_picker(tr("rep.hasta"),  hoy);

    let btn_cargar = Button::with_label(tr("rep.btn_cargar"));
    btn_cargar.add_css_class("btn-load");

    date_col.append(&dl);
    date_col.append(&desde_row);
    date_col.append(&hasta_row);
    date_col.append(&btn_cargar);

    // Filtro de texto
    let filter_col = GtkBox::new(Orientation::Vertical, 6);
    let fl = Label::new(Some(tr("rep.buscar_audio")));
    fl.add_css_class("sec-lbl");
    fl.set_halign(Align::Start);
    let entry_filtro = Entry::builder()
        .placeholder_text(tr("rep.buscar_ph"))
        .hexpand(true)
        .build();
    entry_filtro.add_css_class("date-entry");
    let fl2 = Label::new(Some(tr("rep.buscar_hint")));
    fl2.add_css_class("status-lbl");
    fl2.set_halign(Align::Start);
    filter_col.append(&fl);
    filter_col.append(&entry_filtro);
    filter_col.append(&fl2);
    filter_col.set_hexpand(true);

    ctrl.append(&cat_col);
    ctrl.append(&Separator::new(Orientation::Vertical));
    ctrl.append(&date_col);
    ctrl.append(&Separator::new(Orientation::Vertical));
    ctrl.append(&filter_col);
    root.append(&ctrl);

    // ── Lista de resultados ───────────────────────────────────────────────────
    let list_box = ListBox::new();
    list_box.set_selection_mode(gtk4::SelectionMode::None);

    let scroll = ScrolledWindow::builder()
        .child(&list_box)
        .vexpand(true)
        .hexpand(true)
        .margin_start(16)
        .margin_end(16)
        .margin_bottom(6)
        .build();
    root.append(&scroll);

    // ── Resumen por Día ───────────────────────────────────────────────────────
    let dia_hdr_box = GtkBox::new(Orientation::Horizontal, 8);
    dia_hdr_box.add_css_class("list-hdr");
    dia_hdr_box.set_margin_start(16);
    dia_hdr_box.set_margin_end(16);
    dia_hdr_box.set_margin_top(6);
    let mk_dh = |txt: &str, chars: i32| {
        let l = Label::new(Some(txt));
        l.add_css_class("sec-lbl");
        l.set_width_chars(chars);
        l.set_halign(Align::Start);
        l
    };
    let dh_fecha = mk_dh(tr("rep.col_fecha"),        11);
    let dh_tot   = mk_dh(tr("rep.col_total"),         9);
    let dh_nac   = mk_dh(tr("rep.col_nacional"),     20);
    let dh_int   = mk_dh(tr("rep.col_intercult"),    22);
    dia_hdr_box.append(&dh_fecha);
    dia_hdr_box.append(&dh_tot);
    dia_hdr_box.append(&dh_nac);
    dia_hdr_box.append(&dh_int);
    root.append(&dia_hdr_box);

    let dia_list = ListBox::new();
    dia_list.set_selection_mode(gtk4::SelectionMode::None);
    let dia_scroll = ScrolledWindow::builder()
        .child(&dia_list)
        .hexpand(true)
        .margin_start(16)
        .margin_end(16)
        .margin_bottom(4)
        .build();
    // altura fija: máximo ~5 filas visibles
    dia_scroll.set_min_content_height(120);
    dia_scroll.set_max_content_height(180);
    root.append(&dia_scroll);

    // ── Estado ───────────────────────────────────────────────────────────────
    let status = Label::new(Some(tr("rep.status_inicio")));
    status.add_css_class("status-lbl");
    status.set_halign(Align::Start);
    root.append(&status);

    // ── Botones de acción ─────────────────────────────────────────────────────
    let act_box = GtkBox::new(Orientation::Horizontal, 8);
    act_box.set_margin_start(16);
    act_box.set_margin_end(16);
    act_box.set_margin_bottom(14);

    let btn_pdf = Button::with_label(tr("rep.btn_pdf"));
    btn_pdf.add_css_class("btn-pdf");
    btn_pdf.set_sensitive(false);

    let btn_csv_btn = Button::with_label(tr("rep.btn_csv"));
    btn_csv_btn.add_css_class("btn-csv");
    btn_csv_btn.set_sensitive(false);

    let spacer = Label::new(None);
    spacer.set_hexpand(true);

    let btn_close = Button::with_label(tr("btn.close"));
    btn_close.add_css_class("btn-close");

    act_box.append(&btn_pdf);
    act_box.append(&btn_csv_btn);
    act_box.append(&spacer);
    act_box.append(&btn_close);
    root.append(&act_box);

    win.set_child(Some(&root));

    // ── Estado compartido: entradas cargadas ──────────────────────────────────
    let store: Arc<Mutex<Vec<Entrada>>> = Arc::new(Mutex::new(vec![]));

    // ── Cargar datos ──────────────────────────────────────────────────────────
    {
        let lb     = list_box.clone();
        let dl     = dia_list.clone();
        let st     = status.clone();
        let bp     = btn_pdf.clone();
        let bc     = btn_csv_btn.clone();
        let data   = store.clone();
        let b_com  = btn_com.clone();
        let b_par  = btn_par.clone();
        let e_d    = entry_desde.clone();
        let e_h    = entry_hasta.clone();
        let e_f    = entry_filtro.clone();

        btn_cargar.connect_clicked(move |_| {
            let cat    = if b_com.is_active() { "Comerciales" }
                         else if b_par.is_active() { "Parrilla" }
                         else { "Eventos" };
            let desde  = e_d.text().to_string();
            let hasta  = e_h.text().to_string();
            let filtro = e_f.text().to_string().to_lowercase();

            let mut entradas = cargar_entradas(cat, &desde, &hasta);
            if !filtro.is_empty() {
                entradas.retain(|e| e.nombre.to_lowercase().contains(&filtro));
            }
            let agrupado = agrupar(&entradas);

            // Limpiar listas
            while let Some(c) = lb.first_child() { lb.remove(&c); }
            while let Some(c) = dl.first_child() { dl.remove(&c); }

            if entradas.is_empty() {
                st.set_text(tr("rep.status_vacio"));
                bp.set_sensitive(false);
                bc.set_sensitive(false);
                *data.lock().unwrap() = vec![];
                return;
            }

            // Cabecera de columnas
            {
                let hdr = GtkBox::new(Orientation::Horizontal, 8);
                hdr.add_css_class("list-hdr");
                hdr.set_margin_start(6);
                hdr.set_margin_end(6);
                hdr.set_margin_top(3);
                hdr.set_margin_bottom(3);
                let h1 = Label::new(Some(tr("rep.col_archivo")));
                h1.set_hexpand(true);
                h1.set_halign(Align::Start);
                h1.add_css_class("sec-lbl");
                let h2 = Label::new(Some(tr("rep.col_emisiones")));
                h2.set_width_chars(10);
                h2.add_css_class("sec-lbl");
                let h3 = Label::new(Some(tr("rep.col_dur_total")));
                h3.set_width_chars(12);
                h3.add_css_class("sec-lbl");
                hdr.append(&h1);
                hdr.append(&h2);
                hdr.append(&h3);
                let row = ListBoxRow::new();
                row.set_child(Some(&hdr));
                row.set_activatable(false);
                lb.append(&row);
            }

            for (nombre, (dur_s, fechas)) in &agrupado {
                let total: usize = fechas.values().map(|v| v.len()).sum();
                let dur_t = *dur_s as u64 * total as u64;

                let rb = GtkBox::new(Orientation::Horizontal, 8);
                rb.set_margin_start(6);
                rb.set_margin_end(6);
                rb.set_margin_top(2);
                rb.set_margin_bottom(2);

                let nl = Label::new(Some(nombre));
                nl.set_hexpand(true);
                nl.set_halign(Align::Start);
                nl.set_ellipsize(gtk4::pango::EllipsizeMode::End);
                nl.add_css_class("res-name");

                let cl = Label::new(Some(&total.to_string()));
                cl.set_width_chars(10);
                cl.set_halign(Align::End);
                cl.add_css_class("res-count");

                let dl = Label::new(Some(&dur_hms(dur_t)));
                dl.set_width_chars(12);
                dl.set_halign(Align::End);
                dl.add_css_class("res-dur");

                rb.append(&nl);
                rb.append(&cl);
                rb.append(&dl);

                let row = ListBoxRow::new();
                row.set_child(Some(&rb));
                row.set_activatable(false);
                lb.append(&row);
            }

            // ── Resumen por día ──────────────────────────────────────────
            let (nacionales, intercultural) = leer_carpetas_clasificacion();
            let resumen = calcular_resumen_por_dia(&entradas, &nacionales, &intercultural);
            for rd in &resumen {
                let rb = GtkBox::new(Orientation::Horizontal, 8);
                rb.set_margin_start(6); rb.set_margin_end(6);
                rb.set_margin_top(2);   rb.set_margin_bottom(2);

                let fecha_fmt = if let Ok(d) = NaiveDate::parse_from_str(&rd.fecha, "%Y-%m-%d") {
                    d.format("%d/%m/%Y").to_string()
                } else { rd.fecha.clone() };

                let lf = Label::new(Some(&fecha_fmt));
                lf.set_width_chars(11); lf.set_halign(Align::Start);
                lf.add_css_class("dia-fecha");

                let lt = Label::new(Some(&dur_hms(rd.total_seg)));
                lt.set_width_chars(9); lt.set_halign(Align::Start);
                lt.add_css_class("dia-total");

                let pct_nac = if rd.total_seg > 0 {
                    rd.nacional_seg * 100 / rd.total_seg
                } else { 0 };
                let pct_int = if rd.total_seg > 0 {
                    rd.intercultural_seg * 100 / rd.total_seg
                } else { 0 };

                let ln = Label::new(Some(&format!("{}  ({} %)", dur_hms(rd.nacional_seg), pct_nac)));
                ln.set_width_chars(20); ln.set_halign(Align::Start);
                ln.add_css_class("dia-nac");

                let li = Label::new(Some(&format!("{}  ({} %)", dur_hms(rd.intercultural_seg), pct_int)));
                li.set_width_chars(22); li.set_halign(Align::Start);
                li.add_css_class("dia-int");

                rb.append(&lf); rb.append(&lt); rb.append(&ln); rb.append(&li);
                let row = ListBoxRow::new();
                row.set_child(Some(&rb));
                row.set_activatable(false);
                dl.append(&row);
            }

            st.set_text(
                &tr("rep.status_resumen")
                    .replace("{titulos}",   &agrupado.len().to_string())
                    .replace("{emisiones}", &entradas.len().to_string())
                    .replace("{desde}",     &desde)
                    .replace("{hasta}",     &hasta)
            );
            bp.set_sensitive(true);
            bc.set_sensitive(true);
            *data.lock().unwrap() = entradas;
        });
    }

    // ── Generar PDF ───────────────────────────────────────────────────────────
    {
        let data   = store.clone();
        let st     = status.clone();
        let b_com  = btn_com.clone();
        let b_par  = btn_par.clone();
        let e_d    = entry_desde.clone();
        let e_h    = entry_hasta.clone();

        btn_pdf.connect_clicked(move |_| {
            let cat   = if b_com.is_active() { "Comerciales" }
                        else if b_par.is_active() { "Parrilla" }
                        else { "Eventos" };
            let desde = e_d.text().to_string();
            let hasta = e_h.text().to_string();

            let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("/"));
            let dl   = home.join("Downloads");
            let base = if dl.exists() { dl } else { home };
            let fname = format!("GR6-reporte-{}-{}_{}.pdf",
                cat.to_lowercase().replace(' ', "-"), desde, hasta);
            let path = base.join(&fname);

            let entradas = data.lock().unwrap().clone();
            match generar_pdf(&entradas, cat, &desde, &hasta, &path) {
                Ok(_) => {
                    st.set_text(&tr("rep.status_pdf_ok").replace("{path}", &path.display().to_string()));
                    #[cfg(target_os = "windows")]
                    let _ = std::process::Command::new("explorer").arg(&path).spawn();
                    #[cfg(not(target_os = "windows"))]
                    let _ = std::process::Command::new("xdg-open").arg(&path).spawn();
                }
                Err(e) => {
                    st.set_text(&tr("rep.status_pdf_err").replace("{err}", &e.to_string()));
                }
            }
        });
    }

    // ── Exportar CSV ──────────────────────────────────────────────────────────
    {
        let data   = store.clone();
        let st     = status.clone();
        let b_com  = btn_com.clone();
        let b_par  = btn_par.clone();
        let e_d    = entry_desde.clone();
        let e_h    = entry_hasta.clone();

        btn_csv_btn.connect_clicked(move |_| {
            let cat   = if b_com.is_active() { "Comerciales" }
                        else if b_par.is_active() { "Parrilla" }
                        else { "Eventos" };
            let desde = e_d.text().to_string();
            let hasta = e_h.text().to_string();

            let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("/"));
            let dl   = home.join("Downloads");
            let base = if dl.exists() { dl } else { home };
            let fname = format!("GR6-reporte-{}-{}_{}.csv",
                cat.to_lowercase().replace(' ', "-"), desde, hasta);
            let path = base.join(&fname);

            let entradas = data.lock().unwrap().clone();
            match exportar_csv(&entradas, &path) {
                Ok(_) => {
                    st.set_text(&tr("rep.status_csv_ok").replace("{path}", &path.display().to_string()));
                }
                Err(e) => {
                    st.set_text(&tr("rep.status_csv_err").replace("{err}", &e.to_string()));
                }
            }
        });
    }

    // ── Cerrar ────────────────────────────────────────────────────────────────
    {
        let w = win.clone();
        btn_close.connect_clicked(move |_| w.close());
    }

    // Ícono en taskbar
    {
        let loader = gdk4::gdk_pixbuf::PixbufLoader::new();
        let _ = loader.write(&skin::icono("reportes.png", ICONO_REPORTES));
        let _ = loader.close();
        if let Some(pb) = loader.pixbuf() {
            let tex = gdk4::Texture::for_pixbuf(&pb);
            let win2 = win.clone();
            win.connect_realize(move |w| {
                if let Some(surf) = w.surface() {
                    use gdk4::prelude::ToplevelExt;
                    if let Some(tl) = surf.dynamic_cast_ref::<gdk4::Toplevel>() {
                        tl.set_icon_list(&[tex.clone()]);
                    }
                    let display = gtk4::prelude::WidgetExt::display(w);
                    use gdk4::prelude::DisplayExt;
                    if let Some(monitor) = display.monitor_at_surface(&surf) {
                        use gdk4::prelude::MonitorExt;
                        let geo = monitor.geometry();
                        let ancho = (geo.width() - 20).min(900).max(600);
                        let alto  = (geo.height() - 90).min(680).max(400);
                        win2.set_default_size(ancho, alto);
                    }
                }
            });
        }
    }

    win.show();
}

fn main() {
    // i18n: leer locale del config compartido si está, si no autodetectar
    let locale_cfg = read_locale_from_config();
    grpautaje::i18n::init(locale_cfg.as_deref());

    let app = Application::builder()
        .application_id("ar.com.gradio.reportes")
        .build();
    app.connect_activate(construir_ui);
    app.run();
}

fn read_locale_from_config() -> Option<String> {
    let home = dirs::home_dir()?;
    let path = home.join(".gradio/data/gradio_config");
    let content = std::fs::read_to_string(path).ok()?;
    let line = content.lines().nth(16)?.trim();
    if line.is_empty() { None } else { Some(line.to_string()) }
}
