use std::rc::Rc;

use ruin_ui::{Element, KeyboardEventKind, PointerButton, RoutedPointerEventKind, ScrollbarStyle};

use crate::context::{allocate_element_id, with_hook_slot};
use crate::hooks::Signal;
use crate::input::{ScrollbarDrag, clamp_scroll_offset};
use crate::primitives::ContainerProps;
use crate::text_style::{pop_text_style, push_text_style};
use crate::view::{Children, View};

pub struct ScrollBoxBuilder {
    pub(crate) props: ContainerProps,
    pub(crate) offset_y: Option<Signal<f32>>,
    pub(crate) drag: Signal<Option<ScrollbarDrag>>,
}

pub fn scroll_box() -> ScrollBoxBuilder {
    ScrollBoxBuilder {
        props: ContainerProps::new(Element::scroll_box(0.0)),
        offset_y: None,
        drag: with_hook_slot(|| Signal::new(None::<ScrollbarDrag>), |drag| drag.clone()),
    }
}

impl ScrollBoxBuilder {
    impl_props_methods!();

    pub fn text_style(mut self, style: crate::text_style::PartialTextStyle) -> Self {
        self.props = self.props.text_style(style);
        self
    }

    pub fn scrollbar_style(mut self, style: ScrollbarStyle) -> Self {
        self.props.element = self.props.element.scrollbar_style(style);
        self
    }

    pub fn offset_y(mut self, offset_y: Signal<f32>) -> Self {
        self.props.element = self.props.element.scroll_offset(offset_y.get());
        self.offset_y = Some(offset_y);
        self
    }

    pub fn children(mut self, children: impl Children) -> View {
        let element_id = allocate_element_id();
        self.props.element = self.props.element.id(element_id);
        if let Some(widget_ref) = &self.props.widget_ref {
            let _ = widget_ref.set(Some(element_id));
        }

        let had_style = self.props.partial_text_style.is_some();
        if let Some(style) = self.props.partial_text_style.take() {
            push_text_style(style);
        }
        let children_views = children.into_views();
        if had_style {
            pop_text_style();
        }
        let mut view = View::from_container(self.props.element, children_views);
        if let Some(offset_y) = self.offset_y {
            let drag = self.drag;
            let offset_y_for_pointer = offset_y.clone();
            let offset_y_for_keys = offset_y.clone();
            view = view.with_scroll_handler(
                element_id,
                Rc::new(move |event, interaction_tree| {
                    let Some(metrics) = interaction_tree.scroll_metrics_for_element(element_id)
                    else {
                        return;
                    };

                    match event.kind {
                        RoutedPointerEventKind::Scroll { delta } => {
                            offset_y_for_pointer.update(|value| {
                                *value =
                                    clamp_scroll_offset(*value + delta.y, metrics.max_offset_y);
                            });
                        }
                        RoutedPointerEventKind::Down {
                            button: PointerButton::Primary,
                        } => {
                            let Some(thumb_rect) = metrics.scrollbar_thumb else {
                                return;
                            };
                            if !thumb_rect.contains(event.position) {
                                return;
                            }
                            let _ = drag.set(Some(ScrollbarDrag {
                                start_pointer_y: event.position.y,
                                start_offset_y: offset_y_for_pointer.get(),
                            }));
                        }
                        RoutedPointerEventKind::Move => {
                            let Some(drag_state) = drag.get() else {
                                return;
                            };
                            let Some(track_rect) = metrics.scrollbar_track else {
                                return;
                            };
                            let Some(thumb_rect) = metrics.scrollbar_thumb else {
                                return;
                            };
                            let thumb_travel =
                                (track_rect.size.height - thumb_rect.size.height).max(0.0);
                            if thumb_travel <= 0.0 || metrics.max_offset_y <= 0.0 {
                                return;
                            }
                            let pointer_delta = event.position.y - drag_state.start_pointer_y;
                            let next_offset = drag_state.start_offset_y
                                + pointer_delta * (metrics.max_offset_y / thumb_travel);
                            offset_y_for_pointer.update(|value| {
                                *value = clamp_scroll_offset(next_offset, metrics.max_offset_y);
                            });
                        }
                        RoutedPointerEventKind::Up {
                            button: PointerButton::Primary,
                        } => {
                            let _ = drag.set(None);
                        }
                        _ => {}
                    }
                }),
            );
            view = view.with_key_handler(
                element_id,
                Rc::new(move |event, interaction_tree| {
                    if event.kind != KeyboardEventKind::Pressed
                        || event.modifiers.control
                        || event.modifiers.alt
                        || event.modifiers.super_key
                    {
                        return false;
                    }

                    let Some(metrics) = interaction_tree.scroll_metrics_for_element(element_id)
                    else {
                        return false;
                    };

                    let line_step = (metrics.viewport_rect.size.height * 0.12).clamp(28.0, 52.0);
                    let next_offset = match event.key {
                        ruin_ui::KeyboardKey::ArrowUp => offset_y_for_keys.get() - line_step,
                        ruin_ui::KeyboardKey::ArrowDown => offset_y_for_keys.get() + line_step,
                        ruin_ui::KeyboardKey::Home => 0.0,
                        ruin_ui::KeyboardKey::End => metrics.max_offset_y,
                        _ => return false,
                    };
                    let next_offset = clamp_scroll_offset(next_offset, metrics.max_offset_y);
                    let changed = (offset_y_for_keys.get() - next_offset).abs() > f32::EPSILON;
                    if changed {
                        let _ = offset_y_for_keys.set(next_offset);
                    }
                    changed
                }),
            );
        }
        view
    }
}
