use iced::widget::{container, slider, vertical_slider};
use iced::{Element, Theme, widget};
use serde_json::Value;

use crate::PlushieRenderer;
use crate::a11y::A11yOverrides;
use crate::iced_convert;
use crate::message::Message;
use crate::protocol::TreeNode;
use crate::registry::PlushieWidget;
use crate::render_ctx::RenderCtx;
use crate::widget::helpers::*;

use plushie_core::types::{Color, Length, PlushieType, Style as CoreStyle};

// ---------------------------------------------------------------------------
// Shared helpers
// ---------------------------------------------------------------------------

/// Apply rail color/width overrides to a slider or vertical_slider style.
/// Shared by both slider variants since they use the same `Rail` type.
fn apply_rail_overrides(
    style: &mut slider::Style,
    rail_color: Option<iced::Color>,
    rail_width: Option<f32>,
) {
    if let Some(rc) = rail_color {
        style.rail.backgrounds = (iced::Background::Color(rc), iced::Background::Color(rc));
    }
    if let Some(rw) = rail_width {
        style.rail.width = rw;
    }
}

/// Handle slide/slide_release events for sliders. Tracks the latest drag
/// value per (window_id, node_id) so slide_release can report the final
/// value (iced's release event doesn't carry the value itself).
fn handle_slider_message(
    last_values: &mut std::collections::HashMap<(String, String), f64>,
    msg: &Message,
) -> crate::registry::HandleResult {
    use crate::registry::HandleResult;
    match msg {
        Message::Event {
            window_id,
            id,
            value,
            family,
        } if family == "slide" => {
            let v = value.as_f64().unwrap_or(0.0);
            last_values.insert((window_id.clone(), id.clone()), v);
            HandleResult::emit(vec![crate::protocol::OutgoingEvent::slide(id.clone(), v)])
        }
        Message::Event {
            window_id,
            id,
            family,
            ..
        } if family == "slide_release" => {
            let key = (window_id.clone(), id.clone());
            let v = last_values.remove(&key).unwrap_or(0.0);
            HandleResult::emit(vec![crate::protocol::OutgoingEvent::slide_release(
                id.clone(),
                v,
            )])
        }
        _ => HandleResult::Fallthrough,
    }
}

fn effective_slider_step(step: Option<f64>, keyboard_step: Option<f64>) -> Option<f64> {
    keyboard_step.or(step).map(|step| step.max(f64::EPSILON))
}

// ---------------------------------------------------------------------------
// SliderWidget (stateful)
// ---------------------------------------------------------------------------

/// Stateful slider factory. Tracks last-emitted value to deduplicate
/// slide events (iced fires on every pixel move).
pub(crate) struct SliderWidget {
    last_values: std::collections::HashMap<(String, String), f64>,
}

impl SliderWidget {
    pub(crate) fn new() -> Self {
        Self {
            last_values: std::collections::HashMap::new(),
        }
    }
}

impl<R: PlushieRenderer> PlushieWidget<R> for SliderWidget {
    fn type_names(&self) -> &[&str] {
        &["slider"]
    }

    fn render<'a>(
        &'a self,
        node: &'a TreeNode,
        ctx: &RenderCtx<'a, R>,
    ) -> Element<'a, Message, Theme, R> {
        render_slider(node, *ctx)
    }

    fn handle_message(&mut self, msg: &Message) -> crate::registry::HandleResult {
        handle_slider_message(&mut self.last_values, msg)
    }

    fn infer_a11y(&self, node: &TreeNode) -> Option<A11yOverrides> {
        A11yOverrides::from_mnemonic_props(&node.props)
    }

    fn prune_stale(&mut self, live_ids: &std::collections::HashSet<(String, String)>) {
        self.last_values.retain(|k, _| live_ids.contains(k));
    }

    fn fresh_for_session(&self) -> Box<dyn PlushieWidget<R>> {
        Box::new(SliderWidget::new())
    }
}

// ---------------------------------------------------------------------------
// VerticalSliderWidget (stateful)
// ---------------------------------------------------------------------------

/// Vertical variant of [`SliderWidget`].
pub(crate) struct VerticalSliderWidget {
    last_values: std::collections::HashMap<(String, String), f64>,
}

impl VerticalSliderWidget {
    pub(crate) fn new() -> Self {
        Self {
            last_values: std::collections::HashMap::new(),
        }
    }
}

