// gr_record.rs — Grabador de audio G Radio
//
// Graba desde cualquier fuente PulseAudio (línea, monitor, etc.)
// Guarda en ~/GR-grabaciones/GRadio-rec-YYYY-MM-DD-HH-MM.mp3
// VU meter (estilo player) activo solo durante grabación.

use gtk4 as gtk;
use gtk::prelude::*;
use gtk::{
    Application, ApplicationWindow, Box as GtkBox, Button, Label,
    ComboBoxText, DrawingArea, Orientation,
    CssProvider, STYLE_PROVIDER_PRIORITY_APPLICATION,
    Image,
};
use glib;
use gdk4;
use gstreamer as gst;

const ICONO_RECORD: &[u8] = include_bytes!("../assets/grabar48x48.png");
use grpautaje::skin;
use gstreamer::prelude::*;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::sync::{Arc, Mutex};
#[cfg(not(target_os = "windows"))]
use std::process::Command;
use chrono::Local;

const APP_ID: &str = "com.gradio.record";

// ── Estado compartido entre GStreamer bus-watch y GTK ────────────────────────
struct RecState {
    vu_l: f64,
    vu_r: f64,
}

type SharedState = Arc<Mutex<RecState>>;

fn main() -> glib::ExitCode {
    gst::init().expect("GStreamer init falló");
    let app = Application::builder()
        .application_id(APP_ID)
        .build();
    app.connect_activate(|app| {
        if let Some(w) = app.active_window() { w.present(); return; }
        build_ui(app);
    });
    app.run()
}

// ── Listar fuentes de audio ───────────────────────────────────────────────────
#[cfg(target_os = "windows")]
fn list_pulse_sources() -> Vec<(String, String)> {
    vec![("default".to_string(), "Default Audio Input (WASAPI)".to_string())]
}

#[cfg(not(target_os = "windows"))]
fn list_pulse_sources() -> Vec<(String, String)> {
    let out = Command::new("pactl")
        .args(["list", "sources"])
        .env("LANG", "C")
        .output();
    let Ok(out) = out else { return vec![] };
    let text = String::from_utf8_lossy(&out.stdout);

    let mut sources: Vec<(String, String)> = Vec::new();
    let mut cur_name = String::new();
    let mut cur_desc = String::new();

    for line in text.lines() {
        let t = line.trim();
        if t.starts_with("Name:") {
            cur_name = t.trim_start_matches("Name:").trim().to_string();
            cur_desc.clear();
        } else if t.starts_with("Description:") {
            cur_desc = t.trim_start_matches("Description:").trim().to_string();
            if !cur_name.is_empty() {
                sources.push((cur_name.clone(), cur_desc.clone()));
            }
        }
    }
    sources
}

// ── Construir pipeline GStreamer ──────────────────────────────────────────────
fn build_pipeline(source_name: &str, output_path: &str) -> anyhow::Result<gst::Pipeline> {
    let pipeline = gst::Pipeline::new();

    #[cfg(target_os = "windows")]
    let audio_src = gst::ElementFactory::make("wasapi2src")
        .build()
        .or_else(|_| gst::ElementFactory::make("autoaudiosrc").build())
        .map_err(|e| anyhow::anyhow!("audio source: {}", e))?;

    #[cfg(not(target_os = "windows"))]
    let audio_src = gst::ElementFactory::make("pulsesrc")
        .property("device", source_name)
        .build()
        .map_err(|e| anyhow::anyhow!("pulsesrc: {}", e))?;

    let audioconvert = gst::ElementFactory::make("audioconvert")
        .build()
        .map_err(|e| anyhow::anyhow!("audioconvert: {}", e))?;

    let audioresample = gst::ElementFactory::make("audioresample")
        .build()
        .map_err(|e| anyhow::anyhow!("audioresample: {}", e))?;

    let level_elem = gst::ElementFactory::make("level")
        .property("interval",  50_000_000u64) // 50 ms
        .property("peak-ttl",  0u64)
        .property("post-messages", true)
        .build()
        .unwrap_or_else(|_| gst::ElementFactory::make("identity").build().unwrap());

    let encoder = gst::ElementFactory::make("lamemp3enc")
        .property("bitrate", 192i32)
        .build()
        .unwrap_or_else(|_| {
            gst::ElementFactory::make("avenc_mp3").build()
                .unwrap_or_else(|_| gst::ElementFactory::make("identity").build().unwrap())
        });

    let filesink = gst::ElementFactory::make("filesink")
        .property("location", output_path)
        .build()
        .map_err(|e| anyhow::anyhow!("filesink: {}", e))?;

    pipeline.add_many([
        &audio_src, &audioconvert, &audioresample,
        &level_elem, &encoder, &filesink,
    ]).map_err(|e| anyhow::anyhow!("add_many: {}", e))?;

    gst::Element::link_many([
        &audio_src, &audioconvert, &audioresample,
        &level_elem, &encoder, &filesink,
    ]).map_err(|e| anyhow::anyhow!("link_many: {}", e))?;

    Ok(pipeline)
}

