use std::sync::Arc;
use dioxus_core::{
    prelude::consume_context,
    use_hook,
    AttributeValue,
};
use dioxus_hooks::{
    use_context,
    use_memo,
};
use dioxus_signals::{
    Memo,
    ReadOnlySignal,
    Readable,
    Signal,
    Writable,
};
use freya_common::{
    AccessibilityFocusStrategy,
    AccessibilityGenerator,
};
use freya_core::{
    accessibility::ACCESSIBILITY_ROOT_ID,
    platform_state::NavigationMode,
    prelude::EventMessage,
    types::{
        AccessibilityId,
        AccessibilityNode,
    },
};
use freya_elements::events::{
    keyboard::Code,
    KeyboardEvent,
};
use freya_node_state::CustomAttributeValues;
use crate::{
    use_platform,
    NavigationMark,
    UsePlatform,
};
#[derive(Clone, Copy)]
pub struct UseFocus {
    id: AccessibilityId,
    is_focused_with_keyboard: Memo<bool>,
    is_focused: Memo<bool>,
    navigation_mode: Signal<NavigationMode>,
    navigation_mark: Signal<NavigationMark>,
    platform: UsePlatform,
    focused_id: Signal<AccessibilityId>,
    focused_node: Signal<AccessibilityNode>,
}
impl UseFocus {
    pub fn new_id() -> AccessibilityId {
        let accessibility_generator = consume_context::<Arc<AccessibilityGenerator>>();
        AccessibilityId(accessibility_generator.new_id())
    }
    pub fn focus(&mut self) {
        if !*self.is_focused.peek() {
            self.platform
                .focus(AccessibilityFocusStrategy::Node(self.id));
        }
    }
    pub fn id(&self) -> AccessibilityId {
        self.id
    }
    pub fn attribute(&self) -> AttributeValue {
        Self::attribute_for_id(self.id)
    }
    pub fn attribute_for_id(id: AccessibilityId) -> AttributeValue {
        AttributeValue::any_value(CustomAttributeValues::AccessibilityId(id))
    }
    pub fn is_focused(&self) -> bool {
        *self.is_focused.read()
    }
    pub fn is_focused_with_keyboard(&self) -> bool {
        *self.is_focused_with_keyboard.read()
            && *self.navigation_mode.read() == NavigationMode::Keyboard
    }
    pub fn unfocus(&mut self) {
        self.platform
            .send(EventMessage::FocusAccessibilityNode(
                AccessibilityFocusStrategy::Node(ACCESSIBILITY_ROOT_ID),
            ))
            .ok();
    }
    pub fn validate_keydown(&self, e: &KeyboardEvent) -> bool {
        (e.data.code == Code::Enter || e.data.code == Code::Space)
            && self.is_focused_with_keyboard()
    }
    pub fn prevent_navigation(&mut self) {
        self.navigation_mark.write().set_allowed(false);
    }
    pub fn focused_id(&self) -> ReadOnlySignal<AccessibilityId> {
        self.focused_id.into()
    }
    pub fn focused_node(&self) -> ReadOnlySignal<AccessibilityNode> {
        self.focused_node.into()
    }
}
pub fn use_focus() -> UseFocus {
    let id = use_hook(UseFocus::new_id);
    use_focus_from_id(id)
}
pub fn use_focus_from_id(id: AccessibilityId) -> UseFocus {
    let focused_id = use_context::<Signal<AccessibilityId>>();
    let focused_node = use_context::<Signal<AccessibilityNode>>();
    let navigation_mode = use_context::<Signal<NavigationMode>>();
    let navigation_mark = use_context::<Signal<NavigationMark>>();
    let platform = use_platform();
    let is_focused = use_memo(move || id == *focused_id.read());
    let is_focused_with_keyboard =
        use_memo(move || *is_focused.read() && *navigation_mode.read() == NavigationMode::Keyboard);
    use_hook(move || UseFocus {
        id,
        is_focused,
        is_focused_with_keyboard,
        navigation_mode,
        navigation_mark,
        platform,
        focused_id,
        focused_node,
    })
}