use std::{
    any::{
        Any,
        TypeId,
    },
    collections::BTreeMap,
    marker::PhantomData,
    ops::Deref,
    sync::Arc,
};
use parking_lot::RwLock;
use rustc_hash::{
    FxHashMap,
    FxHashSet,
};
use shipyard::{
    Borrow,
    BorrowInfo,
    Component,
    Unique,
    UniqueView,
    View,
    WorkloadSystem,
};
use crate::{
    node::{
        FromAnyValue,
        NodeType,
    },
    node_ref::{
        NodeMaskBuilder,
        NodeView,
    },
    real_dom::{
        DirtyNodesResult,
        SendAnyMapWrapper,
    },
    tree::{
        TreeRef,
        TreeRefView,
    },
    NodeId,
    NodeMask,
    SendAnyMap,
};
#[derive(Default)]
struct DirtyNodes {
    nodes_dirty: FxHashSet<NodeId>,
}
impl DirtyNodes {
    pub fn add_node(&mut self, node_id: NodeId) {
        self.nodes_dirty.insert(node_id);
    }
    pub fn is_empty(&self) -> bool {
        self.nodes_dirty.is_empty()
    }
    pub fn pop(&mut self) -> Option<NodeId> {
        self.nodes_dirty.iter().next().copied().map(|id| {
            self.nodes_dirty.remove(&id);
            id
        })
    }
}
#[derive(Clone, Unique)]
pub struct DirtyNodeStates {
    dirty: Arc<FxHashMap<TypeId, RwLock<BTreeMap<u16, DirtyNodes>>>>,
}
impl DirtyNodeStates {
    pub fn with_passes(passes: impl Iterator<Item = TypeId>) -> Self {
        Self {
            dirty: Arc::new(
                passes
                    .map(|pass| (pass, RwLock::new(BTreeMap::new())))
                    .collect(),
            ),
        }
    }
    pub fn insert(&self, pass_id: TypeId, node_id: NodeId, height: u16) {
        if let Some(btree) = self.dirty.get(&pass_id) {
            let mut write = btree.write();
            if let Some(entry) = write.get_mut(&height) {
                entry.add_node(node_id);
            } else {
                let mut entry = DirtyNodes::default();
                entry.add_node(node_id);
                write.insert(height, entry);
            }
        }
    }
    fn pop_front(&self, pass_id: TypeId) -> Option<(u16, NodeId)> {
        let mut values = self.dirty.get(&pass_id)?.write();
        let mut value = values.first_entry()?;
        let height = *value.key();
        let ids = value.get_mut();
        let id = ids.pop()?;
        if ids.is_empty() {
            value.remove_entry();
        }
        Some((height, id))
    }
    fn pop_back(&self, pass_id: TypeId) -> Option<(u16, NodeId)> {
        let mut values = self.dirty.get(&pass_id)?.write();
        let mut value = values.last_entry()?;
        let height = *value.key();
        let ids = value.get_mut();
        let id = ids.pop()?;
        if ids.is_empty() {
            value.remove_entry();
        }
        Some((height, id))
    }
}
pub trait State<V: FromAnyValue + Send + Sync = ()>: Any + Send + Sync {
    type ParentDependencies: Dependancy;
    type ChildDependencies: Dependancy;
    type NodeDependencies: Dependancy;
    const NODE_MASK: NodeMaskBuilder<'static>;
    const TRAVERSE_SHADOW_DOM: bool = false;
    fn update<'a>(
        &mut self,
        node_view: NodeView<V>,
        node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
        parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
        children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
        context: &SendAnyMap,
    ) -> bool;
    fn create<'a>(
        node_view: NodeView<V>,
        node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
        parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
        children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
        context: &SendAnyMap,
    ) -> Self;
    fn workload_system(
        type_id: TypeId,
        dependants: Arc<Dependants>,
        pass_direction: PassDirection,
    ) -> WorkloadSystem;
    fn to_type_erased() -> TypeErasedState<V>
    where
        Self: Sized,
    {
        let node_mask = Self::NODE_MASK.build();
        TypeErasedState {
            this_type_id: TypeId::of::<Self>(),
            parent_dependancies_ids: Self::ParentDependencies::type_ids()
                .iter()
                .copied()
                .collect(),
            child_dependancies_ids: Self::ChildDependencies::type_ids()
                .iter()
                .copied()
                .collect(),
            node_dependancies_ids: Self::NodeDependencies::type_ids().iter().copied().collect(),
            dependants: Default::default(),
            mask: node_mask,
            pass_direction: pass_direction::<V, Self>(),
            enter_shadow_dom: Self::TRAVERSE_SHADOW_DOM,
            workload: Self::workload_system,
            phantom: PhantomData,
        }
    }
}
fn pass_direction<V: FromAnyValue + Send + Sync, S: State<V>>() -> PassDirection {
    if S::ChildDependencies::type_ids()
        .iter()
        .any(|type_id| *type_id == TypeId::of::<S>())
    {
        PassDirection::ChildToParent
    } else if S::ParentDependencies::type_ids()
        .iter()
        .any(|type_id| *type_id == TypeId::of::<S>())
    {
        PassDirection::ParentToChild
    } else {
        PassDirection::AnyOrder
    }
}
#[doc(hidden)]
#[derive(Borrow, BorrowInfo)]
pub struct RunPassView<'a, V: FromAnyValue + Send + Sync = ()> {
    pub tree: TreeRefView<'a>,
    pub node_type: View<'a, NodeType<V>>,
    dirty_nodes_result: UniqueView<'a, DirtyNodesResult>,
    node_states: UniqueView<'a, DirtyNodeStates>,
    any_map: UniqueView<'a, SendAnyMapWrapper>,
}
#[doc(hidden)]
pub fn run_pass<V: FromAnyValue + Send + Sync>(
    type_id: TypeId,
    dependants: Arc<Dependants>,
    pass_direction: PassDirection,
    view: RunPassView<V>,
    mut update_node: impl FnMut(NodeId, &SendAnyMap, u16) -> bool,
) {
    let RunPassView {
        tree,
        dirty_nodes_result: nodes_updated,
        node_states: dirty,
        any_map: ctx,
        ..
    } = view;
    let ctx = ctx.as_ref();
    match pass_direction {
        PassDirection::ParentToChild => {
            while let Some((height, id)) = dirty.pop_front(type_id) {
                if (update_node)(id, ctx, height) {
                    nodes_updated.insert(id);
                    dependants.mark_dirty(&dirty, id, &tree, height);
                }
            }
        }
        PassDirection::ChildToParent => {
            while let Some((height, id)) = dirty.pop_back(type_id) {
                if (update_node)(id, ctx, height) {
                    nodes_updated.insert(id);
                    dependants.mark_dirty(&dirty, id, &tree, height);
                }
            }
        }
        PassDirection::AnyOrder => {
            while let Some((height, id)) = dirty.pop_back(type_id) {
                if (update_node)(id, ctx, height) {
                    nodes_updated.insert(id);
                    dependants.mark_dirty(&dirty, id, &tree, height);
                }
            }
        }
    }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Dependant {
    pub(crate) type_id: TypeId,
    pub(crate) enter_shadow_dom: bool,
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct Dependants {
    pub(crate) parent: Vec<Dependant>,
    pub(crate) child: Vec<Dependant>,
    pub(crate) node: Vec<TypeId>,
}
impl Dependants {
    fn mark_dirty(&self, dirty: &DirtyNodeStates, id: NodeId, tree: &impl TreeRef, height: u16) {
        for &Dependant {
            type_id,
            enter_shadow_dom,
        } in &self.child
        {
            for id in tree.children_ids_advanced(id, enter_shadow_dom) {
                dirty.insert(type_id, id, height + 1);
            }
        }
        for &Dependant {
            type_id,
            enter_shadow_dom,
        } in &self.parent
        {
            if let Some(id) = tree.parent_id_advanced(id, enter_shadow_dom) {
                dirty.insert(type_id, id, height - 1);
            }
        }
        for dependant in &self.node {
            dirty.insert(*dependant, id, height);
        }
    }
}
pub struct TypeErasedState<V: FromAnyValue + Send = ()> {
    pub(crate) this_type_id: TypeId,
    pub(crate) parent_dependancies_ids: FxHashSet<TypeId>,
    pub(crate) child_dependancies_ids: FxHashSet<TypeId>,
    pub(crate) node_dependancies_ids: FxHashSet<TypeId>,
    pub(crate) dependants: Arc<Dependants>,
    pub(crate) mask: NodeMask,
    pub(crate) workload: fn(TypeId, Arc<Dependants>, PassDirection) -> WorkloadSystem,
    pub(crate) pass_direction: PassDirection,
    pub(crate) enter_shadow_dom: bool,
    phantom: PhantomData<V>,
}
impl<V: FromAnyValue + Send> TypeErasedState<V> {
    pub(crate) fn create_workload(&self) -> WorkloadSystem {
        (self.workload)(
            self.this_type_id,
            self.dependants.clone(),
            self.pass_direction,
        )
    }
    pub(crate) fn combined_dependancy_type_ids(&self) -> impl Iterator<Item = TypeId> + '_ {
        self.parent_dependancies_ids
            .iter()
            .chain(self.child_dependancies_ids.iter())
            .chain(self.node_dependancies_ids.iter())
            .copied()
    }
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum PassDirection {
    ParentToChild,
    ChildToParent,
    AnyOrder,
}
pub trait Dependancy {
    type ElementBorrowed<'a>;
    fn type_ids() -> Box<[TypeId]> {
        Box::new([])
    }
}
macro_rules! impl_dependancy {
    ($($t:ident),*) => {
        impl< $($t: Send + Sync + Component),* > Dependancy for ($($t,)*) {
            type ElementBorrowed<'a> = ($(DependancyView<'a, $t>,)*);
            fn type_ids() -> Box<[TypeId]> {
                Box::new([$(TypeId::of::<$t>()),*])
            }
        }
    };
}
pub struct DependancyView<'a, T> {
    inner: &'a T,
}
impl<'a, T> DependancyView<'a, T> {
    #[doc(hidden)]
    pub fn new(inner: &'a T) -> Self {
        Self { inner }
    }
}
impl<'a, T> Deref for DependancyView<'a, T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        self.inner
    }
}
impl_dependancy!();
impl_dependancy!(A);
impl_dependancy!(A, B);
impl_dependancy!(A, B, C);
impl_dependancy!(A, B, C, D);
impl_dependancy!(A, B, C, D, E);
impl_dependancy!(A, B, C, D, E, F);
impl_dependancy!(A, B, C, D, E, F, G);
impl_dependancy!(A, B, C, D, E, F, G, H);
impl_dependancy!(A, B, C, D, E, F, G, H, I);
impl_dependancy!(A, B, C, D, E, F, G, H, I, J);