use std::time::{Duration, Instant};

use smithay_client_toolkit::{
    compositor::{CompositorHandler, CompositorState},
    delegate_compositor, delegate_layer, delegate_output, delegate_registry, delegate_shm,
    globals::GlobalData,
    output::{OutputHandler, OutputState},
    registry::{ProvidesRegistryState, RegistryState},
    registry_handlers,
    shell::{
        wlr_layer::{
            Anchor, KeyboardInteractivity, Layer, LayerShell, LayerShellHandler,
            LayerSurface, LayerSurfaceConfigure,
        },
        WaylandSurface,
    },
    shm::{slot::SlotPool, Shm, ShmHandler},
};
use wayland_client::{
    globals::GlobalList,
    protocol::{wl_output, wl_pointer, wl_seat, wl_shm, wl_surface},
    Connection, Dispatch, QueueHandle, WEnum,
};

use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping, SwashCache, Wrap};

use crate::output::Source;
use crate::pipeline::Pipeline;
use crate::Config;

// ────────────────────── 曲线 & 缓动 ──────────────────────

#[derive(Clone, Copy)]
enum Curve {
    Linear,
    Log,
    Srgb,
}

impl Curve {
    fn from_config(s: &str) -> Self {
        match s {
            "log" => Curve::Log,
            "srgb" => Curve::Srgb,
            _ => Curve::Linear,
        }
    }

    fn apply(self, ratio: f64) -> f64 {
        match self {
            Curve::Linear => ratio,
            Curve::Log => ratio.powf(3.0),
            Curve::Srgb => ratio.powf(2.2),
        }
    }

    fn reverse(self, ratio: f64) -> f64 {
        match self {
            Curve::Linear => ratio,
            Curve::Log => ratio.powf(1.0 / 3.0),
            Curve::Srgb => ratio.powf(1.0 / 2.2),
        }
    }
}

#[derive(Clone, Copy)]
enum Easing {
    Linear,
    EaseOut,
    EaseInOut,
}

impl Easing {
    fn from_config(s: &str) -> Self {
        match s {
            "ease-out" => Easing::EaseOut,
            "ease-in-out" => Easing::EaseInOut,
            _ => Easing::Linear,
        }
    }

    fn sample(self, t: f64) -> f64 {
        match self {
            Easing::Linear => t,
            Easing::EaseOut => 1.0 - (1.0 - t).powi(2),
            Easing::EaseInOut => {
                if t < 0.5 {
                    2.0 * t * t
                } else {
                    1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
                }
            }
        }
    }
}

// ────────────────────── 动画状态 ──────────────────────

struct Animation {
    from: f64,
    to: f64,
    start: Instant,
    duration: Duration,
    easing: Easing,
}

impl Animation {
    fn sample(&self) -> (f64, bool) {
        let elapsed = self.start.elapsed();
        let total = self.duration.as_secs_f64();
        let done = elapsed >= self.duration;
        let t = if done || total < f64::EPSILON {
            1.0
        } else {
            (elapsed.as_secs_f64() / total).clamp(0.0, 1.0)
        };
        let eased = self.easing.sample(t);
        (self.from + (self.to - self.from) * eased, done)
    }
}

// ────────────────────── 标签渲染器 ──────────────────────

struct LabelRenderer {
    font_system: FontSystem,
    swash_cache: SwashCache,
    text_color: u32,
    bg_color: u32,
}

impl LabelRenderer {
    fn new(text_color: u32, bg_color: u32) -> Self {
        let mut db = fontdb::Database::new();

        for pattern in &["sans:lang=zh", "sans-serif", "sans"] {
            if let Ok(out) = std::process::Command::new("fc-match")
                .args(["--format", "%{file}", pattern])
                .output()
            {
                let path = String::from_utf8_lossy(&out.stdout);
                let path = path.trim();
                if !path.is_empty() && db.load_font_file(path).is_ok() {
                    break;
                }
            }
        }

        if db.faces().count() == 0 {
            db.load_system_fonts();
        }

        let font_system = FontSystem::new_with_locale_and_db("en".to_string(), db);
        Self {
            font_system,
            swash_cache: SwashCache::new(),
            text_color,
            bg_color,
        }
    }

