use std::sync::{
    Arc,
    Mutex,
};
use accesskit::{
    Action,
    Affine,
    Node,
    NodeBuilder,
    NodeId as AccessibilityId,
    Rect,
    Role,
    TextDirection,
    Tree,
    TreeUpdate,
};
use freya_common::AccessibilityDirtyNodes;
use freya_engine::prelude::{
    Color,
    Slant,
    TextAlign,
    TextDecoration,
    TextDecorationStyle,
};
use freya_native_core::{
    node::NodeType,
    prelude::NodeImmutable,
    tags::TagName,
    NodeId,
};
use freya_node_state::{
    AccessibilityNodeState,
    Fill,
    FontStyleState,
    OverflowMode,
    StyleState,
    TransformState,
};
use rustc_hash::{
    FxHashMap,
    FxHashSet,
};
use torin::{
    prelude::LayoutNode,
    torin::Torin,
};
use super::{
    AccessibilityFocusStrategy,
    NodeAccessibility,
};
use crate::dom::{
    DioxusDOM,
    DioxusNode,
};
pub const ACCESSIBILITY_ROOT_ID: AccessibilityId = AccessibilityId(0);
pub type SharedAccessibilityTree = Arc<Mutex<AccessibilityTree>>;
pub struct AccessibilityTree {
    pub map: FxHashMap<AccessibilityId, NodeId>,
    pub focused_id: AccessibilityId,
}
impl AccessibilityTree {
    pub fn new(focused_id: AccessibilityId) -> Self {
        Self {
            focused_id,
            map: FxHashMap::default(),
        }
    }
    pub fn focused_node_id(&self) -> Option<NodeId> {
        self.map.get(&self.focused_id).cloned()
    }
    pub fn init(
        &self,
        rdom: &DioxusDOM,
        layout: &Torin<NodeId>,
        dirty_nodes: &mut AccessibilityDirtyNodes,
    ) -> TreeUpdate {
        dirty_nodes.clear();
        let mut nodes = vec![];
        rdom.traverse_depth_first_advanced(|node_ref| {
            if !node_ref.node_type().is_element() {
                return false;
            }
            let accessibility_id = node_ref.get_accessibility_id();
            let layout_node = layout.get(node_ref.id());
            if let Some((accessibility_id, layout_node)) = accessibility_id.zip(layout_node) {
                let node_accessibility_state = node_ref.get::<AccessibilityNodeState>().unwrap();
                let accessibility_node =
                    Self::create_node(&node_ref, layout_node, &node_accessibility_state);
                nodes.push((accessibility_id, accessibility_node));
            }
            if let Some(tag) = node_ref.node_type().tag() {
                if *tag == TagName::Paragraph || *tag == TagName::Label {
                    return false;
                }
            }
            true
        });
        #[cfg(debug_assertions)]
        tracing::info!(
            "Initialized the Accessibility Tree with {} nodes.",
            nodes.len()
        );
        TreeUpdate {
            nodes,
            tree: Some(Tree::new(ACCESSIBILITY_ROOT_ID)),
            focus: ACCESSIBILITY_ROOT_ID,
        }
    }
    pub fn process_updates(
        &mut self,
        rdom: &DioxusDOM,
        layout: &Torin<NodeId>,
        dirty_nodes: &mut AccessibilityDirtyNodes,
    ) -> (TreeUpdate, NodeId) {
        let requested_focus_id = dirty_nodes.requested_focus.take();
        let removed_ids = dirty_nodes.removed.drain().collect::<FxHashMap<_, _>>();
        let mut added_or_updated_ids = dirty_nodes
            .added_or_updated
            .drain()
            .collect::<FxHashSet<_>>();
        #[cfg(debug_assertions)]
        if !removed_ids.is_empty() || !added_or_updated_ids.is_empty() {
            tracing::info!(
                "Updating the Accessibility Tree with {} removals and {} additions/modifications",
                removed_ids.len(),
                added_or_updated_ids.len()
            );
        }
        for (node_id, _) in removed_ids.iter() {
            added_or_updated_ids.remove(node_id);
            self.map.retain(|_, id| id != node_id);
        }
        for (_, parent_id) in removed_ids.iter() {
            if !removed_ids.contains_key(parent_id) {
                added_or_updated_ids.insert(*parent_id);
            }
        }
        for node_id in added_or_updated_ids.clone() {
            let node_ref = rdom.get(node_id).unwrap();
            let node_ref_parent = node_ref.parent_id().unwrap_or(rdom.root_id());
            added_or_updated_ids.insert(node_ref_parent);
            self.map
                .insert(node_ref.get_accessibility_id().unwrap(), node_id);
        }
        let mut nodes = Vec::new();
        for node_id in added_or_updated_ids {
            let node_ref = rdom.get(node_id).unwrap();
            let node_accessibility_state = node_ref.get::<AccessibilityNodeState>();
            let layout_node = layout.get(node_id);
            if let Some((node_accessibility_state, layout_node)) =
                node_accessibility_state.as_ref().zip(layout_node)
            {
                let accessibility_node =
                    Self::create_node(&node_ref, layout_node, node_accessibility_state);
                let accessibility_id = node_ref.get_accessibility_id().unwrap();
                nodes.push((accessibility_id, accessibility_node));
            }
        }
        if let Some(node_id) = requested_focus_id {
            let node_ref = rdom.get(node_id).unwrap();
            let node_accessibility_state = node_ref.get::<AccessibilityNodeState>();
            if let Some(focused_id) = node_accessibility_state
                .as_ref()
                .and_then(|state| state.a11y_id)
            {
                self.focused_id = focused_id;
            }
        }
        if !self.map.contains_key(&self.focused_id) {
            self.focused_id = ACCESSIBILITY_ROOT_ID;
        }
        let node_id = self.map.get(&self.focused_id).cloned().unwrap();
        (
            TreeUpdate {
                nodes,
                tree: Some(Tree::new(ACCESSIBILITY_ROOT_ID)),
                focus: self.focused_id,
            },
            node_id,
        )
    }
    pub fn set_focus_with_update(
        &mut self,
        new_focus_id: AccessibilityId,
    ) -> Option<(TreeUpdate, NodeId)> {
        self.focused_id = new_focus_id;
        if let Some(node_id) = self.map.get(&new_focus_id).copied() {
            #[cfg(debug_assertions)]
            tracing::info!("Focused {new_focus_id:?} node.");
            Some((
                TreeUpdate {
                    nodes: Vec::new(),
                    tree: Some(Tree::new(ACCESSIBILITY_ROOT_ID)),
                    focus: self.focused_id,
                },
                node_id,
            ))
        } else {
            None
        }
    }
    pub fn set_focus_on_next_node(
        &mut self,
        stragegy: AccessibilityFocusStrategy,
        rdom: &DioxusDOM,
    ) -> (TreeUpdate, NodeId) {
        let mut nodes = Vec::new();
        rdom.traverse_depth_first_advanced(|node_ref| {
            if !node_ref.node_type().is_element() {
                return false;
            }
            let accessibility_id = node_ref.get_accessibility_id();
            if let Some(accessibility_id) = accessibility_id {
                let accessibility_state = node_ref.get::<AccessibilityNodeState>().unwrap();
                if accessibility_state.a11y_focusable.is_enabled() {
                    nodes.push((accessibility_id, node_ref.id()))
                }
            }
            if let Some(tag) = node_ref.node_type().tag() {
                if *tag == TagName::Paragraph || *tag == TagName::Label {
                    return false;
                }
            }
            true
        });
        let node_index = nodes
            .iter()
            .enumerate()
            .find(|(_, (accessibility_id, _))| *accessibility_id == self.focused_id)
            .map(|(i, _)| i);
        let target_node = if stragegy == AccessibilityFocusStrategy::Forward {
            if let Some(node_index) = node_index {
                if node_index == nodes.len() - 1 {
                    nodes.first()
                } else {
                    nodes.get(node_index + 1)
                }
            } else {
                nodes.first()
            }
        } else {
            if let Some(node_index) = node_index {
                if node_index == 0 {
                    nodes.last()
                } else {
                    nodes.get(node_index - 1)
                }
            } else {
                nodes.last()
            }
        };
        let (accessibility_id, node_id) = target_node
            .copied()
            .unwrap_or((ACCESSIBILITY_ROOT_ID, rdom.root_id()));
        self.focused_id = accessibility_id;
        #[cfg(debug_assertions)]
        tracing::info!("Focused {accessibility_id:?} node.");
        (
            TreeUpdate {
                nodes: Vec::new(),
                tree: Some(Tree::new(ACCESSIBILITY_ROOT_ID)),
                focus: self.focused_id,
            },
            node_id,
        )
    }
    pub fn create_node(
        node_ref: &DioxusNode,
        layout_node: &LayoutNode,
        node_accessibility: &AccessibilityNodeState,
    ) -> Node {
        let font_style_state = &*node_ref.get::<FontStyleState>().unwrap();
        let style_state = &*node_ref.get::<StyleState>().unwrap();
        let transform_state = &*node_ref.get::<TransformState>().unwrap();
        let node_type = node_ref.node_type();
        let mut builder = match node_type.tag() {
            Some(&TagName::Root) => NodeBuilder::new(Role::Window),
            Some(_) => node_accessibility.builder.clone().unwrap(),
            None => unreachable!(),
        };
        let children = node_ref.get_accessibility_children();
        builder.set_children(children);
        let area = layout_node.area.to_f64();
        builder.set_bounds(Rect {
            x0: area.min_x(),
            x1: area.max_x(),
            y0: area.min_y(),
            y1: area.max_y(),
        });
        if let NodeType::Element(node) = &*node_type {
            if matches!(node.tag, TagName::Label | TagName::Paragraph) && builder.name().is_none() {
                if let Some(inner_text) = node_ref.get_inner_texts() {
                    builder.set_name(inner_text);
                }
            }
        }
        if node_accessibility.a11y_focusable.is_enabled() {
            builder.add_action(Action::Focus);
        }
        if let Some((_, rotation)) = transform_state
            .rotations
            .iter()
            .find(|(id, _)| id == &node_ref.id())
        {
            builder.set_transform(Affine::rotate(rotation.to_radians() as _));
        }
        if style_state.overflow == OverflowMode::Clip {
            builder.set_clips_children();
        }
        builder.set_foreground_color(skia_color_to_rgba_u32(font_style_state.color));
        if let Fill::Color(color) = style_state.background {
            builder.set_background_color(skia_color_to_rgba_u32(color));
        }
        if !node_type.is_text() {
            if let NodeType::Element(node) = &*node_type {
                if node.tag != TagName::Text {
                    builder.set_is_line_breaking_object();
                }
            }
        }
        builder.set_font_size(font_style_state.font_size as _);
        if let Some(parent_node) = node_ref.parent() {
            if parent_node.get::<FontStyleState>().unwrap().font_family
                != font_style_state.font_family
            {
                builder.set_font_family(font_style_state.font_family.join(", "));
            }
        } else {
            builder.set_font_family(font_style_state.font_family.join(", "));
        }
        if font_style_state.font_weight > 700.into() {
            builder.set_bold();
        }
        builder.set_text_align(match font_style_state.text_align {
            TextAlign::Center => accesskit::TextAlign::Center,
            TextAlign::Justify => accesskit::TextAlign::Justify,
            TextAlign::Left | TextAlign::Start => accesskit::TextAlign::Left,
            TextAlign::Right | TextAlign::End => accesskit::TextAlign::Right,
        });
        builder.set_text_direction(TextDirection::LeftToRight);
        match font_style_state.font_slant {
            Slant::Italic | Slant::Oblique => builder.set_italic(),
            _ => {}
        }
        if font_style_state
            .decoration
            .ty
            .contains(TextDecoration::LINE_THROUGH)
        {
            builder.set_strikethrough(skia_decoration_style_to_accesskit(
                font_style_state.decoration.style,
            ));
        }
        if font_style_state
            .decoration
            .ty
            .contains(TextDecoration::UNDERLINE)
        {
            builder.set_underline(skia_decoration_style_to_accesskit(
                font_style_state.decoration.style,
            ));
        }
        if font_style_state
            .decoration
            .ty
            .contains(TextDecoration::OVERLINE)
        {
            builder.set_overline(skia_decoration_style_to_accesskit(
                font_style_state.decoration.style,
            ));
        }
        builder.build()
    }
}
fn skia_decoration_style_to_accesskit(style: TextDecorationStyle) -> accesskit::TextDecoration {
    match style {
        TextDecorationStyle::Solid => accesskit::TextDecoration::Solid,
        TextDecorationStyle::Dotted => accesskit::TextDecoration::Dotted,
        TextDecorationStyle::Dashed => accesskit::TextDecoration::Dashed,
        TextDecorationStyle::Double => accesskit::TextDecoration::Double,
        TextDecorationStyle::Wavy => accesskit::TextDecoration::Wavy,
    }
}
fn skia_color_to_rgba_u32(color: Color) -> u32 {
    ((color.a() as u32) << 24)
        | ((color.b() as u32) << 16)
        | (((color.g() as u32) << 8) + (color.r() as u32))
}