// SPDX-License-Identifier: GPL-3.0-or-later

pub mod annotations;
pub mod components;
pub mod handlers;
pub mod props;
pub mod styles;

pub use components::*;
pub use grx_macros::component;
pub use grx_macros::props;
pub use styles::Style;

pub use serde_json;

#[cfg(feature = "gstore")]
pub mod gstore_debug;
#[cfg(feature = "gtk")]
pub mod gtk_components;

mod grx {
    pub use super::*;
}

/// All-in-one import for writing components
pub mod prelude {
    pub use super::component;
    pub use super::components::*;
    pub use super::grx;
    pub use super::props;
    pub use super::Component;
    pub use super::ComponentExt;
    pub use std::rc::Rc;
}

/// Define a UI component
///
/// # Examples
/// ```
/// use grx::{Component, grx, container, Style::*};
/// fn my_component() -> Component {
///     grx! {
///         container(id="root".to_string(), styles=[vertical, p(2)])
///     }
/// }
///
/// ```
#[macro_export]
macro_rules! grx {
    (
        $text:literal
    ) => {{
        {
            $crate::text::Text::new($text)
        }
    }};

    (
        $component_name:ident
        // args
        $( ($($args:tt)*) )?
        // body
        $([$($body:tt)+])?
    ) => {{
        #[allow(unused_mut)]
        let mut props = $component_name::Props::default();

        // overrise props based on the macros args section.
        $(
        $crate::grx_args!{props, $($args)*}
        )?

        // collect all component declarations in the body in the props children vector.
        $(
        props.children = $crate::grx_body!{$($body)+};
        )?

        let styles = props.styles.clone();
        let c = $component_name::$component_name(props);
        $(
        $crate::grx_id!{c, $($args)*};
        )?
        let root: $crate::Component = c;
        for s in styles {
            $crate::styles::apply(&s, &root);
        }

        root
    }};
}

#[macro_export]
macro_rules! grx_args {
    ($props:ident, $styles:ident=[$($st:tt)*] $(,$($rest:tt)*)?) => {
        #[allow(unused_mut)]
        let mut st = vec![];
        $crate::grx_styles!{st, $($st)*}
        $props.$styles = st;
        $(
        $crate::grx_args!{$props, $($rest)*}
        )?
    };
    ($props:ident, id=$id:ident $(,$($rest:tt)*)?) => {
        $props.id = stringify!($id).into();
        $(
        $crate::grx_args!{$props, $($rest)*}
        )?
    };
    ($props:ident, $handler:ident=|| {$($handle:stmt);*$(;)?} $(,$($rest:tt)*)?) => {
        $props.$handler = Some(Rc::new(|_| {
            $($handle)*
        }));
        $(
        $crate::grx_args!{$props, $($rest)*}
        )?
    };
    ($props:ident, $handler:ident=|$i:ident| $handle:expr $(,$($rest:tt)*)?) => {
        $props.$handler = Some(Rc::new(|$i| $handle));
        $(
        $crate::grx_args!{$props, $($rest)*}
        )?
    };
    ($props:ident, $handler:ident=move |$i:ident| {$($handle:stmt);*$(;)?} $(,$($rest:tt)*)?) => {
        $props.$handler = Some(Rc::new(move |$i| {
            $($handle)*
        }));
        $(
        $crate::grx_args!{$props, $($rest)*}
        )?
    };
    ($props:ident, $handler:ident=move || $handle:stmt $(,$($rest:tt)*)?) => {
        $props.$handler = Some(Rc::new(move |_| {
            $handle
        }));
        $(
        $crate::grx_args!{$props, $($rest)*}
        )?
    };
    ($props:ident, $handler:ident=|$($args:ident),+| {$($handle:stmt)*} $(,$($rest:tt)*)?) => {
        $props.$handler = Some(Rc::new(|_| {
            $($handle)*
        }));
        $(
        $crate::grx_args!{$props, $($rest)*}
        )?
    };
    ($props:ident, $prop_key:ident=$val:expr $(,$($rest:tt)*)?) => {
        #[allow(unused_braces)]
        {
            $props.$prop_key = $val;
        }
        $(
        $crate::grx_args!{$props, $($rest)*}
        )?
    }
}