    fn measure_width(&mut self, text: &str, _width: usize, height: usize, _scale: f32) -> usize {
        let font_size = (height as f32 * 0.5).max(8.0);
        let line_h = font_size * 1.4;
        let metrics = Metrics::new(font_size, line_h);
        let mut buffer = Buffer::new(&mut self.font_system, metrics);
        buffer.set_size(&mut self.font_system, Some(10000.0), Some(10000.0));
        let attrs = Attrs::new().family(Family::SansSerif);
        buffer.set_text(&mut self.font_system, text, attrs, Shaping::Advanced);
        buffer.shape_until_scroll(&mut self.font_system, false);

        let mut max_x = 0.0f32;
        for run in buffer.layout_runs() {
            for glyph in run.glyphs.iter() {
                let physical = glyph.physical((0.0, run.line_y), 1.0);
                let right = physical.x as f32 + glyph.w;
                if right > max_x { max_x = right; }
            }
        }
        (max_x.ceil() as usize) + 16
    }

    fn render(&mut self, text: &str, width: usize, height: usize, _scale: f32) -> Vec<u8> {
        let font_size = (height as f32 * 0.5).max(8.0);
        let line_h = font_size * 1.4;
        let metrics = Metrics::new(font_size, line_h);

        let mut buffer = Buffer::new(&mut self.font_system, metrics);
        buffer.set_size(&mut self.font_system, Some(10000.0), Some(10000.0));
        buffer.set_wrap(&mut self.font_system, Wrap::None);

        let attrs = Attrs::new().family(Family::SansSerif);
        buffer.set_text(&mut self.font_system, text, attrs, Shaping::Advanced);
        buffer.shape_until_scroll(&mut self.font_system, false);

        let stride = width * 4;
        let mut pixels = vec![0u8; height * stride];
        let bg_r = ((self.bg_color >> 16) & 0xFF) as u8;
        let bg_g = ((self.bg_color >> 8) & 0xFF) as u8;
        let bg_b = (self.bg_color & 0xFF) as u8;
        let bg_a = ((self.bg_color >> 24) & 0xFF) as u8;
        let bg = [bg_b, bg_g, bg_r, bg_a];
        for chunk in pixels.chunks_exact_mut(4) {
            chunk.copy_from_slice(&bg);
        }

        let tc_r = ((self.text_color >> 16) & 0xFF) as u8;
        let tc_g = ((self.text_color >> 8) & 0xFF) as u8;
        let tc_b = (self.text_color & 0xFF) as u8;
        let text_y_offset = ((line_h - font_size) / 2.0).round() as i32;
        let left_pad = 8i32;

        for run in buffer.layout_runs() {
            for glyph in run.glyphs.iter() {
                let physical = glyph.physical((0.0, run.line_y), 1.0);
                if let Some(image) = self.swash_cache.get_image(&mut self.font_system, physical.cache_key) {
                    let placement = image.placement;
                    let px = physical.x + placement.left + left_pad;
                    let py = physical.y - placement.top + text_y_offset;

                    for yy in 0..placement.height as i32 {
                        for xx in 0..placement.width as i32 {
                            let tx = px + xx;
                            let ty = py + yy;
                            if tx < 0 || ty < 0 || tx >= width as i32 || ty >= height as i32 { continue; }
                            let idx = (ty as usize * stride) + (tx as usize * 4);
                            let img_idx = yy as usize * placement.width as usize + xx as usize;
                            if img_idx >= image.data.len() { continue; }

                            let alpha = image.data[img_idx];
                            if alpha > 0 {
                                let a = alpha as f32 / 255.0;
                                let bg_b = pixels[idx] as f32;
                                let bg_g = pixels[idx + 1] as f32;
                                let bg_r = pixels[idx + 2] as f32;
                                pixels[idx]     = (bg_b * (1.0 - a) + tc_b as f32 * a) as u8;
                                pixels[idx + 1] = (bg_g * (1.0 - a) + tc_g as f32 * a) as u8;
                                pixels[idx + 2] = (bg_r * (1.0 - a) + tc_r as f32 * a) as u8;
                            }
                        }
                    }
                }
            }
        }

        pixels
    }

}

// ─────────────────── Ticker 结构体 ──────────────────────

