use std::{collections::HashMap, mem};
pub use euclid::Rect;
use rustc_hash::{FxHashMap, FxHashSet};
use tracing::info;
use crate::{
    custom_measurer::LayoutMeasurer,
    dom_adapter::{DOMAdapter, NodeAreas, NodeKey},
    geometry::{Area, Size2D},
    measure::measure_node,
    prelude::Gaps,
};
pub struct LayoutMetadata {
    pub root_area: Area,
}
#[derive(PartialEq, Debug, Clone)]
pub enum RootNodeCandidate<Key: NodeKey> {
    Valid(Key),
    None,
}
impl<Key: NodeKey> RootNodeCandidate<Key> {
    pub fn take(&mut self) -> Self {
        mem::replace(self, Self::None)
    }
    pub fn propose_new_candidate(
        &mut self,
        proposed_candidate: &Key,
        dom_adapter: &mut impl DOMAdapter<Key>,
    ) {
        if let RootNodeCandidate::Valid(current_candidate) = self {
            if current_candidate != proposed_candidate {
                let closest_parent =
                    dom_adapter.closest_common_parent(proposed_candidate, current_candidate);
                if let Some(closest_parent) = closest_parent {
                    *self = RootNodeCandidate::Valid(closest_parent);
                }
            }
        } else {
            *self = RootNodeCandidate::Valid(*proposed_candidate)
        }
    }
}
pub struct Torin<Key: NodeKey> {
    pub results: FxHashMap<Key, NodeAreas>,
    pub dirty: FxHashSet<Key>,
    pub root_node_candidate: RootNodeCandidate<Key>,
}
impl<Key: NodeKey> Default for Torin<Key> {
    fn default() -> Self {
        Self::new()
    }
}
#[cfg(feature = "dioxus")]
use dioxus_core::Mutations;
#[cfg(feature = "dioxus")]
use dioxus_native_core::prelude::*;
#[cfg(feature = "dioxus")]
impl Torin<NodeId> {
    pub fn apply_mutations(
        &mut self,
        mutations: &Mutations,
        dioxus_integration_state: &DioxusState,
        dom_adapter: &mut impl DOMAdapter<NodeId>,
    ) {
        use dioxus_core::Mutation;
        for mutation in &mutations.edits {
            match mutation {
                Mutation::SetText { id, .. } => {
                    self.invalidate(dioxus_integration_state.element_to_node_id(*id));
                }
                Mutation::InsertAfter { id, m } => {
                    if *m > 0 {
                        self.invalidate(dioxus_integration_state.element_to_node_id(*id));
                    }
                }
                Mutation::InsertBefore { id, m } => {
                    if *m > 0 {
                        self.invalidate(dioxus_integration_state.element_to_node_id(*id));
                    }
                }
                Mutation::Remove { id } => {
                    self.remove(
                        dioxus_integration_state.element_to_node_id(*id),
                        dom_adapter,
                        true,
                    );
                }
                Mutation::ReplaceWith { id, m } => {
                    if *m > 0 {
                        self.remove(
                            dioxus_integration_state.element_to_node_id(*id),
                            dom_adapter,
                            true,
                        );
                    }
                }
                _ => {}
            }
        }
    }
}
impl<Key: NodeKey> Torin<Key> {
    pub fn new() -> Self {
        Self {
            results: HashMap::default(),
            dirty: FxHashSet::default(),
            root_node_candidate: RootNodeCandidate::None,
        }
    }
    pub fn size(&self) -> usize {
        self.results.len()
    }
    pub fn reset(&mut self) {
        self.root_node_candidate = RootNodeCandidate::None;
        self.results.clear();
        self.dirty.clear();
    }
    pub fn get_dirty_nodes(&self) -> &FxHashSet<Key> {
        &self.dirty
    }
    pub fn raw_remove(&mut self, node_id: Key) {
        self.results.remove(&node_id);
        self.dirty.remove(&node_id);
        if let RootNodeCandidate::Valid(id) = self.root_node_candidate {
            if id == node_id {
                self.root_node_candidate = RootNodeCandidate::None
            }
        }
    }
    pub fn remove(
        &mut self,
        node_id: Key,
        dom_adapter: &mut impl DOMAdapter<Key>,
        invalidate_parent: bool,
    ) {
        self.raw_remove(node_id);
        if invalidate_parent {
            self.invalidate(dom_adapter.parent_of(&node_id).unwrap());
        }
        for child_id in dom_adapter.children_of(&node_id) {
            self.remove(child_id, dom_adapter, false);
        }
    }
    pub fn invalidate(&mut self, node_id: Key) {
        self.dirty.insert(node_id);
    }
    pub fn check_dirty_dependants(
        &mut self,
        node_id: Key,
        dom_adapter: &mut impl DOMAdapter<Key>,
        ignore: bool,
    ) {
        if (self.dirty.contains(&node_id) && ignore) || !dom_adapter.is_node_valid(&node_id) {
            return;
        }
        self.invalidate(node_id);
        self.root_node_candidate
            .propose_new_candidate(&node_id, dom_adapter);
        let parent_id = dom_adapter.parent_of(&node_id);
        if let Some(parent_id) = parent_id {
            let parent = dom_adapter.get_node(&parent_id);
            if let Some(parent) = parent {
                if parent.does_depend_on_inner() {
                    self.check_dirty_dependants(parent_id, dom_adapter, true);
                } else {
                    let parent_children = dom_adapter.children_of(&parent_id);
                    let multiple_children = parent_children.len() > 1;
                    if multiple_children {
                        self.root_node_candidate
                            .propose_new_candidate(&parent_id, dom_adapter);
                    }
                }
            }
        }
    }
    pub fn get_root_candidate(&self) -> RootNodeCandidate<Key> {
        self.root_node_candidate.clone()
    }
    pub fn find_best_root(&mut self, dom_adapter: &mut impl DOMAdapter<Key>) {
        if self.results.is_empty() {
            return;
        }
        for dirty in self.dirty.clone() {
            self.check_dirty_dependants(dirty, dom_adapter, false);
        }
    }
    pub fn measure(
        &mut self,
        suggested_root_id: Key,
        root_area: Area,
        measurer: &mut Option<impl LayoutMeasurer<Key>>,
        dom_adapter: &mut impl DOMAdapter<Key>,
    ) {
        if self.dirty.is_empty() && !self.results.is_empty() {
            return;
        }
        let root_id = if let RootNodeCandidate::Valid(id) = self.root_node_candidate.take() {
            id
        } else {
            suggested_root_id
        };
        let root_parent = dom_adapter.parent_of(&root_id);
        let areas = root_parent
            .and_then(|root_parent| self.get(root_parent).cloned())
            .unwrap_or(NodeAreas {
                area: root_area,
                inner_area: root_area,
                inner_sizes: Size2D::default(),
                margin: Gaps::default(),
            });
        let root = dom_adapter.get_node(&root_id).unwrap();
        let root_height = dom_adapter.height(&root_id).unwrap();
        info!(
            "Processing {} dirty nodes and {} cached nodes from a height of {}",
            self.dirty.len(),
            self.results.len(),
            root_height
        );
        let metadata = LayoutMetadata { root_area };
        let (root_revalidated, root_areas) = measure_node(
            root_id,
            &root,
            self,
            &areas.inner_area,
            &areas.inner_area,
            measurer,
            true,
            dom_adapter,
            &metadata,
            false,
        );
        if root_revalidated {
            self.cache_node(root_id, root_areas);
        }
        self.dirty.clear();
        self.root_node_candidate = RootNodeCandidate::None;
    }
    pub fn get(&self, node_id: Key) -> Option<&NodeAreas> {
        self.results.get(&node_id)
    }
    pub fn cache_node(&mut self, node_id: Key, areas: NodeAreas) {
        self.results.insert(node_id, areas);
    }
}