#[macro_export]
macro_rules! grx_styles {
    ($styles:ident, $st:ident $(( $($st_arg:expr),+ ))? $(, $($rest:tt)*)?) => {{
        #[allow(unused_imports)]
        use $crate::styles::Align::*;
        #[allow(unused_imports)]
        use $crate::styles::Overflow::*;
        #[allow(unused_imports)]
        use $crate::styles::Wrap::*;
        #[allow(unused_imports)]
        use $crate::styles::Justify::*;
        $styles.push(
            $crate::styles::Style::$st(
                $crate::styles::Modifier::none
                $(,
                $($st_arg),+)?
            )
        );
        $(
        $crate::grx_styles!{$styles, $($rest)*}
        )?
    }};
    ($styles:ident, {$st:ident} $(, $($rest:tt)*)?) => {{
        use $crate::styles::Align::*;
        use $crate::styles::Overflow::*;
        use $crate::styles::Wrap::*;
        use $crate::styles::Justify::*;
        $styles.push($st);
        $(
        $crate::grx_styles!{$styles, $($rest)*}
        )?
    }};
    ($styles:ident, $modi:ident:$st:ident $(( $($st_arg:expr),+ ))? $(, $($rest:tt)*)?) => {{
        $styles.push(
            $crate::styles::Style::$st(
                $crate::styles::Modifier::$modi
                $(,
                $($st_arg),+)?
            )
        );
        $(
        $crate::grx_styles!{$styles, $($rest)*}
        )?
    }};
}