pub struct Ticker {
    registry: RegistryState,
    output_state: OutputState,
    compositor: CompositorState,
    shm: Shm,
    #[allow(dead_code)]
    seat: wl_seat::WlSeat,
    pointer: Option<wl_pointer::WlPointer>,

    layer: Option<LayerSurface>,
    pool: Option<SlotPool>,
    current_buffer: Option<smithay_client_toolkit::shm::slot::Buffer>,
    width: u32,
    height: u32,
    config_width: u32,
    config_height: u32,

    value: f64,
    min_val: f64,
    max_val: f64,
    slider_padding: u32,
    curve: Curve,
    easing: Easing,
    touchable: bool,
    axis_accumulator: f64,

    pointer_x: f64,
    pointer_y: f64,
    dragging: bool,
    right_dragging: bool,
    drag_start_x: f64,
    drag_start_y: f64,
    drag_start_ratio: f64,
    visual_ratio: f64,

    pipeline: Pipeline,
    last_input: Instant,
    dirty: bool,
    persist: bool,

    label_renderer: Option<LabelRenderer>,
    label_text: Option<String>,
    label_pixels: Option<Vec<u8>>,
    label_width: u32,
    label_height: u32,
    scale: f32,
    vertical: bool,
    fill_color: u32,
    track_color: u32,
    bg_color: u32,
    inotify_fd: i32,
    file_path: Option<String>,
    anim: Option<Animation>,
    last_notify_time: Option<Instant>,
}

delegate_compositor!(Ticker);
delegate_shm!(Ticker);
delegate_layer!(Ticker);
delegate_output!(Ticker);
delegate_registry!(Ticker);

// ────────────────────── Wayland handlers ──────────────────────

impl CompositorHandler for Ticker {
    fn scale_factor_changed(&mut self, _: &Connection, _: &QueueHandle<Self>,
        _: &wl_surface::WlSurface, scale: i32)
    {
        self.scale = scale as f32;
        self.reconfigure();
    }
    fn transform_changed(&mut self, _: &Connection, _: &QueueHandle<Self>,
        _: &wl_surface::WlSurface, _: wl_output::Transform) {}

    fn frame(&mut self, _: &Connection, qh: &QueueHandle<Self>,
        _: &wl_surface::WlSurface, _: u32)
    {
        if !self.persist {
            if !self.dragging && self.anim.is_none()
                && self.last_input.elapsed() > Duration::from_secs(1)
            {
                std::process::exit(0);
            }
            if !self.dragging {
                if let Some(layer) = &self.layer {
                    let s = layer.wl_surface();
                    s.frame(qh, s.clone());
                }
            }
        }
    }

    fn surface_enter(&mut self, _: &Connection, _: &QueueHandle<Self>,
        _: &wl_surface::WlSurface, _: &wl_output::WlOutput) {}
    fn surface_leave(&mut self, _: &Connection, _: &QueueHandle<Self>,
        _: &wl_surface::WlSurface, _: &wl_output::WlOutput) {}
}

impl LayerShellHandler for Ticker {
    fn configure(&mut self, _: &Connection, _: &QueueHandle<Self>,
        _: &LayerSurface, configure: LayerSurfaceConfigure, _: u32)
    {
        let (w, h) = configure.new_size;
        self.width = if w == 0 { self.config_width } else { w };
        self.height = if h == 0 { self.config_height } else { h };
        self.reconfigure();
    }

    fn closed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &LayerSurface) {
        std::process::exit(0);
    }
}

impl OutputHandler for Ticker {
    fn output_state(&mut self) -> &mut OutputState { &mut self.output_state }
    fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
    fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
    fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
}

impl ShmHandler for Ticker {
    fn shm_state(&mut self) -> &mut Shm { &mut self.shm }
}

impl ProvidesRegistryState for Ticker {
    fn registry(&mut self) -> &mut RegistryState { &mut self.registry }
    registry_handlers![OutputState];
}

// ────────────────────── Input dispatch ──────────────────────

impl Dispatch<wl_seat::WlSeat, GlobalData> for Ticker {
    fn event(
        state: &mut Ticker,
        seat: &wl_seat::WlSeat,
        event: wl_seat::Event,
        _: &GlobalData,
        _: &Connection,
        qh: &QueueHandle<Ticker>,
    ) {
        if let wl_seat::Event::Capabilities { capabilities } = event {
            if let WEnum::Value(caps) = capabilities {
                if caps.contains(wl_seat::Capability::Pointer) {
                    state.pointer = Some(seat.get_pointer(qh, ()));
                }
            }
        }
    }
}