// ── Bus watch: alimenta el estado VU ─────────────────────────────────────────
fn attach_level_watch(pipeline: &gst::Pipeline, state: SharedState) {
    let bus = match pipeline.bus() {
        Some(b) => b,
        None => return,
    };
    let watch = bus.add_watch_local(move |_, msg| {
        if let gst::MessageView::Element(elem) = msg.view() {
            if let Some(structure) = elem.structure() {
                if structure.name() == "level" {
                    let to_display = |db: f64| -> f64 {
                        if db <= -60.0 { return 0.0; }
                        let min_db = -50.0_f64;
                        let max_db =  -3.0_f64;
                        ((db - min_db) / (max_db - min_db)).clamp(0.0, 1.0)
                    };
                    let chs: Vec<f64> = if let Ok(arr) = structure.get::<glib::ValueArray>("rms") {
                        arr.iter().filter_map(|v| v.get::<f64>().ok()).collect()
                    } else {
                        vec![]
                    };
                    if !chs.is_empty() {
                        if let Ok(mut st) = state.try_lock() {
                            st.vu_l = to_display(chs[0]);
                            st.vu_r = to_display(*chs.get(1).unwrap_or(&chs[0]));
                        }
                    }
                }
            }
        }
        glib::ControlFlow::Continue
    });
    if let Ok(guard) = watch {
        std::mem::forget(guard);
    }
}

