use std::sync::{
    Arc,
    Mutex,
};
use accesskit_winit::Adapter;
use freya_common::AccessibilityDirtyNodes;
use freya_core::{
    dom::DioxusDOM,
    prelude::{
        AccessibilityFocusStrategy,
        AccessibilityTree,
        EventMessage,
        SharedAccessibilityTree,
        ACCESSIBILITY_ROOT_ID,
    },
    types::{
        AccessibilityId,
        NativePlatformSender,
    },
};
use freya_native_core::NodeId;
use torin::torin::Torin;
use winit::{
    dpi::{
        LogicalPosition,
        LogicalSize,
    },
    event::WindowEvent,
    event_loop::EventLoopProxy,
    window::Window,
};
pub struct AccessKitManager {
    accessibility_tree: SharedAccessibilityTree,
    accessibility_adapter: Adapter,
    adapter_initialized: bool,
}
impl AccessKitManager {
    pub fn new(window: &Window, proxy: EventLoopProxy<EventMessage>) -> Self {
        let accessibility_tree =
            Arc::new(Mutex::new(AccessibilityTree::new(ACCESSIBILITY_ROOT_ID)));
        let accessibility_adapter = Adapter::with_event_loop_proxy(window, proxy);
        Self {
            accessibility_tree,
            accessibility_adapter,
            adapter_initialized: false,
        }
    }
    pub fn focused_node_id(&self) -> Option<NodeId> {
        self.accessibility_tree.lock().unwrap().focused_node_id()
    }
    pub fn process_accessibility_event(&mut self, event: &WindowEvent, window: &Window) {
        self.accessibility_adapter.process_event(window, event)
    }
    pub fn init_accessibility(
        &mut self,
        rdom: &DioxusDOM,
        layout: &Torin<NodeId>,
        dirty_nodes: &mut AccessibilityDirtyNodes,
    ) {
        let tree = self
            .accessibility_tree
            .lock()
            .unwrap()
            .init(rdom, layout, dirty_nodes);
        self.accessibility_adapter.update_if_active(|| {
            self.adapter_initialized = true;
            tree
        });
    }
    pub fn process_updates(
        &mut self,
        rdom: &DioxusDOM,
        layout: &Torin<NodeId>,
        platform_sender: &NativePlatformSender,
        window: &Window,
        dirty_nodes: &mut AccessibilityDirtyNodes,
    ) {
        let (tree, node_id) =
            self.accessibility_tree
                .lock()
                .unwrap()
                .process_updates(rdom, layout, dirty_nodes);
        platform_sender.send_modify(|state| {
            state.focused_id = tree.focus;
        });
        self.update_ime_position(node_id, window, layout);
        if self.adapter_initialized {
            self.accessibility_adapter.update_if_active(|| tree);
        }
    }
    pub fn focus_next_node(
        &mut self,
        rdom: &DioxusDOM,
        direction: AccessibilityFocusStrategy,
        platform_sender: &NativePlatformSender,
        window: &Window,
        layout: &Torin<NodeId>,
    ) {
        let (tree, node_id) = self
            .accessibility_tree
            .lock()
            .unwrap()
            .set_focus_on_next_node(direction, rdom);
        platform_sender.send_modify(|state| {
            state.focused_id = tree.focus;
        });
        self.update_ime_position(node_id, window, layout);
        if self.adapter_initialized {
            self.accessibility_adapter.update_if_active(|| tree);
        }
    }
    pub fn focus_node(
        &mut self,
        id: AccessibilityId,
        platform_sender: &NativePlatformSender,
        window: &Window,
        layout: &Torin<NodeId>,
    ) {
        let res = self
            .accessibility_tree
            .lock()
            .unwrap()
            .set_focus_with_update(id);
        if let Some((tree, node_id)) = res {
            platform_sender.send_modify(|state| {
                state.focused_id = tree.focus;
            });
            self.update_ime_position(node_id, window, layout);
            if self.adapter_initialized {
                self.accessibility_adapter.update_if_active(|| tree);
            }
        }
    }
    fn update_ime_position(&self, node_id: NodeId, window: &Window, layout: &Torin<NodeId>) {
        let layout_node = layout.get(node_id);
        if let Some(layout_node) = layout_node {
            let area = layout_node.visible_area();
            return window.set_ime_cursor_area(
                LogicalPosition::new(area.min_x(), area.min_y()),
                LogicalSize::new(area.width(), area.height()),
            );
        }
        window.set_ime_cursor_area(
            window.inner_position().unwrap_or_default(),
            LogicalSize::<u32>::default(),
        );
    }
}