impl Dispatch<wl_pointer::WlPointer, ()> for Ticker {
    fn event(
        state: &mut Ticker,
        _: &wl_pointer::WlPointer,
        event: wl_pointer::Event,
        _: &(),
        _: &Connection,
        qh: &QueueHandle<Ticker>,
    ) {
        match event {
            wl_pointer::Event::Enter { surface_x, surface_y, .. } => {
                state.pointer_x = surface_x;
                state.pointer_y = surface_y;
                state.last_input = Instant::now();
            }
            wl_pointer::Event::Motion { surface_x, surface_y, .. } => {
                state.pointer_x = surface_x;
                state.pointer_y = surface_y;
                state.last_input = Instant::now();
                if state.touchable {
                    if state.dragging {
                        state.update_value_from_pointer();
                    }
                    if state.right_dragging {
                        state.update_value_relative();
                    }
                }
            }
            wl_pointer::Event::Button { button, state: btn_state, .. } => {
                match btn_state {
                    WEnum::Value(wl_pointer::ButtonState::Pressed) => {
                        if state.touchable {
                            if button == 0x110 {
                                state.dragging = true;
                                state.anim = None;
                                state.last_input = Instant::now();
                                state.update_value_from_pointer();
                            } else if button == 0x111 {
                                state.right_dragging = true;
                                state.drag_start_x = state.pointer_x;
                                state.drag_start_y = state.pointer_y;
                                state.drag_start_ratio = state.visual_ratio;
                                state.last_input = Instant::now();
                            }
                        }
                    }
                    WEnum::Value(wl_pointer::ButtonState::Released) => {
                        if state.touchable {
                            if button == 0x110 {
                                state.dragging = false;
                                state.last_input = Instant::now();
                                state.pipeline.process_raw(state.value, true, Source::User);
                                state.dirty = true;

                                if !state.persist {
                                    if let Some(layer) = &state.layer {
                                        let s = layer.wl_surface();
                                        s.frame(qh, s.clone());
                                    }
                                }
                            } else if button == 0x111 {
                                state.right_dragging = false;
                                state.last_input = Instant::now();
                                state.pipeline.process_raw(state.value, true, Source::User);
                                state.dirty = true;

                                if !state.persist {
                                    if let Some(layer) = &state.layer {
                                        let s = layer.wl_surface();
                                        s.frame(qh, s.clone());
                                    }
                                }
                            }
                        }
                    }
                    _ => {}
                }
            }
            wl_pointer::Event::Leave { .. } => {
                state.dragging = false;
                state.right_dragging = false;
                state.axis_accumulator = 0.0;
            }
            wl_pointer::Event::AxisValue120 { axis, value120, .. } => {
                if let WEnum::Value(wl_pointer::Axis::VerticalScroll) = axis {
                    if state.touchable {
                        let direction = if value120 > 0 { -1.0 } else { 1.0 };
                        let steps = (value120 / 120).abs() as f64;
                        let new_raw = state.value + direction * state.pipeline.step * steps;
                        state.pipeline.process_raw(new_raw, false, Source::User);
                        state.value = state.pipeline.current_value;
                        state.sync_visual_ratio();
                        state.dirty = true;
                        state.last_input = Instant::now();
                    }
                }
            }
            wl_pointer::Event::Axis { axis, value, .. } => {
                if let WEnum::Value(wl_pointer::Axis::VerticalScroll) = axis {
                    if state.touchable {
                        state.axis_accumulator += value;
                        let threshold = 15.0;
                        while state.axis_accumulator >= threshold {
                            state.axis_accumulator -= threshold;
                            let new_raw = state.value - state.pipeline.step;
                            state.pipeline.process_raw(new_raw, false, Source::User);
                        }
                        while state.axis_accumulator <= -threshold {
                            state.axis_accumulator += threshold;
                            let new_raw = state.value + state.pipeline.step;
                            state.pipeline.process_raw(new_raw, false, Source::User);
                        }
                        state.value = state.pipeline.current_value;
                        state.sync_visual_ratio();
                        state.dirty = true;
                        state.last_input = Instant::now();
                    }
                }
            }
            _ => {}
        }
    }
}

