use gtk4::prelude::*;
use gtk4::{DrawingArea, EventControllerScroll, GestureDrag};
use std::cell::RefCell;
use std::rc::Rc;

const KNOB_SIZE: i32 = 64;
// Arco total: 270° centrado abajo. Start en 135° (225° desde las 3 en punto = abajo-izq)
const ARC_START: f64 = std::f64::consts::PI * 0.75;   // 135°
const ARC_END:   f64 = std::f64::consts::PI * 2.25;   // 405°

#[derive(Clone)]
pub struct Knob {
    pub widget: DrawingArea,
    state: Rc<RefCell<KnobState>>,
}

struct KnobState {
    value:     f32,
    min:       f32,
    max:       f32,
    default:   f32,
    label:     String,
    unit:      String,
    // Callback de usuario. Solo se dispara ante interacción humana (drag/scroll),
    // NO al llamar set_value() desde código (evita ciclos).
    on_change: Option<Box<dyn Fn(f32) + 'static>>,
}

impl Knob {
    pub fn new(label: &str, unit: &str, min: f32, max: f32, default: f32) -> Self {
        let da = DrawingArea::builder()
            .width_request(KNOB_SIZE)
            .height_request(KNOB_SIZE + 16)
            .focusable(true)
            .build();

        let state = Rc::new(RefCell::new(KnobState {
            value: default, min, max, default,
            label: label.to_string(), unit: unit.to_string(),
            on_change: None,
        }));

        // ── Dibujado ─────────────────────────────────────────────────────────
        {
            let s = state.clone();
            da.set_draw_func(move |_da, cr, w, h| draw_knob(cr, w, h, &s.borrow()));
        }

        // ── Scroll del ratón ─────────────────────────────────────────────────
        let scroll = EventControllerScroll::new(gtk4::EventControllerScrollFlags::VERTICAL);
        {
            let sc = state.clone();
            let da_sc = da.clone();
            scroll.connect_scroll(move |_, _dx, dy| {
                let new_val = {
                    let mut s = sc.borrow_mut();
                    let step = (s.max - s.min) / 200.0;
                    s.value = (s.value - dy as f32 * step).clamp(s.min, s.max);
                    s.value
                };
                da_sc.queue_draw();
                // Disparar callback fuera del borrow
                if let Some(ref cb) = sc.borrow().on_change { cb(new_val); }
                glib::Propagation::Stop
            });
        }
        da.add_controller(scroll);

        // ── Drag vertical ────────────────────────────────────────────────────
        let drag = GestureDrag::new();
        {
            let sd = state.clone();
            let da_d = da.clone();
            drag.connect_drag_update(move |_, _dx, dy| {
                let new_val = {
                    let mut s = sd.borrow_mut();
                    let range = s.max - s.min;
                    s.value = (s.value + -dy as f32 * range / 200.0).clamp(s.min, s.max);
                    s.value
                };
                da_d.queue_draw();
                if let Some(ref cb) = sd.borrow().on_change { cb(new_val); }
            });
        }
        da.add_controller(drag);

        Self { widget: da, state }
    }

    /// Valor actual del knob.
    pub fn value(&self) -> f32 {
        self.state.borrow().value
    }

    /// Establece el valor sin disparar el callback (para inicialización desde config).
    pub fn set_value(&self, v: f32) {
        let clamped = {
            let s = self.state.borrow();
            v.clamp(s.min, s.max)
        };
        self.state.borrow_mut().value = clamped;
        self.widget.queue_draw();
    }

    /// Registra el callback que se invoca cuando el usuario mueve el knob.
    /// El closure recibe el nuevo valor. Solo puede registrarse uno; una llamada
    /// posterior reemplaza al anterior.
    pub fn connect_changed<F: Fn(f32) + 'static>(&self, cb: F) {
        self.state.borrow_mut().on_change = Some(Box::new(cb));
    }
}

// ── Dibujo Cairo ──────────────────────────────────────────────────────────────

fn draw_knob(cr: &cairo::Context, w: i32, h: i32, s: &KnobState) {
    let cx  = w as f64 / 2.0;
    let cy  = (h - 16) as f64 / 2.0 + 8.0;
    let r   = ((KNOB_SIZE as f64 / 2.0) - 4.0).min(cx - 2.0).min(cy - 2.0);

    // Fondo del knob
    cr.set_source_rgb(0.22, 0.22, 0.26);
    cr.arc(cx, cy, r, 0.0, std::f64::consts::TAU);
    let _ = cr.fill_preserve();
    cr.set_source_rgb(0.38, 0.38, 0.44);
    cr.set_line_width(1.5);
    let _ = cr.stroke();

    // Arco de rango (fondo gris)
    let ra = r - 5.0;
    cr.set_source_rgb(0.15, 0.15, 0.18);
    cr.set_line_width(4.0);
    cr.arc(cx, cy, ra, ARC_START, ARC_END);
    let _ = cr.stroke();

    // Arco de valor (ámbar)
    let t = ((s.value - s.min) / (s.max - s.min)) as f64;
    let angle_val = ARC_START + t * (ARC_END - ARC_START);
    if angle_val > ARC_START + 0.02 {
        cr.set_source_rgb(0.91, 0.51, 0.04);
        cr.set_line_width(4.0);
        cr.arc(cx, cy, ra, ARC_START, angle_val);
        let _ = cr.stroke();
    }

    // Indicador central
    cr.set_source_rgb(0.95, 0.95, 0.98);
    cr.set_line_width(2.0);
    cr.move_to(cx, cy);
    cr.line_to(cx + (r - 8.0) * angle_val.cos(), cy + (r - 8.0) * angle_val.sin());
    let _ = cr.stroke();

    // Etiqueta nombre (arriba)
    cr.set_source_rgb(0.55, 0.60, 0.65);
    cr.select_font_face("Monospace", cairo::FontSlant::Normal, cairo::FontWeight::Normal);
    cr.set_font_size(8.5);
    let lbl = &s.label;
    let x_lbl = if let Ok(e) = cr.text_extents(lbl) { cx - e.width()/2.0 } else { 2.0 };
    cr.move_to(x_lbl, 10.0);
    let _ = cr.show_text(lbl);

    // Valor numérico (abajo)
    let val_str = fmt_val(s.value, &s.unit);
    cr.set_source_rgb(0.91, 0.51, 0.04);
    cr.set_font_size(8.0);
    let x_val = if let Ok(e) = cr.text_extents(&val_str) { cx - e.width()/2.0 } else { 2.0 };
    cr.move_to(x_val, h as f64 - 2.0);
    let _ = cr.show_text(&val_str);
}

fn fmt_val(v: f32, unit: &str) -> String {
    if unit == ":1" {
        if v >= 10.0 { format!("{:.0}:1", v) } else { format!("{:.1}:1", v) }
    } else if unit == "ms" {
        if v >= 100.0 { format!("{:.0}ms", v) } else { format!("{:.1}ms", v) }
    } else {
        format!("{:.1}{}", v, unit)
    }
}
