use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;

use ruin_ui::{
    CursorIcon, DisplayItem, ElementId, HitTarget, InteractionTree, KeyboardEvent, PointerButton,
    PointerEvent, Quad, RoutedPointerEvent, RoutedPointerEventKind, WindowController,
};

use crate::hooks::{Shortcut, ShortcutScope, Signal};
use crate::view::View;

pub(crate) type PressHandler = Rc<dyn Fn(&RoutedPointerEvent) + 'static>;
pub(crate) type ScrollHandler = Rc<dyn Fn(&RoutedPointerEvent, &InteractionTree) + 'static>;
pub(crate) type KeyHandler = Rc<dyn Fn(&KeyboardEvent, &InteractionTree) -> bool + 'static>;

#[derive(Clone, Default)]
pub(crate) struct EventBindings {
    pub(crate) on_press: HashMap<ElementId, PressHandler>,
    pub(crate) on_scroll: HashMap<ElementId, ScrollHandler>,
    pub(crate) on_key: HashMap<ElementId, KeyHandler>,
}

impl EventBindings {
    pub(crate) fn extend(&mut self, other: EventBindings) {
        self.on_press.extend(other.on_press);
        self.on_scroll.extend(other.on_scroll);
        self.on_key.extend(other.on_key);
    }

    pub(crate) fn dispatch(
        &self,
        event: &RoutedPointerEvent,
        interaction_tree: &InteractionTree,
        hovered_targets: &[HitTarget],
    ) {
        match event.kind {
            RoutedPointerEventKind::Up {
                button: PointerButton::Primary,
            } => {
                if let Some(element_id) = event.target.element_id
                    && let Some(handler) = self.on_press.get(&element_id)
                {
                    handler(event);
                }
                if let Some(handler) = scroll_handler_for_event(
                    &self.on_scroll,
                    event.target.element_id,
                    hovered_targets,
                ) {
                    handler(event, interaction_tree);
                }
            }
            RoutedPointerEventKind::Down {
                button: PointerButton::Primary,
            }
            | RoutedPointerEventKind::Move
            | RoutedPointerEventKind::Scroll { .. }
            | RoutedPointerEventKind::Leave => {
                if let Some(handler) = scroll_handler_for_event(
                    &self.on_scroll,
                    event.target.element_id,
                    hovered_targets,
                ) {
                    handler(event, interaction_tree);
                }
            }
            _ => {}
        }
    }

    pub(crate) fn dispatch_key(
        &self,
        focused_element: Option<ElementId>,
        event: &KeyboardEvent,
        interaction_tree: &InteractionTree,
    ) {
        let Some(handler) = key_handler_for_focus(&self.on_key, focused_element, interaction_tree)
        else {
            return;
        };
        let _ = handler(event, interaction_tree);
    }
}

#[derive(Clone, Copy, PartialEq)]
pub(crate) struct ScrollbarDrag {
    pub(crate) start_pointer_y: f32,
    pub(crate) start_offset_y: f32,
}

#[derive(Clone, Copy, PartialEq)]
pub(crate) struct TextSelection {
    pub(crate) element_id: ElementId,
    pub(crate) anchor: usize,
    pub(crate) focus: usize,
}

#[derive(Clone, Copy, PartialEq)]
pub(crate) struct TextSelectionDrag {
    pub(crate) element_id: ElementId,
    pub(crate) anchor: usize,
}

pub(crate) struct TextSelectionState {
    pub(crate) selection: RefCell<Option<TextSelection>>,
    pub(crate) drag: RefCell<Option<TextSelectionDrag>>,
    pub(crate) version: Signal<u64>,
}

impl TextSelectionState {
    pub(crate) fn new() -> Self {
        Self {
            selection: RefCell::new(None),
            drag: RefCell::new(None),
            version: Signal::new(0),
        }
    }
}

pub(crate) struct InputState {
    pub(crate) current_cursor: CursorIcon,
    pub(crate) focused_element: Option<ElementId>,
    pub(crate) text_selection: Rc<TextSelectionState>,
    pub(crate) hovered_elements: Signal<Vec<ElementId>>,
    pub(crate) pressed_elements: Signal<Vec<ElementId>>,
    pub(crate) interaction_version: Signal<u64>,
}

impl InputState {
    pub(crate) fn new() -> Self {
        Self {
            current_cursor: CursorIcon::Default,
            focused_element: None,
            text_selection: Rc::new(TextSelectionState::new()),
            hovered_elements: Signal::new(Vec::new()),
            pressed_elements: Signal::new(Vec::new()),
            interaction_version: Signal::new(0),
        }
    }
}

#[derive(Clone)]
pub(crate) struct ShortcutBinding {
    pub(crate) shortcut: Shortcut,
    pub(crate) scope: ShortcutScope,
    pub(crate) action: Rc<dyn Fn(&InteractionTree)>,
}

impl ShortcutBinding {
    pub(crate) fn matches(
        &self,
        event: &KeyboardEvent,
        focused_element: Option<ElementId>,
        interaction_tree: &InteractionTree,
    ) -> bool {
        self.shortcut.matches(event) && self.scope.matches(focused_element, interaction_tree)
    }

    pub(crate) fn trigger(&self, interaction_tree: &InteractionTree) {
        (self.action)(interaction_tree);
    }
}

pub(crate) fn clamp_scroll_offset(offset_y: f32, max_offset_y: f32) -> f32 {
    offset_y.clamp(0.0, max_offset_y.max(0.0))
}