impl<R: PlushieRenderer> PlushieWidget<R> for VerticalSliderWidget {
    fn type_names(&self) -> &[&str] {
        &["vertical_slider"]
    }

    fn render<'a>(
        &'a self,
        node: &'a TreeNode,
        ctx: &RenderCtx<'a, R>,
    ) -> Element<'a, Message, Theme, R> {
        render_vertical_slider(node, *ctx)
    }

    fn handle_message(&mut self, msg: &Message) -> crate::registry::HandleResult {
        handle_slider_message(&mut self.last_values, msg)
    }

    fn infer_a11y(&self, node: &TreeNode) -> Option<A11yOverrides> {
        A11yOverrides::from_mnemonic_props(&node.props)
    }

    fn prune_stale(&mut self, live_ids: &std::collections::HashSet<(String, String)>) {
        self.last_values.retain(|k, _| live_ids.contains(k));
    }

    fn fresh_for_session(&self) -> Box<dyn PlushieWidget<R>> {
        Box::new(VerticalSliderWidget::new())
    }
}

// ---------------------------------------------------------------------------
// Render logic
// ---------------------------------------------------------------------------

struct SliderProps {
    value: Option<f64>,
    step: Option<f64>,
    keyboard_step: Option<f64>,
    width: Option<Length>,
    default: Option<f64>,
    shift_step: Option<f64>,
    label: Option<String>,
    rail_color: Option<Color>,
    style: Option<CoreStyle>,
}

impl SliderProps {
    fn from_node(node: &TreeNode) -> Self {
        let p = &node.props;
        Self {
            value: f64::extract(p, "value"),
            step: f64::extract(p, "step"),
            keyboard_step: f64::extract(p, "keyboard_step"),
            width: Length::extract(p, "width"),
            default: f64::extract(p, "default"),
            shift_step: f64::extract(p, "shift_step"),
            label: String::extract(p, "label"),
            rail_color: Color::extract(p, "rail_color"),
            style: CoreStyle::extract(p, "style"),
        }
    }
}

fn render_slider<'a, R: PlushieRenderer>(
    node: &'a TreeNode,
    ctx: RenderCtx<'a, R>,
) -> Element<'a, Message, Theme, R> {
    let props = &node.props;
    let sp = SliderProps::from_node(node);
    let range = prop_range_f64(props);
    let value = sp.value.unwrap_or(*range.start());
    let width = sp
        .width
        .as_ref()
        .map(iced_convert::length)
        .unwrap_or(iced::Length::Fill);
    let id = node.id.clone();
    let release_id = node.id.clone();
    let window_id = ctx.window_id.to_string();
    let release_window_id = window_id.clone();

    let mut s = slider(range, value, move |v| Message::Event {
        window_id: window_id.clone(),
        id: id.clone(),
        value: serde_json::json!(v),
        family: "slide".into(),
    })
    .on_release(Message::Event {
        window_id: release_window_id,
        id: release_id,
        value: Value::Null,
        family: "slide_release".into(),
    })
    .width(width);

    if let Some(st) = effective_slider_step(sp.step, sp.keyboard_step) {
        // iced uses the base step for keyboard movement and pointer snapping.
        // `keyboard_step` deliberately overrides `step` until iced exposes a
        // keyboard-only step.
        s = s.step(st);
    }
    if let Some(d) = sp.default {
        s = s.default(d);
    }
    if let Some(h) = prop_animated_f32(&ctx.caches.interpolated_props, &node.id, props, "height") {
        s = s.height(h);
    }
    if let Some(ss) = sp.shift_step {
        s = s.shift_step(ss);
    }
    if let Some(label) = sp.label {
        s = s.label(label);
    }

    // Rail styling props (applied on top of any style preset)
    let rail_color = sp.rail_color.as_ref().map(iced_convert::color);
    let rail_width = prop_f32(props, "rail_width");
    let has_rail_overrides = rail_color.is_some() || rail_width.is_some();

    // Style with optional circular handle
    let circular = prop_bool_default(props, "circular_handle", false);
    if circular {
        let radius = prop_f32(props, "handle_radius").unwrap_or(8.0);
        s = s.style(move |theme, status| {
            let mut style = slider::default(theme, status).with_circular_handle(radius);
            apply_rail_overrides(&mut style, rail_color, rail_width);
            style
        });
    } else {
        match &sp.style {
            Some(CoreStyle::Preset(name)) => {
                s = match name.as_str() {
                    "default" => {
                        if has_rail_overrides {
                            s.style(move |theme: &iced::Theme, status| {
                                let mut style = slider::default(theme, status);
                                apply_rail_overrides(&mut style, rail_color, rail_width);
                                style
                            })
                        } else {
                            s.style(slider::default)
                        }
                    }
                    _ => {
                        log::warn!(
                            "unknown style {:?} for widget type {:?}, using default",
                            name,
                            "slider"
                        );
                        s
                    }
                };
            }
            Some(CoreStyle::Custom(style_map)) => {
                let ov = style_overrides_from_style_map(&node.id, style_map, ctx.caches);
                s = s.style(move |theme: &iced::Theme, status| {
                    let mut style = slider::default(theme, status);
                    apply_slider_handle_fields(&mut style.handle, &ov.base);
                    apply_rail_overrides(&mut style, rail_color, rail_width);
                    if matches!(status, slider::Status::Hovered) {
                        if let Some(ref f) = ov.hovered {
                            apply_slider_handle_fields(&mut style.handle, f);
                        } else {
                            style.handle.background =
                                deviate_background(style.handle.background, 0.1);
                        }
                    }
                    style
                });
            }
            None => {}
        }
    }
    if !circular && sp.style.is_none() && has_rail_overrides {
        s = s.style(move |theme: &iced::Theme, status| {
            let mut style = slider::default(theme, status);
            apply_rail_overrides(&mut style, rail_color, rail_width);
            style
        });
    }

    {
        let status_wid = ctx.window_id.to_string();
        let status_id = node.id.clone();
        s = s.on_status_change(move |status| Message::Event {
            window_id: status_wid.clone(),
            id: status_id.clone(),
            value: Value::String(status.to_string()),
            family: "status".into(),
        });
    }

    container(s).id(widget::Id::from(node.id.clone())).into()
}