// ───────────────── Ticker 核心实现 ──────────────────────

impl Ticker {
    pub fn new(globals: &GlobalList, qh: &QueueHandle<Self>, config: Config, pipeline: Pipeline, watch_file: Option<String>) -> Self {
        let registry = RegistryState::new(globals);
        let output_state = OutputState::new(globals, qh);
        let compositor = CompositorState::bind(globals, qh).expect("compositor");
        let shm = Shm::bind(globals, qh).expect("shm");
        let layer_shell = LayerShell::bind(globals, qh).expect("layer-shell");
        let seat: wl_seat::WlSeat = globals.bind(qh, 1..=9, GlobalData).expect("seat");

        let surface = compositor.create_surface(qh);
        let layer = layer_shell.create_layer_surface(
            qh, surface, Layer::Overlay, Some("ticker"), None,
        );

        // ── 锚点与堆叠 ──
        let anchor_flags = match config.anchor.as_str() {
            "top-left"      => Anchor::TOP | Anchor::LEFT,
            "top-right"     => Anchor::TOP | Anchor::RIGHT,
            "top-center"    => Anchor::TOP,
            "bottom-left"   => Anchor::BOTTOM | Anchor::LEFT,
            "bottom-right"  => Anchor::BOTTOM | Anchor::RIGHT,
            "bottom-center" => Anchor::BOTTOM,
            _               => Anchor::TOP,
        };
        layer.set_anchor(anchor_flags);
        layer.set_size(config.width, config.height);
        layer.set_exclusive_zone(-1);
        layer.set_keyboard_interactivity(KeyboardInteractivity::None);

        layer.wl_surface().commit();

        // ── 标签 ──
        let label_text = config.label.filter(|l| !l.is_empty());
        let scale = 1.0f32;

        let (label_renderer, label_width, label_pixels) = if label_text.is_some() {
            (Some(LabelRenderer::new(config.text_color, config.bg_color)), 0, None)
        } else {
            (None, 0, None)
        };

        // ── 曲线 & 缓动 ──
        let curve = Curve::from_config(&config.curve);
        let easing = Easing::from_config(&config.anim);

        // ── 初始值与视觉位置 ──
        let value = pipeline.current_value;
        let visual_ratio = if config.max > config.min {
            curve.reverse((value - config.min) / (config.max - config.min))
        } else { 0.0 };

        // ── margin 逻辑 ──
        if config.vertical {
            let offset = config.slot as i32 * (config.width as i32 + config.gap as i32);
            if config.anchor.contains("right") {
                layer.set_margin(0, offset, 0, 0);
            } else {
                layer.set_margin(0, 0, 0, offset);
            }
        } else {
            let offset = config.slot as i32 * (config.height as i32 + config.gap as i32);
            if config.anchor.starts_with("bottom") {
                layer.set_margin(0, 0, offset, 0);
            } else {
                layer.set_margin(offset, 0, 0, 0);
            }
        }

        // inotify 初始化
        let mut inotify_fd = -1i32;
        if let Some(path) = &watch_file {
            unsafe {
                let fd = libc::inotify_init1(libc::IN_NONBLOCK);
                if fd >= 0 {
                    if let Ok(cpath) = std::ffi::CString::new(path.as_str()) {
                        let wd = libc::inotify_add_watch(fd, cpath.as_ptr(), libc::IN_MODIFY);
                        if wd >= 0 {
                            inotify_fd = fd;
                        } else {
                            libc::close(fd);
                        }
                    } else {
                        libc::close(fd);
                    }
                }
            }
        }

        Self {
            registry, output_state, compositor, shm, seat,
            pointer: None,
            layer: Some(layer),
            pool: None, current_buffer: None,
            width: 0, height: 0,
            config_width: config.width,
            config_height: config.height,
            value,
            min_val: config.min,
            max_val: config.max,
            slider_padding: 4,
            curve,
            easing,
            label_height: 0,
            vertical: config.vertical,
            fill_color: config.fill_color,
            track_color: config.track_color,
            bg_color: config.bg_color,
            pointer_x: 0.0,
            pointer_y: 0.0,
            dragging: false,
            touchable: config.touchable,
            axis_accumulator: 0.0,
            right_dragging: false,
            drag_start_x: 0.0,
            drag_start_y: 0.0,
            drag_start_ratio: 0.0,
            visual_ratio,
            pipeline,
            last_input: Instant::now(),
            dirty: true,
            persist: config.persist,
            label_renderer,
            label_text,
            label_pixels,
            label_width,
            scale,
            inotify_fd,
            file_path: watch_file,
            anim: None,
            last_notify_time: None,
        }
    }