#[macro_export]
macro_rules! grx_body {
    (
        $text:literal
        $(,$($rest:tt)*)?
    ) => {{
        {
            #[allow(unused_mut)]
            let mut body: Vec<$crate::Component> = vec![
                $crate::text::Text::new($text)
            ];
            $(
            body.append(&mut $crate::grx_body!{$($rest)*});
            )?
            body
        }
    }};

    (
        $(#[$($anno:ident=$value:expr),+])?
        icon($name:literal)
        $([$($body:tt)+])?
        $(,$($rest:tt)*)?
    ) => {$crate::grx_body!{ $(#[$($anno=$value),+])? icon( name=$name ) $([$($body)+])? $(,$($rest)*)? }};

    (
        $(#[$($anno:ident=$value:expr),+])?
        $component_name:ident
        $( ($($args:tt)*) )?
        $([$($body:tt)+])?
        $(,$($rest:tt)*)?
    ) => {{
        #[allow(unused_mut)]
        let mut annotations: std::collections::HashMap<String, $crate::serde_json::Value> = std::collections::HashMap::new();
        $(
        $(
        annotations.insert(stringify!($anno).into(), $value.into());
        )+
        )?
        #[allow(unused_mut)]
        let mut props = $component_name::Props::default();
        // override component props based on macros args section
        $(
        $crate::grx_args!{props, $($args)*}
        )?

        // collect all children from the body.
        $(
        props.children = $crate::grx_body!{$($body)+};
        )?
        {
            let styles = props.styles.clone();

            // concrete_component
            #[allow(unused_mut)]
            let mut c = $component_name::$component_name(props);

            // trait type
            let component: $crate::Component = c.clone();
            *component.annotations_mut() = annotations;

            for style in styles {
                $crate::styles::apply(&style, &c);
            }
            $(
            $crate::grx_id!{c, $($args)*};
            )?
            #[allow(unused_mut)]
            let mut body: Vec<$crate::Component> = vec![c];
            $(
            body.append(&mut $crate::grx_body!{$($rest)*});
            )?
            body
        }
    }};
}

#[macro_export]
macro_rules! grx_id {
    ($variable:ident, id=$id:ident $($rest:tt)*) => {
        $id = $variable.clone();
    };
    ($variable:ident, $($rest:tt)*) => {
        ();
    };
}

#[macro_export]
macro_rules! state {
    ($( [$getter:ident, $setter:ident]: $typ:ty );* $(;)?) => {
    $(
        mod $getter {
            use super::*;

            use std::sync::Mutex;
            use once_cell::sync::Lazy;

            type Callback = dyn Fn(&$typ) + Send + 'static;

            #[allow(non_upper_case_globals)]
            static _state: Lazy<Mutex<($typ, Vec<Box<Callback>>)>> =
            Lazy::new(|| Mutex::new((Default::default(), Vec::new())));

            pub(super) fn $getter<T>(cb: T)
            where
                T: Fn(&$typ) + Send + 'static,
            {
                if let Ok(mut lock) = _state.lock() {
                    cb(&lock.0);
                    lock.1.push(Box::new(cb));
                }
            }
            pub(super) fn $setter<T>(setter: T)
            where
                T: (Fn(&$typ) -> $typ) + Send + 'static,
            {
                if let Ok(mut lock) = _state.lock() {
                    let value = setter(&lock.0);
                    lock.1.iter().for_each(|cb| cb(&value));
                    lock.0 = value;
                }
            }
        }
        use $getter::$getter;
        use $getter::$setter;
    )*
    };
}

#[macro_export]
macro_rules! new_gc {
    ($name:ident {
        $widget:ident,
        $props:ident
        $(,
        $($field:ident: $init:expr),*$(,)?
        )?
    }) => {{
        let children = $props.children.drain(0..).collect();
        Rc::new($name {
            widget: $widget,
            props: $props,
            children: std::cell::RefCell::new(children),
            annotations: Default::default(),
            $(
            $(
            $field: $init,
            )*
            )?
        })
    }};
}

#[macro_export]
macro_rules! default_on_click {
    () => {
        fn on_click(self: &Rc<Self>, handler: impl Fn(&Rc<Self>) + 'static) {
            let gesture_click = gtk::GestureClick::builder().build();
            let s = self.clone();
            gesture_click.connect_released(move |_, _, _, _| handler(&s));
            gtk::traits::WidgetExt::add_controller(&self.widget, gesture_click);
        }
    };
}

#[macro_export]
macro_rules! default_on_swipe {
    () => {
        fn on_swipe(self: &Rc<Self>, handler: impl Fn(&Rc<Self>, f64, f64) + 'static) {
            let swipe = gtk::GestureSwipe::builder().build();
            let s = self.clone();
            swipe.connect_swipe(move |_gs, x, y| handler(&s, x, y));
            gtk::traits::WidgetExt::add_controller(&self.widget, swipe);
        }
    };
}

#[macro_export]
macro_rules! default_interactable {
    ($name:ident) => {
        impl $crate::Interactable for $name {
            $crate::default_on_click! {}
            $crate::default_on_swipe! {}
        }
    };
}

#[macro_export]
macro_rules! default_clear {
    () => {
        fn clear(self: &Rc<Self>) {
            use gtk::prelude::WidgetExt;
            let widget = &self.widget;
            let mut child = widget.first_child();
            while let Some(c) = child {
                child = c.next_sibling();
                widget.remove(&c);
            }
        }
    };
}

#[macro_export]
macro_rules! default_remove {
    () => {
        fn remove<W>(self: &Rc<Self>, child: std::rc::Rc<W>)
        where
            W: $crate::ComponentExt + ?Sized,
        {
            let inner = child.inner();
            let w: &gtk::Widget = inner.downcast_ref().unwrap();
            self.widget.remove(w);
        }
    };
}

#[macro_export]
macro_rules! container_ext {
    ($comp:ident) => {
        impl $crate::ContainerExt for $comp {
            fn append<W>(self: &Rc<Self>, child: std::rc::Rc<W>)
            where
                W: $crate::ComponentExt + ?Sized,
            {
                let inner = child.inner();
                let w: &gtk::Widget = inner.downcast_ref().unwrap();
                self.widget.append(w);
            }
            $crate::default_clear! {}
            $crate::default_remove! {}
        }
    };
}
