use std::any::TypeId;
use std::any::type_name;
use std::cell::Cell as StdCell;
use std::future::Future;
use std::marker::PhantomData;
use std::rc::Rc;

use ruin_ui::{ElementId, InteractionTree, KeyboardEvent, KeyboardEventKind, KeyboardKey};

use crate::ContextKey;
use crate::context::{
    ContextEntry, MemoSlot, ResourceSlot, with_hook_slot, with_render_context,
    with_render_context_state,
};
use crate::input::ShortcutBinding;

pub struct SignalInner<T> {
    pub(crate) cell: ruin_reactivity::Cell<T>,
}

pub struct Signal<T> {
    pub(crate) inner: Rc<SignalInner<T>>,
}

impl<T> Clone for Signal<T> {
    fn clone(&self) -> Self {
        Self {
            inner: Rc::clone(&self.inner),
        }
    }
}

impl<T: 'static> Signal<T> {
    pub(crate) fn new(initial: T) -> Self {
        Self {
            inner: Rc::new(SignalInner {
                cell: ruin_reactivity::cell(initial),
            }),
        }
    }

    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
        self.inner.cell.with(f)
    }

    pub fn replace(&self, value: T) -> T {
        self.inner.cell.replace(value)
    }

    pub fn update<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
        self.inner.cell.update(f)
    }
}

impl<T: Clone + 'static> Signal<T> {
    pub fn get(&self) -> T {
        self.inner.cell.get()
    }
}

impl<T: PartialEq + 'static> Signal<T> {
    pub fn set(&self, value: T) -> Option<T> {
        self.inner.cell.set(value)
    }
}

pub struct Memo<T> {
    pub(crate) compute: Rc<dyn Fn() -> T>,
}

impl<T> Clone for Memo<T> {
    fn clone(&self) -> Self {
        Self {
            compute: Rc::clone(&self.compute),
        }
    }
}

impl<T> Memo<T> {
    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
        let value = (self.compute)();
        f(&value)
    }
}

impl<T: Clone> Memo<T> {
    pub fn get(&self) -> T {
        (self.compute)()
    }
}

pub fn use_signal<T: 'static>(initial: impl FnOnce() -> T) -> Signal<T> {
    with_hook_slot(|| Signal::new(initial()), |signal| signal.clone())
}

pub fn use_context<C: ContextKey>() -> C::Value {
    with_render_context_state(|context| {
        context
            .context_entries
            .iter()
            .rev()
            .find_map(|entry| {
                (entry.key == TypeId::of::<C>())
                    .then(|| entry.value.downcast_ref::<C::Value>())
                    .flatten()
                    .cloned()
            })
            .unwrap_or_else(|| {
                panic!(
                    "missing context provider for {} while rendering",
                    type_name::<C>()
                )
            })
    })
}

pub fn provide<C: ContextKey>(
    value: C::Value,
    render: impl FnOnce() -> crate::view::View,
) -> crate::view::View {
    with_render_context_state(|context| {
        let mut context_entries = (*context.context_entries).clone();
        context_entries.push(ContextEntry::new::<C>(value));
        with_render_context(
            context.with_context_entries(Rc::new(context_entries)),
            render,
        )
    })
}

pub fn use_memo<T: 'static>(compute: impl Fn() -> T + 'static) -> Memo<T> {
    use std::cell::RefCell;
    let compute: Rc<RefCell<Box<dyn Fn() -> T>>> =
        Rc::new(RefCell::new(Box::new(compute) as Box<dyn Fn() -> T>));
    with_hook_slot(
        {
            let compute = Rc::clone(&compute);
            move || MemoSlot::new(compute)
        },
        |slot: &mut MemoSlot<T>| {
            slot.replace_compute(Rc::clone(&compute));
            slot.handle.clone()
        },
    )
}

pub fn use_effect(effect: impl Fn() + 'static) {
    with_hook_slot(|| ruin_reactivity::effect(effect), |_| ());
}