struct VerticalSliderProps {
    value: Option<f64>,
    step: Option<f64>,
    keyboard_step: Option<f64>,
    height: Option<Length>,
    default: Option<f64>,
    shift_step: Option<f64>,
    label: Option<String>,
    rail_color: Option<Color>,
    style: Option<CoreStyle>,
}

impl VerticalSliderProps {
    fn from_node(node: &TreeNode) -> Self {
        let p = &node.props;
        Self {
            value: f64::extract(p, "value"),
            step: f64::extract(p, "step"),
            keyboard_step: f64::extract(p, "keyboard_step"),
            height: Length::extract(p, "height"),
            default: f64::extract(p, "default"),
            shift_step: f64::extract(p, "shift_step"),
            label: String::extract(p, "label"),
            rail_color: Color::extract(p, "rail_color"),
            style: CoreStyle::extract(p, "style"),
        }
    }
}

fn render_vertical_slider<'a, R: PlushieRenderer>(
    node: &'a TreeNode,
    ctx: RenderCtx<'a, R>,
) -> Element<'a, Message, Theme, R> {
    let props = &node.props;
    let vp = VerticalSliderProps::from_node(node);
    let range = prop_range_f64(props);
    let value = vp.value.unwrap_or(*range.start());
    let width = prop_animated_f32(&ctx.caches.interpolated_props, &node.id, props, "width");
    let height = vp
        .height
        .as_ref()
        .map(iced_convert::length)
        .unwrap_or(iced::Length::Fill);
    let id = node.id.clone();
    let release_id = node.id.clone();
    let window_id = ctx.window_id.to_string();
    let release_window_id = window_id.clone();

    let mut s = vertical_slider(range, value, move |v| Message::Event {
        window_id: window_id.clone(),
        id: id.clone(),
        value: serde_json::json!(v),
        family: "slide".into(),
    })
    .on_release(Message::Event {
        window_id: release_window_id,
        id: release_id,
        value: Value::Null,
        family: "slide_release".into(),
    })
    .height(height);

    if let Some(w) = width {
        s = s.width(w);
    }

    if let Some(st) = effective_slider_step(vp.step, vp.keyboard_step) {
        s = s.step(st);
    }
    if let Some(d) = vp.default {
        s = s.default(d);
    }
    if let Some(ss) = vp.shift_step {
        s = s.shift_step(ss);
    }
    if let Some(label) = vp.label {
        s = s.label(label);
    }

    // Rail styling props (applied on top of any style preset)
    let rail_color = vp.rail_color.as_ref().map(iced_convert::color);
    let rail_width = prop_f32(props, "rail_width");
    let has_rail_overrides = rail_color.is_some() || rail_width.is_some();

    // Circular handle support (mirrors horizontal slider)
    let circular = prop_bool_default(props, "circular_handle", false);
    if circular {
        let radius = prop_f32(props, "handle_radius").unwrap_or(8.0);
        s = s.style(move |theme, status| {
            let mut style = vertical_slider::default(theme, status).with_circular_handle(radius);
            apply_rail_overrides(&mut style, rail_color, rail_width);
            style
        });
    } else {
        // Style: preset name or custom style map
        match &vp.style {
            Some(CoreStyle::Preset(name)) => {
                s = match name.as_str() {
                    "default" => {
                        if has_rail_overrides {
                            s.style(move |theme: &iced::Theme, status| {
                                let mut style = vertical_slider::default(theme, status);
                                apply_rail_overrides(&mut style, rail_color, rail_width);
                                style
                            })
                        } else {
                            s.style(vertical_slider::default)
                        }
                    }
                    _ => {
                        log::warn!(
                            "unknown style {:?} for widget type {:?}, using default",
                            name,
                            "vertical_slider"
                        );
                        s
                    }
                };
            }
            Some(CoreStyle::Custom(style_map)) => {
                let ov = style_overrides_from_style_map(&node.id, style_map, ctx.caches);
                s = s.style(move |theme: &iced::Theme, status| {
                    let mut style = vertical_slider::default(theme, status);
                    apply_slider_handle_fields(&mut style.handle, &ov.base);
                    apply_rail_overrides(&mut style, rail_color, rail_width);
                    if matches!(status, vertical_slider::Status::Hovered) {
                        if let Some(ref f) = ov.hovered {
                            apply_slider_handle_fields(&mut style.handle, f);
                        } else {
                            style.handle.background =
                                deviate_background(style.handle.background, 0.1);
                        }
                    }
                    style
                });
            }
            None => {}
        }
        if vp.style.is_none() && has_rail_overrides {
            s = s.style(move |theme: &iced::Theme, status| {
                let mut style = vertical_slider::default(theme, status);
                apply_rail_overrides(&mut style, rail_color, rail_width);
                style
            });
        }
    }

    {
        let status_wid = ctx.window_id.to_string();
        let status_id = node.id.clone();
        s = s.on_status_change(move |status| Message::Event {
            window_id: status_wid.clone(),
            id: status_id.clone(),
            value: Value::String(status.to_string()),
            family: "status".into(),
        });
    }

    container(s).id(widget::Id::from(node.id.clone())).into()
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn keyboard_step_overrides_base_step() {
        assert_eq!(effective_slider_step(Some(1.0), Some(5.0)), Some(5.0));
    }

    #[test]
    fn keyboard_step_clamps_to_positive_step() {
        assert_eq!(
            effective_slider_step(Some(1.0), Some(0.0)),
            Some(f64::EPSILON)
        );
        assert_eq!(
            effective_slider_step(Some(1.0), Some(-1.0)),
            Some(f64::EPSILON)
        );
    }

    #[test]
    fn base_step_is_used_without_keyboard_step() {
        assert_eq!(effective_slider_step(Some(2.0), None), Some(2.0));
    }

    fn infer_slider(props: serde_json::Value) -> Option<A11yOverrides> {
        let node = crate::testing::node_with_props("s", "slider", props);
        let widget = SliderWidget::new();
        <SliderWidget as PlushieWidget<iced::Renderer>>::infer_a11y(&widget, &node)
    }

    fn infer_vertical(props: serde_json::Value) -> Option<A11yOverrides> {
        let node = crate::testing::node_with_props("vs", "vertical_slider", props);
        let widget = VerticalSliderWidget::new();
        <VerticalSliderWidget as PlushieWidget<iced::Renderer>>::infer_a11y(&widget, &node)
    }

    #[test]
    fn slider_mnemonic_propagates() {
        let o = infer_slider(json!({"mnemonic": "V"})).expect("mnemonic should infer");
        assert_eq!(o.core().mnemonic, Some('V'));
    }

    #[test]
    fn slider_no_mnemonic_returns_none() {
        assert!(infer_slider(json!({})).is_none());
    }

    #[test]
    fn vertical_slider_mnemonic_propagates() {
        let o = infer_vertical(json!({"mnemonic": "V"})).expect("mnemonic should infer");
        assert_eq!(o.core().mnemonic, Some('V'));
    }
}