    pub fn process(&mut self) {
        if let Some(a) = self.anim.take() {
            let (v, done) = a.sample();
            let value = v.clamp(self.min_val, self.max_val);
            self.value = value;
            self.pipeline.current_value = value;
            self.sync_visual_ratio();
            self.dirty = true;
            if !done {
                self.anim = Some(a);
            }
        }

        self.pipeline.flush_pending();
        if self.dirty {
            self.render();
            self.dirty = false;
        }
    }

    fn update_value_from_pointer(&mut self) {
        let scale = self.scale.max(1.0) as f64;
        let pad = self.slider_padding as f64;

        let ratio = if self.vertical {
            let label_h_logical = self.label_height as f64 / scale;
            let track_top = label_h_logical + pad;
            let track_bottom = self.height as f64 - pad;
            let track_h = (track_bottom - track_top).max(0.0);
            if track_h <= 0.0 { return; }
            ((track_bottom - self.pointer_y) / track_h).clamp(0.0, 1.0)
        } else {
            let lw_logical = self.label_width as f64 / scale;
            let track_left = lw_logical + pad;
            let track_right = self.width as f64 - pad;
            let track_w = (track_right - track_left).max(0.0);
            if track_w <= 0.0 { return; }
            ((self.pointer_x - track_left) / track_w).clamp(0.0, 1.0)
        };

        self.visual_ratio = ratio;

        let curved_ratio = self.curve.apply(ratio);
        let raw = self.min_val + curved_ratio * (self.max_val - self.min_val);

        self.pipeline.process_raw(raw, false, Source::User);
        self.value = self.pipeline.current_value;

        self.dirty = true;
    }

    fn update_value_relative(&mut self) {
        let scale = self.scale.max(1.0) as f64;
        let pad = self.slider_padding as f64;

        let delta_ratio = if self.vertical {
            let label_h_logical = self.label_height as f64 / scale;
            let track_top = label_h_logical + pad;
            let track_bottom = self.height as f64 - pad;
            let track_h = (track_bottom - track_top).max(1.0);
            (self.drag_start_y - self.pointer_y) / track_h
        } else {
            let lw_logical = self.label_width as f64 / scale;
            let track_left = lw_logical + pad;
            let track_right = self.width as f64 - pad;
            let track_w = (track_right - track_left).max(1.0);
            (self.pointer_x - self.drag_start_x) / track_w
        };

        let new_ratio = (self.drag_start_ratio + delta_ratio).clamp(0.0, 1.0);
        self.visual_ratio = new_ratio;

        let curved_ratio = self.curve.apply(new_ratio);
        let raw = self.min_val + curved_ratio * (self.max_val - self.min_val);

        self.pipeline.process_raw(raw, false, Source::User);
        self.value = self.pipeline.current_value;

        self.dirty = true;
    }

    fn sync_visual_ratio(&mut self) {
        let range = self.max_val - self.min_val;
        if range > 0.0 {
            self.visual_ratio = self.curve.reverse(
                (self.value - self.min_val) / range,
            );
        }
    }