pub fn use_window_title(compute: impl FnOnce() -> String) {
    with_render_context_state(|context| {
        context.side_effects.borrow_mut().window_title = Some(compute());
    });
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Key {
    Character(char),
    Enter,
    Backspace,
    Escape,
    Delete,
    ArrowLeft,
    ArrowRight,
    ArrowUp,
    ArrowDown,
    Home,
    End,
}

impl Key {
    pub(crate) fn matches(&self, event: &KeyboardEvent) -> bool {
        match (self, &event.key) {
            (Self::Character(expected), KeyboardKey::Character(actual)) => actual
                .chars()
                .next()
                .is_some_and(|actual| actual.eq_ignore_ascii_case(expected)),
            (Self::Enter, KeyboardKey::Enter) => true,
            (Self::Backspace, KeyboardKey::Backspace) => true,
            (Self::Escape, KeyboardKey::Escape) => true,
            (Self::Delete, KeyboardKey::Delete) => true,
            (Self::ArrowLeft, KeyboardKey::ArrowLeft) => true,
            (Self::ArrowRight, KeyboardKey::ArrowRight) => true,
            (Self::ArrowUp, KeyboardKey::ArrowUp) => true,
            (Self::ArrowDown, KeyboardKey::ArrowDown) => true,
            (Self::Home, KeyboardKey::Home) => true,
            (Self::End, KeyboardKey::End) => true,
            _ => false,
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Shortcut {
    pub(crate) key: Key,
    pub(crate) control: bool,
    pub(crate) shift: bool,
    pub(crate) alt: bool,
    pub(crate) super_key: bool,
}

impl Shortcut {
    pub fn new(key: Key) -> Self {
        Self {
            key,
            control: false,
            shift: false,
            alt: false,
            super_key: false,
        }
    }

    pub fn with_ctrl(mut self) -> Self {
        self.control = true;
        self
    }

    pub fn with_shift(mut self) -> Self {
        self.shift = true;
        self
    }

    pub(crate) fn matches(&self, event: &KeyboardEvent) -> bool {
        if event.kind != KeyboardEventKind::Pressed {
            return false;
        }
        if !self.key.matches(event) {
            return false;
        }
        if event.modifiers.control != self.control {
            return false;
        }
        if event.modifiers.alt != self.alt {
            return false;
        }
        if event.modifiers.super_key != self.super_key {
            return false;
        }
        // For character shortcuts the produced character already encodes the shift
        // state, so don't additionally require that Shift is (un)held.
        if !matches!(self.key, Key::Character(_)) && event.modifiers.shift != self.shift {
            return false;
        }
        true
    }
}

#[derive(Clone)]
pub struct FocusScope {
    pub(crate) element_id: Signal<Option<ElementId>>,
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct InteractionState {
    pub hovered: bool,
    pub pressed: bool,
}

#[derive(Clone)]
pub enum ShortcutScope {
    Application,
    FocusedWithin(FocusScope),
}

impl ShortcutScope {
    pub(crate) fn matches(
        &self,
        focused_element: Option<ElementId>,
        interaction_tree: &InteractionTree,
    ) -> bool {
        match self {
            Self::Application => true,
            Self::FocusedWithin(scope) => {
                let Some(scope_element) = scope.element_id.get() else {
                    return false;
                };
                let Some(focused_element) = focused_element else {
                    return false;
                };
                crate::input::element_contains_element(
                    interaction_tree,
                    scope_element,
                    focused_element,
                )
            }
        }
    }
}

pub struct WidgetRef<T> {
    pub(crate) element_id: Signal<Option<ElementId>>,
    pub(crate) _marker: PhantomData<fn() -> T>,
}

impl<T> Clone for WidgetRef<T> {
    fn clone(&self) -> Self {
        Self {
            element_id: self.element_id.clone(),
            _marker: PhantomData,
        }
    }
}

impl<T> WidgetRef<T> {
    pub(crate) fn new() -> Self {
        Self {
            element_id: Signal::new(None),
            _marker: PhantomData,
        }
    }

    pub fn element_id(&self) -> Option<ElementId> {
        self.element_id.get()
    }

    pub fn focus_scope(&self) -> FocusScope {
        FocusScope {
            element_id: self.element_id.clone(),
        }
    }
}

pub struct ScrollBoxWidget;
pub struct BlockWidget;

pub fn use_widget_ref<T: 'static>() -> WidgetRef<T> {
    with_hook_slot(WidgetRef::new, |widget_ref: &mut WidgetRef<T>| {
        widget_ref.clone()
    })
}

pub fn use_interaction_state<T: 'static>(widget_ref: WidgetRef<T>) -> InteractionState {
    with_render_context_state(|context| {
        let Some(element_id) = widget_ref.element_id() else {
            return InteractionState::default();
        };
        InteractionState {
            hovered: context.interaction.hovered.contains(&element_id),
            pressed: context.interaction.pressed.contains(&element_id),
        }
    })
}

pub fn use_shortcut(shortcut: Shortcut, scope: ShortcutScope, action: impl Fn() + 'static) {
    use_shortcut_with_context(shortcut, scope, move |_| action());
}

pub fn use_shortcut_with_context(
    shortcut: Shortcut,
    scope: ShortcutScope,
    action: impl Fn(&InteractionTree) + 'static,
) {
    with_render_context_state(|context| {
        context
            .side_effects
            .borrow_mut()
            .shortcuts
            .push(ShortcutBinding {
                shortcut,
                scope,
                action: Rc::new(action),
            });
    });
}

#[derive(Clone)]
pub struct Resource<T, E> {
    pub(crate) state: Signal<ResourceState<T, E>>,
}

impl<T: Clone + 'static, E: Clone + 'static> Resource<T, E> {
    pub fn read(&self) -> ResourceState<T, E> {
        self.state.get()
    }
}

#[derive(Clone)]
pub enum ResourceState<T, E> {
    Pending,
    Ready(std::result::Result<T, E>),
}

pub fn use_resource<T, E, Fut, F>(factory: F) -> Resource<T, E>
where
    T: Clone + 'static,
    E: Clone + 'static,
    Fut: Future<Output = std::result::Result<T, E>> + 'static,
    F: Fn() -> Fut + 'static,
{
    with_hook_slot(
        || {
            let resource = Resource {
                state: Signal::new(ResourceState::Pending),
            };
            let generation = Rc::new(StdCell::new(0_u64));
            let _effect = ruin_reactivity::effect({
                let resource = resource.clone();
                let generation = Rc::clone(&generation);
                move || {
                    let next_generation = generation.get().wrapping_add(1);
                    generation.set(next_generation);
                    let _ = resource.state.replace(ResourceState::Pending);
                    let resource = resource.clone();
                    let future = factory();
                    let generation = Rc::clone(&generation);
                    std::mem::drop(ruin_runtime::queue_future(async move {
                        let result = future.await;
                        if generation.get() == next_generation {
                            let _ = resource.state.replace(ResourceState::Ready(result));
                        }
                    }));
                }
            });
            ResourceSlot { resource, _effect }
        },
        |slot: &mut ResourceSlot<T, E>| slot.resource.clone(),
    )
}
