use accesskit::{
    Action, DefaultActionVerb, Node, NodeBuilder, NodeClassSet, NodeId as AccessibilityId, Rect,
    Role, Tree, TreeUpdate,
};
use dioxus_native_core::{
    prelude::{NodeType, TextNode},
    real_dom::NodeImmutable,
    NodeId,
};
use freya_dom::prelude::{DioxusDOM, DioxusNode};
use freya_node_state::AccessibilityState;
use std::slice::Iter;
use tokio::sync::watch;
use torin::{prelude::NodeAreas, torin::Torin};
use crate::layout::*;
#[derive(PartialEq)]
pub enum AccessibilityFocusDirection {
    Forward,
    Backward,
}
pub trait AccessibilityProvider {
    fn add_node(
        &mut self,
        dioxus_node: &DioxusNode,
        node_areas: &NodeAreas,
        accessibility_id: AccessibilityId,
        node_accessibility: &AccessibilityState,
    ) {
        let mut builder = NodeBuilder::new(Role::Unknown);
        let children = dioxus_node.get_accessibility_children();
        if !children.is_empty() {
            builder.set_children(children);
        }
        if let Some(alt) = &node_accessibility.alt {
            builder.set_value(alt.to_owned());
        } else if let Some(value) = dioxus_node.get_inner_texts() {
            builder.set_value(value);
        }
        if let Some(name) = &node_accessibility.name {
            builder.set_name(name.to_owned());
        }
        if let Some(role) = node_accessibility.role {
            builder.set_role(role);
        }
        let area = node_areas.area.to_f64();
        builder.set_bounds(Rect {
            x0: area.min_x(),
            x1: area.max_x(),
            y0: area.min_y(),
            y1: area.max_y(),
        });
        if node_accessibility.focusable {
            builder.add_action(Action::Focus);
        } else {
            builder.add_action(Action::Default);
            builder.set_default_action_verb(DefaultActionVerb::Focus);
        }
        let node = builder.build(self.node_classes());
        self.push_node(accessibility_id, node);
    }
    fn push_node(&mut self, id: AccessibilityId, node: Node);
    fn node_classes(&mut self) -> &mut NodeClassSet;
    fn nodes(&self) -> Iter<(AccessibilityId, Node)>;
    fn focus_id(&self) -> Option<AccessibilityId>;
    fn set_focus(&mut self, new_focus_id: Option<AccessibilityId>);
    fn set_focus_with_update(
        &mut self,
        new_focus_id: Option<AccessibilityId>,
    ) -> Option<TreeUpdate> {
        self.set_focus(new_focus_id);
        let node_focused_exists = self.nodes().any(|node| Some(node.0) == new_focus_id);
        if node_focused_exists {
            Some(TreeUpdate {
                nodes: Vec::new(),
                tree: None,
                focus: self.focus_id(),
            })
        } else {
            None
        }
    }
    fn build_root(&mut self, root_name: &str) -> Node {
        let mut builder = NodeBuilder::new(Role::Window);
        builder.set_name(root_name.to_string());
        builder.set_children(
            self.nodes()
                .map(|(id, _)| *id)
                .collect::<Vec<AccessibilityId>>(),
        );
        builder.build(self.node_classes())
    }
    fn process(&mut self, root_id: AccessibilityId, root_name: &str) -> TreeUpdate {
        let root = self.build_root(root_name);
        let mut nodes = vec![(root_id, root)];
        nodes.extend(self.nodes().cloned());
        nodes.reverse();
        let focus = self.nodes().find_map(|node| {
            if Some(node.0) == self.focus_id() {
                Some(node.0)
            } else {
                None
            }
        });
        TreeUpdate {
            nodes,
            tree: Some(Tree::new(root_id)),
            focus,
        }
    }
    fn set_focus_on_next_node(
        &mut self,
        direction: AccessibilityFocusDirection,
        focus_sender: &watch::Sender<Option<AccessibilityId>>,
    ) -> Option<TreeUpdate> {
        if let Some(focused_node_id) = self.focus_id() {
            let current_node = self
                .nodes()
                .enumerate()
                .find(|(_, node)| node.0 == focused_node_id)
                .map(|(i, _)| i);
            if let Some(node_index) = current_node {
                let target_node_index = if direction == AccessibilityFocusDirection::Forward {
                    if node_index == self.nodes().len() - 1 {
                        0
                    } else {
                        node_index + 1
                    }
                } else {
                    if node_index == 0 {
                        self.nodes().len() - 1
                    } else {
                        node_index - 1
                    }
                };
                let target_node = self
                    .nodes()
                    .enumerate()
                    .find(|(i, _)| *i == target_node_index)
                    .map(|(_, node)| node.0);
                self.set_focus(target_node);
            } else {
                self.set_focus(self.nodes().next().map(|(id, _)| *id))
            }
            focus_sender.send(self.focus_id()).ok();
            Some(TreeUpdate {
                nodes: Vec::new(),
                tree: None,
                focus: self.focus_id(),
            })
        } else {
            None
        }
    }
}
trait NodeAccessibility {
    fn get_inner_texts(&self) -> Option<String>;
    fn get_accessibility_children(&self) -> Vec<AccessibilityId>;
}
impl NodeAccessibility for DioxusNode<'_> {
    fn get_inner_texts(&self) -> Option<String> {
        let children = self.children();
        let first_child = children.first()?;
        let node_type = first_child.node_type();
        if let NodeType::Text(TextNode { text, .. }) = &*node_type {
            Some(text.to_owned())
        } else {
            None
        }
    }
    fn get_accessibility_children(&self) -> Vec<AccessibilityId> {
        self.children()
            .iter()
            .filter_map(|child| {
                let node_accessibility = &*child.get::<AccessibilityState>().unwrap();
                node_accessibility.focus_id
            })
            .collect::<Vec<AccessibilityId>>()
    }
}
pub fn process_accessibility(
    layers: &Layers,
    layout: &Torin<NodeId>,
    rdom: &DioxusDOM,
    access_provider: &mut impl AccessibilityProvider,
) {
    for layer in layers.layers.values() {
        for node_id in layer {
            let node_areas = layout.get(*node_id).unwrap();
            let dioxus_node = rdom.get(*node_id);
            if let Some(dioxus_node) = dioxus_node {
                let node_accessibility = &*dioxus_node.get::<AccessibilityState>().unwrap();
                if let Some(accessibility_id) = node_accessibility.focus_id {
                    access_provider.add_node(
                        &dioxus_node,
                        node_areas,
                        accessibility_id,
                        node_accessibility,
                    );
                }
            }
        }
    }
}