// ── CSS ──────────────────────────────────────────────────────────────────────
const APP_CSS: &str = r#"
window { background-color: #1e1e1e; color: #e0e0e0; }

.toolbar {
    background-color: #2a2a2a;
    border-bottom: 1px solid #444;
    padding: 6px 10px;
}

.source-label {
    color: #aaccee;
    font-weight: bold;
    font-size: 13px;
    padding-right: 6px;
}

.vu-row {
    background-color: #181818;
    padding: 6px 10px;
    border-bottom: 1px solid #333;
}

.vu-ch-label {
    color: #888;
    font-size: 11px;
    font-weight: bold;
    min-width: 14px;
}

.vu-gradio-label {
    color: #cc3333;
    font-size: 11px;
    font-weight: bold;
    padding-left: 8px;
}

.info-bar {
    background-color: #252525;
    border-top: 1px solid #3a3a3a;
    padding: 4px 10px;
}

.rec-name-label {
    color: #aaaaaa;
    font-size: 12px;
    padding-right: 8px;
}

.rec-name-entry {
    background-color: #2a2a2a;
    color: #e0e0e0;
    border: 1px solid #444;
    border-radius: 3px;
    font-size: 12px;
}

.rec-time-label {
    color: #88cc88;
    font-size: 13px;
    font-weight: bold;
    font-family: monospace;
    padding: 0 12px;
}

.btn-rec {
    background: #2a2a2a;
    border: 1px solid #555;
    border-radius: 4px;
    padding: 4px;
    min-width: 48px;
    min-height: 48px;
}
.btn-rec:hover { background: #3a3a3a; }

.btn-rec.recording {
    background: #3a0000;
    border-color: #880000;
}

.bottom-bar {
    background-color: #1e1e1e;
    border-top: 1px solid #333;
    padding: 8px 10px;
}
"#;

// ── UI ────────────────────────────────────────────────────────────────────────
fn build_ui(app: &Application) {
    if let Some(settings) = gtk::Settings::default() {
        settings.set_gtk_application_prefer_dark_theme(true);
    }

    let provider = CssProvider::new();
    provider.load_from_data(APP_CSS);
    if let Some(display) = gdk4::Display::default() {
        gtk::style_context_add_provider_for_display(
            &display, &provider,
            STYLE_PROVIDER_PRIORITY_APPLICATION + 200,
        );
        skin::aplicar_css_extra(&display);
    }

    let window = ApplicationWindow::builder()
        .application(app)
        .title("GRadio Record")
        .default_width(700)
        .default_height(200)
        .resizable(false)
        .build();

    // ── Estado ────────────────────────────────────────────────────────────────
    let recording: Rc<Cell<bool>>                    = Rc::new(Cell::new(false));
    let pipeline_holder: Rc<RefCell<Option<gst::Pipeline>>> = Rc::new(RefCell::new(None));
    let vu_state: SharedState = Arc::new(Mutex::new(RecState { vu_l: 0.0, vu_r: 0.0 }));

    let vu_l_draw = Rc::new(Cell::new(0.0f64));
    let vu_r_draw = Rc::new(Cell::new(0.0f64));
    let rec_active_draw = Rc::new(Cell::new(false));

    // Segundos de grabación
    let rec_secs: Rc<Cell<u64>> = Rc::new(Cell::new(0));

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

    // ── Barra de fuente ───────────────────────────────────────────────────────
    let toolbar = GtkBox::new(Orientation::Horizontal, 8);
    toolbar.add_css_class("toolbar");

    let lbl_src = Label::new(Some("Fuente:"));
    lbl_src.add_css_class("source-label");

    let combo_src = ComboBoxText::new();
    combo_src.set_hexpand(true);

    // Poblar fuentes PulseAudio
    let sources = list_pulse_sources();
    for (name, desc) in &sources {
        combo_src.append(Some(name.as_str()), desc.as_str());
    }
    if !sources.is_empty() {
        combo_src.set_active(Some(0));
    }

    // Botón refrescar fuentes
    let btn_refresh = Button::with_label("⟳");
    btn_refresh.set_tooltip_text(Some("Actualizar fuentes"));
    {
        let combo_r = combo_src.clone();
        btn_refresh.connect_clicked(move |_| {
            combo_r.remove_all();
            for (name, desc) in list_pulse_sources() {
                combo_r.append(Some(name.as_str()), desc.as_str());
            }
            combo_r.set_active(Some(0));
        });
    }

    toolbar.append(&lbl_src);
    toolbar.append(&combo_src);
    toolbar.append(&btn_refresh);
    root.append(&toolbar);

    // ── VU Meter ──────────────────────────────────────────────────────────────
    const VU_LEDS: i32 = 24;
    const VU_LED_W: f64 = 15.0;
    const VU_LED_H: f64 = 8.0;
    const VU_LED_GAP: f64 = 2.0;

    fn draw_vu_leds(cr: &cairo::Context, _w: i32, h: i32, level: f64, active: bool) {
        let n = VU_LEDS;
        let y = ((h as f64) - VU_LED_H) / 2.0;
        let lit = if active { (level * n as f64).round() as i32 } else { 0 };
        for i in 0..n {
            let x = i as f64 * (VU_LED_W + VU_LED_GAP);
            let on = i < lit;
            let (r, g, b) = if i >= (n as f64 * 0.75) as i32 {
                if on { (1.0, 0.07, 0.0) } else { (0.25, 0.02, 0.0) }
            } else if i >= (n as f64 * 0.55) as i32 {
                if on { (0.9, 0.78, 0.0) } else { (0.22, 0.19, 0.0) }
            } else {
                if on { (0.0, 0.87, 0.13) } else { (0.0, 0.17, 0.03) }
            };
            cr.set_source_rgb(r, g, b);
            let rad = 1.5_f64;
            cr.new_sub_path();
            cr.arc(x + VU_LED_W - rad, y + rad,             rad, -std::f64::consts::PI / 2.0, 0.0);
            cr.arc(x + VU_LED_W - rad, y + VU_LED_H - rad,  rad, 0.0,                         std::f64::consts::PI / 2.0);
            cr.arc(x + rad,            y + VU_LED_H - rad,  rad, std::f64::consts::PI / 2.0,  std::f64::consts::PI);
            cr.arc(x + rad,            y + rad,             rad, std::f64::consts::PI,        3.0 * std::f64::consts::PI / 2.0);
            cr.close_path();
            let _ = cr.fill();
        }
    }

    let vu_bar_l = DrawingArea::new();
    let vu_bar_r = DrawingArea::new();
    vu_bar_l.set_hexpand(true);
    vu_bar_r.set_hexpand(true);
    vu_bar_l.set_content_height(14);
    vu_bar_r.set_content_height(14);

    {
        let lev = vu_l_draw.clone();
        let act = rec_active_draw.clone();
        vu_bar_l.set_draw_func(move |_, cr, w, h| draw_vu_leds(cr, w, h, lev.get(), act.get()));
    }
    {
        let lev = vu_r_draw.clone();
        let act = rec_active_draw.clone();
        vu_bar_r.set_draw_func(move |_, cr, w, h| draw_vu_leds(cr, w, h, lev.get(), act.get()));
    }

    let lbl_l = Label::new(Some("L"));
    let lbl_r = Label::new(Some("R"));
    lbl_l.add_css_class("vu-ch-label");
    lbl_r.add_css_class("vu-ch-label");

    let row_l = GtkBox::new(Orientation::Horizontal, 2);
    row_l.append(&lbl_l);
    row_l.append(&vu_bar_l);

    let row_r = GtkBox::new(Orientation::Horizontal, 2);
    row_r.append(&lbl_r);
    row_r.append(&vu_bar_r);

    let gradio_lbl = Label::new(Some("G-Radio"));
    gradio_lbl.add_css_class("vu-gradio-label");
    gradio_lbl.set_valign(gtk::Align::Center);
    gradio_lbl.set_halign(gtk::Align::End);

    let vu_bars = GtkBox::new(Orientation::Vertical, 0);
    vu_bars.set_hexpand(true);
    vu_bars.append(&row_l);
    vu_bars.append(&row_r);

    let vu_row = GtkBox::new(Orientation::Horizontal, 8);
    vu_row.add_css_class("vu-row");
    vu_row.append(&vu_bars);
    vu_row.append(&gradio_lbl);
    root.append(&vu_row);

    // ── Barra de info (nombre archivo + tiempo) ───────────────────────────────
    let info_bar = GtkBox::new(Orientation::Horizontal, 0);
    info_bar.add_css_class("info-bar");

    let lbl_grabacion = Label::new(Some("Grabación:"));
    lbl_grabacion.add_css_class("rec-name-label");

    // Entry de solo-lectura que muestra el nombre del archivo actual
    let entry_name = gtk::Entry::new();
    entry_name.set_editable(false);
    entry_name.set_hexpand(true);
    entry_name.add_css_class("rec-name-entry");
    entry_name.set_placeholder_text(Some("(sin grabación activa)"));

    let lbl_time = Label::new(Some("00:00:00"));
    lbl_time.add_css_class("rec-time-label");

    info_bar.append(&lbl_grabacion);
    info_bar.append(&entry_name);
    info_bar.append(&lbl_time);
    root.append(&info_bar);

    // ── Barra inferior (botón grabar) ─────────────────────────────────────────
    let bottom_bar = GtkBox::new(Orientation::Horizontal, 0);
    bottom_bar.add_css_class("bottom-bar");
    bottom_bar.set_halign(gtk::Align::End);

    // Icono rec (mismo que el player)
    let icon_off = load_rec_icon(false);
    let btn_rec = Button::new();
    btn_rec.set_child(Some(&icon_off));
    btn_rec.add_css_class("btn-rec");
    btn_rec.set_tooltip_text(Some("Grabar / Detener"));

    bottom_bar.append(&btn_rec);
    root.append(&bottom_bar);

    window.set_child(Some(&root));

    // ── Timer: refresca VU y tiempo ───────────────────────────────────────────
    {
        let vu_state_t   = vu_state.clone();
        let vu_l_t       = vu_l_draw.clone();
        let vu_r_t       = vu_r_draw.clone();
        let act_t        = rec_active_draw.clone();
        let bar_l_t      = vu_bar_l.clone();
        let bar_r_t      = vu_bar_r.clone();
        let recording_t  = recording.clone();

        glib::timeout_add_local(std::time::Duration::from_millis(80), move || {
            let is_rec = recording_t.get();
            act_t.set(is_rec);

            if is_rec {
                if let Ok(st) = vu_state_t.try_lock() {
                    vu_l_t.set(st.vu_l);
                    vu_r_t.set(st.vu_r);
                }
            } else {
                // Decay rápido cuando no graba
                let decay = (vu_l_t.get() - 0.05).max(0.0);
                vu_l_t.set(decay);
                let decay = (vu_r_t.get() - 0.05).max(0.0);
                vu_r_t.set(decay);
            }
            bar_l_t.queue_draw();
            bar_r_t.queue_draw();
            glib::ControlFlow::Continue
        });

        // Segundo timer para el contador de tiempo (1 s)
        let recording_s = recording.clone();
        let rec_secs_s  = rec_secs.clone();
        let lbl_time_s  = lbl_time.clone();
        glib::timeout_add_local(std::time::Duration::from_secs(1), move || {
            if recording_s.get() {
                let s = rec_secs_s.get() + 1;
                rec_secs_s.set(s);
                lbl_time_s.set_text(&format!("{:02}:{:02}:{:02}", s / 3600, (s % 3600) / 60, s % 60));
            }
            glib::ControlFlow::Continue
        });
    }

    // ── Acción del botón grabar ───────────────────────────────────────────────
    {
        let recording_b   = recording.clone();
        let pipeline_b    = pipeline_holder.clone();
        let vu_state_b    = vu_state.clone();
        let combo_b       = combo_src.clone();
        let entry_b       = entry_name.clone();
        let lbl_time_b    = lbl_time.clone();
        let rec_secs_b    = rec_secs.clone();
        let btn_rec_b     = btn_rec.clone();
        let vu_l_b        = vu_l_draw.clone();
        let vu_r_b        = vu_r_draw.clone();

        btn_rec.connect_clicked(move |_| {
            if recording_b.get() {
                // ── DETENER ──────────────────────────────────────────────────
                if let Some(pipe) = pipeline_b.borrow_mut().take() {
                    let _ = pipe.send_event(gst::event::Eos::new());
                    // Esperar EOS breve
                    let _ = pipe.state(gst::ClockTime::from_seconds(2));
                    let _ = pipe.set_state(gst::State::Null);
                }
                recording_b.set(false);
                vu_l_b.set(0.0);
                vu_r_b.set(0.0);
                btn_rec_b.remove_css_class("recording");
                btn_rec_b.set_child(Some(&load_rec_icon(false)));
            } else {
                // ── INICIAR ───────────────────────────────────────────────────
                let source = match combo_b.active_id() {
                    Some(id) => id.to_string(),
                    None => {
                        eprintln!("[gr-record] Sin fuente seleccionada");
                        return;
                    }
                };

                // Crear carpeta destino
                let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("/tmp"));
                let dest_dir = home.join("GR-grabaciones");
                if !dest_dir.exists() {
                    if let Err(e) = std::fs::create_dir_all(&dest_dir) {
                        eprintln!("[gr-record] No se pudo crear {}: {}", dest_dir.display(), e);
                        return;
                    }
                }

                let filename = format!("GRadio-rec-{}.mp3", Local::now().format("%Y-%m-%d-%H-%M"));
                let output_path = dest_dir.join(&filename);
                let output_str = output_path.to_string_lossy().to_string();

                match build_pipeline(&source, &output_str) {
                    Ok(pipe) => {
                        attach_level_watch(&pipe, vu_state_b.clone());
                        if pipe.set_state(gst::State::Playing).is_err() {
                            eprintln!("[gr-record] Error iniciando pipeline");
                            return;
                        }
                        *pipeline_b.borrow_mut() = Some(pipe);
                        recording_b.set(true);
                        rec_secs_b.set(0);
                        lbl_time_b.set_text("00:00:00");
                        entry_b.set_text(&filename);
                        btn_rec_b.add_css_class("recording");
                        btn_rec_b.set_child(Some(&load_rec_icon(true)));
                    }
                    Err(e) => {
                        eprintln!("[gr-record] Error creando pipeline: {:?}", e);
                    }
                }
            }
        });
    }

    // Ícono en taskbar
    {
        let loader = gdk4::gdk_pixbuf::PixbufLoader::new();
        let _ = loader.write(&skin::icono("grabar48x48.png", ICONO_RECORD));
        let _ = loader.close();
        if let Some(pb) = loader.pixbuf() {
            let tex = gdk4::Texture::for_pixbuf(&pb);
            let win = window.clone();
            window.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(720).max(400);
                        let alto  = (geo.height() - 90).min(250).max(180);
                        win.set_default_size(ancho, alto);
                    }
                }
            });
        }
    }

    window.present();
}

// ── Carga el ícono de grabar (on/off) ─────────────────────────────────────────
fn load_rec_icon(active: bool) -> Image {
    let asset = if active { "assets/rec-on.png" } else { "assets/rec-off.png" };

    // Intentar ruta relativa al binario
    let exe_dir = std::env::current_exe()
        .ok()
        .and_then(|p| p.parent().map(|d| d.to_path_buf()));

    for base in [
        std::env::current_dir().ok(),
        exe_dir.as_ref().and_then(|d| d.parent().map(|p| p.to_path_buf())),
        exe_dir,
    ]
    .into_iter()
    .flatten()
    {
        let path = base.join(asset);
        if path.exists() {
            let img = Image::from_file(&path);
            img.set_pixel_size(36);
            return img;
        }
    }

    // Fallback: icono de tema del sistema
    let img = Image::from_icon_name(if active { "media-record" } else { "media-record-symbolic" });
    img.set_pixel_size(36);
    img
}