    fn render(&mut self) {
        let layer = match &self.layer { Some(l) => l, None => return };
        let pool = match &mut self.pool { Some(p) => p, None => return };
        if self.width == 0 || self.height == 0 { return; }

        let scale = self.scale.max(1.0);
        let w = (self.width as f32 * scale) as usize;
        let h = (self.height as f32 * scale) as usize;

        self.current_buffer = None;

        let stride = w * 4;
        let padding = (self.slider_padding as f32 * scale) as usize;

        let (buf, canvas) = match pool.create_buffer(
            w as i32, h as i32, stride as i32, wl_shm::Format::Argb8888
        ) {
            Ok(b) => b,
            Err(_) => return,
        };

        let bg_r = ((self.bg_color >> 16) & 0xFF) as u8;
        let bg_g = ((self.bg_color >> 8) & 0xFF) as u8;
        let bg_b = (self.bg_color & 0xFF) as u8;
        let bg_a = ((self.bg_color >> 24) & 0xFF) as u8;
        let bg_pixel = [bg_b, bg_g, bg_r, bg_a];
        for chunk in canvas.chunks_exact_mut(4) {
            chunk.copy_from_slice(&bg_pixel);
        }

        fill_slider(canvas, w, h, stride, padding,
                    if self.vertical { 0 } else { self.label_width as usize },
                    if self.vertical { self.label_height as usize } else { 0 },
                    self.visual_ratio, self.vertical,
                    self.fill_color, self.track_color);

        if let (Some(ref label_img), lw) = (&self.label_pixels, self.label_width as usize) {
            let lw_bytes = lw * 4;
            let label_h = label_img.len() / lw_bytes.max(1);
            let copy_h = h.min(label_h);
            for y in 0..copy_h {
                let dst = y * stride;
                let src = y * lw_bytes;
                if dst + lw_bytes <= canvas.len() && src + lw_bytes <= label_img.len() {
                    canvas[dst..dst + lw_bytes]
                        .copy_from_slice(&label_img[src..src + lw_bytes]);
                }
            }
        }

        let s = layer.wl_surface();
        s.damage_buffer(0, 0, w as i32, h as i32);
        s.attach(Some(buf.wl_buffer()), 0, 0);
        s.commit();
        self.current_buffer = Some(buf);
    }

    fn reconfigure(&mut self) {
        let scale = self.scale.max(1.0);
        let scale_i = scale as i32;

        if let Some(layer) = &self.layer {
            layer.wl_surface().set_buffer_scale(scale_i);
        }

        let phys_w = (self.width as f32 * scale) as u32;
        let phys_h = (self.height as f32 * scale) as u32;
        let size = (phys_w * phys_h * 4) as usize;
        if size > 0 {
            let needs_new = match &self.pool {
                Some(p) => p.len() < size,
                None => true,
            };
            if needs_new {
                if let Ok(p) = SlotPool::new(size, &self.shm) {
                    self.pool = Some(p);
                }
            }
        }

        if phys_h > 0 {
            if let (Some(lr), Some(t)) = (&mut self.label_renderer, &self.label_text) {
                if self.vertical {
                    let label_render_h = (phys_w as f32 * 0.75).max(16.0) as usize;
                    let lw = lr.measure_width(t, phys_w as usize, label_render_h, scale);
                    let lw = lw.min(phys_w as usize);
                    self.label_width = lw as u32;
                    self.label_height = label_render_h as u32;
                    self.label_pixels = Some(lr.render(t, lw, label_render_h, scale));
                } else {
                    let lw = lr.measure_width(t, phys_w as usize, phys_h as usize, scale);
                    let lw = lw.min(phys_w as usize);
                    self.label_width = lw as u32;
                    self.label_height = 0;
                    self.label_pixels = Some(lr.render(t, lw, phys_h as usize, scale));
                }
            }
        }

        self.dirty = true;
    }

    pub fn get_inotify_fd(&self) -> i32 {
        self.inotify_fd
    }