pub(crate) fn apply_text_selection_overlay(
    scene: &mut ruin_ui::SceneSnapshot,
    selection: Option<TextSelection>,
) {
    let Some(selection) = selection else {
        return;
    };

    let mut next_items = Vec::with_capacity(scene.items.len());
    for item in scene.items.drain(..) {
        match item {
            DisplayItem::Text(mut text) if text.element_id == Some(selection.element_id) => {
                for rect in text.selection_rects(selection.anchor, selection.focus) {
                    next_items.push(DisplayItem::Quad(Quad::new(
                        rect,
                        text.selection_style.highlight_color,
                    )));
                }
                text.apply_selected_text_color(selection.anchor, selection.focus);
                next_items.push(DisplayItem::Text(text));
            }
            other => next_items.push(other),
        }
    }
    scene.items = next_items;
}

pub(crate) fn sync_primary_selection(
    window: &WindowController,
    interaction_tree: &InteractionTree,
    selection: Option<TextSelection>,
) -> crate::Result<()> {
    #[cfg(not(target_os = "linux"))]
    {
        let _ = (window, interaction_tree, selection);
        Ok(())
    }

    #[cfg(target_os = "linux")]
    {
        let Some(selection) = selection else {
            window.set_primary_selection_text(String::new())?;
            return Ok(());
        };

        let Some(text) = interaction_tree.text_for_element(selection.element_id) else {
            return Ok(());
        };
        let copied = text
            .selected_text(selection.anchor, selection.focus)
            .unwrap_or_default()
            .to_owned();
        window.set_primary_selection_text(copied)?;
        Ok(())
    }
}

pub(crate) fn scroll_handler_for_event<'a>(
    handlers: &'a HashMap<ElementId, ScrollHandler>,
    direct_target: Option<ElementId>,
    hovered_targets: &[HitTarget],
) -> Option<&'a ScrollHandler> {
    if let Some(element_id) = direct_target
        && let Some(handler) = handlers.get(&element_id)
    {
        return Some(handler);
    }

    hovered_targets
        .iter()
        .rev()
        .filter_map(|target| target.element_id)
        .find_map(|element_id| handlers.get(&element_id))
}

pub(crate) fn focused_element_for_pointer(
    interaction_tree: &InteractionTree,
    event: &PointerEvent,
) -> Option<ElementId> {
    let hit_path = interaction_tree.hit_path(event.position);
    hit_path
        .iter()
        .rev()
        .find_map(|target| target.focusable.then_some(target.element_id).flatten())
        .or_else(|| hit_path.iter().rev().find_map(|target| target.element_id))
}

pub(crate) fn element_contains_element(
    interaction_tree: &InteractionTree,
    ancestor: ElementId,
    descendant: ElementId,
) -> bool {
    fn contains_descendant(node: &ruin_ui::LayoutNode, descendant: ElementId) -> bool {
        if node.element_id == Some(descendant) {
            return true;
        }
        node.children
            .iter()
            .any(|child| contains_descendant(child, descendant))
    }

    fn ancestor_contains(
        node: &ruin_ui::LayoutNode,
        ancestor: ElementId,
        descendant: ElementId,
    ) -> bool {
        if node.element_id == Some(ancestor) {
            return contains_descendant(node, descendant);
        }
        node.children
            .iter()
            .any(|child| ancestor_contains(child, ancestor, descendant))
    }

    ancestor_contains(&interaction_tree.root, ancestor, descendant)
}

pub(crate) fn key_handler_for_focus<'a>(
    handlers: &'a HashMap<ElementId, KeyHandler>,
    focused_element: Option<ElementId>,
    interaction_tree: &InteractionTree,
) -> Option<&'a KeyHandler> {
    let focused_element = focused_element?;
    focused_ancestor_chain(&interaction_tree.root, focused_element)?
        .into_iter()
        .find_map(|element_id| handlers.get(&element_id))
}

pub(crate) fn focused_ancestor_chain(
    node: &ruin_ui::LayoutNode,
    focused_element: ElementId,
) -> Option<Vec<ElementId>> {
    let mut chain = Vec::new();
    if build_focused_ancestor_chain(node, focused_element, &mut chain) {
        Some(chain)
    } else {
        None
    }
}

pub(crate) fn build_focused_ancestor_chain(
    node: &ruin_ui::LayoutNode,
    focused_element: ElementId,
    chain: &mut Vec<ElementId>,
) -> bool {
    if node.element_id == Some(focused_element) {
        if let Some(element_id) = node.element_id {
            chain.push(element_id);
        }
        return true;
    }

    for child in &node.children {
        if build_focused_ancestor_chain(child, focused_element, chain) {
            if let Some(element_id) = node.element_id {
                chain.push(element_id);
            }
            return true;
        }
    }

    false
}

// View helper methods for attaching handlers — used by primitives
impl View {
    pub(crate) fn with_press_handler(
        mut self,
        element_id: ElementId,
        handler: PressHandler,
    ) -> Self {
        self.bindings.on_press.insert(element_id, handler);
        self
    }

    pub(crate) fn with_scroll_handler(
        mut self,
        element_id: ElementId,
        handler: ScrollHandler,
    ) -> Self {
        self.bindings.on_scroll.insert(element_id, handler);
        self
    }

    pub(crate) fn with_key_handler(mut self, element_id: ElementId, handler: KeyHandler) -> Self {
        self.bindings.on_key.insert(element_id, handler);
        self
    }
}
