use crate::html::attributes::{class, classes, Attribute};
use crate::vdom::AttributeName;
use crate::vdom::AttributeValue;
use crate::vdom::Leaf;
use crate::{dom::Effects, vdom::Node};
use derive_where::derive_where;
use std::any::TypeId;
#[cfg(feature = "with-dom")]
pub use stateful_component::{stateful_component, StatefulComponent, StatefulModel};
#[cfg(feature = "with-dom")]
mod stateful_component;
pub trait Component {
    type MSG: 'static;
    type XMSG: 'static;
    fn init(&mut self) -> Effects<Self::MSG, Self::XMSG> {
        Effects::none()
    }
    fn update(&mut self, msg: Self::MSG) -> Effects<Self::MSG, Self::XMSG>;
    fn view(&self) -> Node<Self::MSG>;
    fn stylesheet() -> Vec<String>
    where
        Self: Sized,
    {
        vec![]
    }
    fn observed_attributes() -> Vec<AttributeName> {
        vec![]
    }
    fn style(&self) -> Vec<String> {
        vec![]
    }
    fn component_name() -> String
    where
        Self: Sized,
    {
        extract_simple_struct_name::<Self>()
    }
    fn prefix_class(class_name: &str) -> String
    where
        Self: Sized,
    {
        let component_name = Self::component_name();
        if class_name.is_empty() {
            component_name
        } else {
            format!("{component_name}__{class_name}")
        }
    }
    fn class_ns(class_name: &str) -> Attribute<Self::MSG>
    where
        Self: Sized,
    {
        let class_names: Vec<&str> = class_name.split(' ').collect();
        let prefixed_classes = class_names
            .iter()
            .map(|c| Self::prefix_class(c))
            .collect::<Vec<_>>()
            .join(" ");
        class(prefixed_classes)
    }
    fn classes_ns_flag(
        pair: impl IntoIterator<Item = (impl ToString, bool)>,
    ) -> Attribute<Self::MSG>
    where
        Self: Sized,
    {
        let class_list = pair.into_iter().filter_map(|(class, flag)| {
            if flag {
                Some(Self::prefix_class(&class.to_string()))
            } else {
                None
            }
        });
        classes(class_list)
    }
    fn selector_ns(class_name: &str) -> String
    where
        Self: Sized,
    {
        let component_name = Self::component_name();
        if class_name.is_empty() {
            format!(".{component_name}")
        } else {
            format!(".{component_name}__{class_name}")
        }
    }
    fn selectors_ns(class_names: impl IntoIterator<Item = impl ToString>) -> String
    where
        Self: Sized,
    {
        let selectors: Vec<String> = class_names
            .into_iter()
            .map(|class_name| Self::selector_ns(&class_name.to_string()))
            .collect();
        selectors.join(" ")
    }
}
pub(crate) fn extract_simple_struct_name<T: ?Sized>() -> String {
    let type_name = std::any::type_name::<T>();
    let name = if let Some(first) = type_name.split(['<', '>']).next() {
        first
    } else {
        type_name
    };
    name.rsplit("::")
        .next()
        .map(|s| s.to_string())
        .expect("must have a name")
}
#[derive_where(Debug)]
pub struct StatelessModel<MSG> {
    pub view: Box<Node<MSG>>,
    pub type_id: TypeId,
}
impl<MSG> StatelessModel<MSG> {
    pub fn map_msg<F, MSG2>(self, cb: F) -> StatelessModel<MSG2>
    where
        F: Fn(MSG) -> MSG2 + Clone + 'static,
        MSG2: 'static,
        MSG: 'static,
    {
        StatelessModel {
            type_id: self.type_id,
            view: Box::new(self.view.map_msg(cb.clone())),
        }
    }
    pub fn attribute_value(&self, name: &AttributeName) -> Option<Vec<&AttributeValue<MSG>>> {
        self.view.attribute_value(name)
    }
    pub fn attributes(&self) -> Option<&[Attribute<MSG>]> {
        self.view.attributes()
    }
}
impl<MSG> Clone for StatelessModel<MSG> {
    fn clone(&self) -> Self {
        Self {
            view: self.view.clone(),
            type_id: self.type_id,
        }
    }
}
impl<MSG> PartialEq for StatelessModel<MSG> {
    fn eq(&self, other: &Self) -> bool {
        self.view == other.view && self.type_id == other.type_id
    }
}
pub fn component<COMP>(app: &COMP) -> Node<COMP::MSG>
where
    COMP: Component + 'static,
{
    let type_id = TypeId::of::<COMP>();
    let app_view = app.view();
    Node::Leaf(Leaf::StatelessComponent(StatelessModel {
        view: Box::new(app_view),
        type_id,
    }))
}
#[cfg(test)]
mod test {
    use super::*;
    use crate::html::*;
    use std::marker::PhantomData;
    #[test]
    fn test_extract_component_name() {
        enum Msg {}
        struct AwesomeEditor {}
        impl Component for AwesomeEditor {
            type MSG = Msg;
            type XMSG = ();
            fn update(&mut self, _msg: Msg) -> Effects<Msg, ()> {
                Effects::none()
            }
            fn view(&self) -> Node<Msg> {
                div([], [])
            }
        }
        let name = extract_simple_struct_name::<AwesomeEditor>();
        assert_eq!("AwesomeEditor", name);
    }
    #[test]
    fn test_name_with_generics() {
        struct ComplexEditor<XMSG> {
            _phantom2: PhantomData<XMSG>,
        }
        enum Xmsg {}
        let name = extract_simple_struct_name::<ComplexEditor<Xmsg>>();
        assert_eq!("ComplexEditor", name);
    }
    #[test]
    fn test_name_with_2_generics() {
        struct ComplexEditor<MSG, XMSG> {
            _phantom1: PhantomData<MSG>,
            _phantom2: PhantomData<XMSG>,
        }
        enum Msg {}
        enum Xmsg {}
        let name = extract_simple_struct_name::<ComplexEditor<Msg, Xmsg>>();
        assert_eq!("ComplexEditor", name);
    }
}