    pub fn handle_inotify_event(&mut self) {
        if self.dragging || self.right_dragging {
            let mut buf = [0u8; 4096];
            unsafe { libc::read(self.inotify_fd, buf.as_mut_ptr() as *mut _, buf.len()); }
            return;
        }

        let mut buf = [0u8; 4096];
        let n = unsafe { libc::read(self.inotify_fd, buf.as_mut_ptr() as *mut _, buf.len()) };
        if n <= 0 { return; }

        if let Some(fpath) = &self.file_path {
            if let Ok(content) = std::fs::read_to_string(fpath) {
                if let Ok(raw) = content.trim().parse::<f64>() {
                    let stepped = (raw / self.pipeline.step).round() * self.pipeline.step;
                    let clamped = stepped.clamp(self.min_val, self.max_val);

                    // 只要有合法输入就更新 last_input，防心跳误杀
                    self.last_input = Instant::now();

                    if (clamped - self.value).abs() > f64::EPSILON {
                        match self.last_notify_time {
                            None => {
                                self.anim = None;
                                self.pipeline.process_raw(clamped, true, Source::Inotify);
                                self.value = self.pipeline.current_value;
                                self.sync_visual_ratio();
                                self.dirty = true;
                            }
                            Some(prev) => {
                                let dt = prev.elapsed().as_secs_f64();
                                if dt < 0.033 {
                                    self.anim = None;
                                    self.pipeline.process_raw(clamped, true, Source::Inotify);
                                    self.value = self.pipeline.current_value;
                                    self.sync_visual_ratio();
                                } else {
                                    let base_dur = Duration::from_secs_f64(
                                        dt.clamp(0.050, 0.300)
                                    );
                                    let dur = if let Some(ref a) = self.anim {
                                        let elapsed = a.start.elapsed();
                                        a.duration.saturating_sub(elapsed)
                                            .max(Duration::from_millis(50))
                                    } else {
                                        base_dur
                                    };
                                    self.anim = Some(Animation {
                                        from: self.value,
                                        to: clamped,
                                        start: Instant::now(),
                                        duration: dur,
                                        easing: self.easing,
                                    });
                                }
                                self.dirty = true;
                            }
                        }
                        self.last_notify_time = Some(Instant::now());
                    }
                }
            }
        }
    }
}

impl Drop for Ticker {
    fn drop(&mut self) {
        if self.inotify_fd >= 0 {
            unsafe { libc::close(self.inotify_fd); }
        }
    }
}

// ────────────────────── 滑块填充 ──────────────────────

fn fill_slider(canvas: &mut [u8], w: usize, h: usize, stride: usize,
               pad: usize, label_w: usize, label_h: usize, ratio: f64, vertical: bool,
               fill_color: u32, track_color: u32)
{
    let tk_b = (track_color & 0xFF) as u8;
    let tk_g = ((track_color >> 8) & 0xFF) as u8;
    let tk_r = ((track_color >> 16) & 0xFF) as u8;
    let tk_a = ((track_color >> 24) & 0xFF) as u8;
    let track = [tk_b, tk_g, tk_r, tk_a];

    let fl_b = (fill_color & 0xFF) as u8;
    let fl_g = ((fill_color >> 8) & 0xFF) as u8;
    let fl_r = ((fill_color >> 16) & 0xFF) as u8;
    let fl_a = ((fill_color >> 24) & 0xFF) as u8;
    let fill = [fl_b, fl_g, fl_r, fl_a];

    if vertical {
        let track_left = pad;
        let track_right = w.saturating_sub(pad);
        let track_top = label_h + pad;
        let track_bottom = h.saturating_sub(pad);
        let track_h = track_bottom.saturating_sub(track_top);
        if track_h == 0 || track_left >= track_right { return; }
        let fill_h = (ratio.clamp(0.0, 1.0) * track_h as f64) as usize;
        let fill_top = track_bottom.saturating_sub(fill_h);

        for y in track_top..track_bottom {
            let row_start = y * stride;
            let color = if y >= fill_top { &fill } else { &track };
            for chunk in canvas[row_start + track_left * 4..row_start + track_right * 4]
                .chunks_exact_mut(4)
            {
                chunk.copy_from_slice(color);
            }
        }
    } else {
        let track_left = label_w + pad;
        let track_right = w.saturating_sub(pad);
        let track_w = track_right.saturating_sub(track_left);
        let track_top = pad;
        let track_bottom = h.saturating_sub(pad);
        if track_w == 0 || track_top >= track_bottom || track_left >= w { return; }
        let fill_end = track_left + (ratio.clamp(0.0, 1.0) * track_w as f64) as usize;

        for y in track_top..track_bottom {
            let row_start = y * stride;
            let track_row = row_start + track_left * 4;
            let fill_stop = row_start + fill_end.min(track_right) * 4;
            let track_stop = row_start + track_right * 4;

            for chunk in canvas[track_row..fill_stop].chunks_exact_mut(4) {
                chunk.copy_from_slice(&fill);
            }
            for chunk in canvas[fill_stop..track_stop].chunks_exact_mut(4) {
                chunk.copy_from_slice(&track);
            }
        }
    }
}
