use crate::dom::program::app_context::WeakContext;
#[cfg(feature = "with-raf")]
use crate::dom::request_animation_frame;
#[cfg(feature = "with-ric")]
use crate::dom::request_idle_callback;
use crate::dom::{document, now, IdleDeadline, Measurements, Modifier};
use crate::dom::{util::body, AnimationFrameHandle, Application, DomPatch, IdleCallbackHandle};
use crate::html::{self, attributes::class, text};
use crate::vdom;
use crate::vdom::diff;
use app_context::AppContext;
use std::collections::hash_map::DefaultHasher;
use std::collections::BTreeMap;
use std::collections::VecDeque;
use std::hash::{Hash, Hasher};
use std::mem::ManuallyDrop;
use std::{
    any::TypeId,
    cell::{Ref, RefCell},
    rc::Rc,
    rc::Weak,
};
use wasm_bindgen::closure::Closure;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{self, Element, Node};
mod app_context;
pub(crate) type Closures = Vec<Closure<dyn FnMut(web_sys::Event)>>;
pub struct Program<APP, MSG>
where
    MSG: 'static,
{
    pub(crate) app_context: AppContext<APP, MSG>,
    pub(crate) root_node: Rc<RefCell<Option<Node>>>,
    mount_node: Rc<RefCell<Node>>,
    pub node_closures: Rc<RefCell<ActiveClosure>>,
    mount_procedure: MountProcedure,
    pending_patches: Rc<RefCell<VecDeque<DomPatch<MSG>>>>,
    idle_callback_handles: Rc<RefCell<Vec<IdleCallbackHandle>>>,
    animation_frame_handles: Rc<RefCell<Vec<AnimationFrameHandle>>>,
    pub(crate) event_closures: Rc<RefCell<Closures>>,
}
pub struct WeakProgram<APP, MSG>
where
    MSG: 'static,
{
    pub(crate) app_context: WeakContext<APP, MSG>,
    pub(crate) root_node: Weak<RefCell<Option<Node>>>,
    mount_node: Weak<RefCell<Node>>,
    pub node_closures: Weak<RefCell<ActiveClosure>>,
    mount_procedure: MountProcedure,
    pending_patches: Weak<RefCell<VecDeque<DomPatch<MSG>>>>,
    idle_callback_handles: Weak<RefCell<Vec<IdleCallbackHandle>>>,
    animation_frame_handles: Weak<RefCell<Vec<AnimationFrameHandle>>>,
    pub(crate) event_closures: Weak<RefCell<Closures>>,
}
pub type ActiveClosure =
    BTreeMap<usize, BTreeMap<&'static str, Closure<dyn FnMut(web_sys::Event)>>>;
#[derive(Clone, Copy)]
pub enum MountAction {
    Append,
    ClearAppend,
    Replace,
}
#[derive(Clone, Copy)]
pub enum MountTarget {
    MountNode,
    ShadowRoot,
}
#[derive(Clone, Copy)]
struct MountProcedure {
    action: MountAction,
    target: MountTarget,
}
impl<APP, MSG> WeakProgram<APP, MSG>
where
    MSG: 'static,
{
    pub fn upgrade(&self) -> Option<Program<APP, MSG>> {
        if let Some(app_context) = self.app_context.upgrade() {
            if let Some(root_node) = self.root_node.upgrade() {
                if let Some(mount_node) = self.mount_node.upgrade() {
                    if let Some(node_closures) = self.node_closures.upgrade() {
                        if let Some(pending_patches) = self.pending_patches.upgrade() {
                            if let Some(idle_callback_handles) =
                                self.idle_callback_handles.upgrade()
                            {
                                if let Some(animation_frame_handles) =
                                    self.animation_frame_handles.upgrade()
                                {
                                    if let Some(event_closures) = self.event_closures.upgrade() {
                                        return Some(Program {
                                            app_context,
                                            root_node,
                                            mount_node,
                                            node_closures,
                                            mount_procedure: self.mount_procedure,
                                            pending_patches,
                                            idle_callback_handles,
                                            animation_frame_handles,
                                            event_closures,
                                        });
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        None
    }
}
impl<APP, MSG> Clone for WeakProgram<APP, MSG>
where
    MSG: 'static,
{
    fn clone(&self) -> Self {
        WeakProgram {
            app_context: self.app_context.clone(),
            root_node: Weak::clone(&self.root_node),
            mount_node: Weak::clone(&self.mount_node),
            node_closures: Weak::clone(&self.node_closures),
            mount_procedure: self.mount_procedure,
            pending_patches: Weak::clone(&self.pending_patches),
            idle_callback_handles: Weak::clone(&self.idle_callback_handles),
            animation_frame_handles: Weak::clone(&self.animation_frame_handles),
            event_closures: Weak::clone(&self.event_closures),
        }
    }
}
impl<APP, MSG> Program<APP, MSG>
where
    MSG: 'static,
{
    pub fn downgrade(&self) -> WeakProgram<APP, MSG> {
        WeakProgram {
            app_context: AppContext::downgrade(&self.app_context),
            root_node: Rc::downgrade(&self.root_node),
            mount_node: Rc::downgrade(&self.mount_node),
            node_closures: Rc::downgrade(&self.node_closures),
            mount_procedure: self.mount_procedure,
            pending_patches: Rc::downgrade(&self.pending_patches),
            idle_callback_handles: Rc::downgrade(&self.idle_callback_handles),
            animation_frame_handles: Rc::downgrade(&self.animation_frame_handles),
            event_closures: Rc::downgrade(&self.event_closures),
        }
    }
}
impl<APP, MSG> Clone for Program<APP, MSG>
where
    MSG: 'static,
{
    fn clone(&self) -> Self {
        Program {
            app_context: self.app_context.clone(),
            root_node: Rc::clone(&self.root_node),
            mount_node: Rc::clone(&self.mount_node),
            node_closures: Rc::clone(&self.node_closures),
            mount_procedure: self.mount_procedure,
            pending_patches: Rc::clone(&self.pending_patches),
            idle_callback_handles: Rc::clone(&self.idle_callback_handles),
            animation_frame_handles: Rc::clone(&self.animation_frame_handles),
            event_closures: Rc::clone(&self.event_closures),
        }
    }
}
impl<APP, MSG> Drop for Program<APP, MSG>
where
    MSG: 'static,
{
    fn drop(&mut self) {
        }
}
impl<APP, MSG> Program<APP, MSG>
where
    MSG: 'static,
    APP: Application<MSG> + 'static,
{
    pub fn new(
        app: APP,
        mount_node: &web_sys::Node,
        action: MountAction,
        target: MountTarget,
    ) -> Self {
        Program {
            app_context: AppContext::new(app),
            root_node: Rc::new(RefCell::new(None)),
            mount_node: Rc::new(RefCell::new(mount_node.clone())),
            node_closures: Rc::new(RefCell::new(ActiveClosure::new())),
            mount_procedure: MountProcedure { action, target },
            pending_patches: Rc::new(RefCell::new(VecDeque::new())),
            idle_callback_handles: Rc::new(RefCell::new(vec![])),
            animation_frame_handles: Rc::new(RefCell::new(vec![])),
            event_closures: Rc::new(RefCell::new(vec![])),
        }
    }
    pub fn app(&self) -> Ref<'_, APP> {
        self.app_context.app.borrow()
    }
    fn after_mounted(&mut self) {
        let cmd = self.app_context.init_app();
        cmd.emit(self.clone());
        self.inject_dynamic_style();
    }
    fn app_hash() -> u64 {
        let type_id = TypeId::of::<APP>();
        let mut hasher = DefaultHasher::new();
        type_id.hash(&mut hasher);
        hasher.finish()
    }
    fn inject_stylesheet(&mut self) {
        let static_style = self.app_context.static_style();
        if !static_style.is_empty() {
            let class_names = format!("static {}", Self::app_hash());
            self.inject_style(class_names, &static_style);
        }
    }
    fn inject_dynamic_style(&mut self) {
        let dynamic_style = self.app_context.dynamic_style();
        if !dynamic_style.is_empty() {
            let class_names = format!("dynamic {}", Self::app_hash());
            self.inject_style(class_names, &dynamic_style);
        }
    }
    pub fn mount_node(&self) -> web_sys::Node {
        self.mount_node.borrow().clone()
    }
    pub fn append_to_mount(app: APP, mount_node: &web_sys::Node) -> ManuallyDrop<Self> {
        let mut program = Self::new(app, mount_node, MountAction::Append, MountTarget::MountNode);
        program.mount();
        ManuallyDrop::new(program)
    }
    pub fn replace_mount(app: APP, mount_node: &web_sys::Node) -> ManuallyDrop<Self> {
        let mut program = Self::new(
            app,
            mount_node,
            MountAction::Replace,
            MountTarget::MountNode,
        );
        program.mount();
        ManuallyDrop::new(program)
    }
    pub fn clear_append_to_mount(app: APP, mount_node: &web_sys::Node) -> ManuallyDrop<Self> {
        let mut program = Self::new(
            app,
            mount_node,
            MountAction::ClearAppend,
            MountTarget::MountNode,
        );
        program.mount();
        ManuallyDrop::new(program)
    }
    pub fn clear_mount(app: APP, mount_node: &web_sys::Node) -> ManuallyDrop<Self> {
        Self::clear_append_to_mount(app, mount_node)
    }
    pub fn clear_mount_to_body(app: APP) -> ManuallyDrop<Self> {
        Self::clear_append_to_mount(app, &body())
    }
    pub fn mount_to_body(app: APP) -> ManuallyDrop<Self> {
        Self::append_to_mount(app, &body())
    }
    pub fn pre_mount(&mut self) {
        self.inject_stylesheet();
    }
    pub fn mount(&mut self) {
        self.pre_mount();
        let created_node = self.create_dom_node(&self.app_context.current_vdom());
        let mount_node: web_sys::Node = match self.mount_procedure.target {
            MountTarget::MountNode => self.mount_node.borrow().clone(),
            MountTarget::ShadowRoot => {
                let mount_element: web_sys::Element =
                    self.mount_node.borrow().clone().unchecked_into();
                mount_element
                    .attach_shadow(&web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open))
                    .expect("unable to attached shadow");
                let mount_shadow = mount_element.shadow_root().expect("must have a shadow");
                *self.mount_node.borrow_mut() = mount_shadow.unchecked_into();
                self.mount_node.borrow().clone()
            }
        };
        match self.mount_procedure.action {
            MountAction::Append => {
                Self::append_child_and_dispatch_mount_event(&mount_node, &created_node);
            }
            MountAction::ClearAppend => {
                Self::clear_children(&mount_node);
                Self::append_child_and_dispatch_mount_event(&mount_node, &created_node);
            }
            MountAction::Replace => {
                let mount_element: &Element = mount_node.unchecked_ref();
                mount_element
                    .replace_with_with_node_1(&created_node)
                    .expect("Could not append child to mount");
                Self::dispatch_mount_event(&created_node);
                *self.mount_node.borrow_mut() = created_node.clone()
            }
        }
        *self.root_node.borrow_mut() = Some(created_node);
        self.after_mounted();
    }
    #[cfg(feature = "with-ric")]
    fn dispatch_pending_msgs_with_ric(&mut self) -> Result<(), JsValue> {
        let program = Program::downgrade(&self);
        let handle = request_idle_callback(move |deadline| {
            let mut program = program.upgrade().expect("must upgrade");
            program
                .dispatch_pending_msgs(Some(deadline))
                .expect("must execute")
        })
        .expect("must execute");
        self.idle_callback_handles.borrow_mut().push(handle);
        Ok(())
    }
    fn dispatch_pending_msgs(&mut self, deadline: Option<IdleDeadline>) -> Result<(), JsValue> {
        if !self.app_context.has_pending_msgs() {
            return Ok(());
        }
        let mut did_complete = true;
        while self.app_context.dispatch_pending_msg() {
            if let Some(deadline) = &deadline {
                if deadline.did_timeout() {
                    did_complete = false;
                    break;
                }
            }
        }
        if !did_complete {
            #[cfg(feature = "with-ric")]
            self.dispatch_pending_msgs_with_ric()
                .expect("must complete");
        }
        Ok(())
    }
    pub fn update_dom(&mut self, modifier: &Modifier) -> Result<Measurements, JsValue> {
        let t1 = now();
        let view = self.app_context.view();
        let t2 = now();
        let node_count = view.node_count();
        let total_patches = self.update_dom_with_vdom(view).expect("must not error");
        let t3 = now();
        let strong_count = self.app_context.strong_count();
        let weak_count = self.app_context.weak_count();
        let root_node_count = Rc::strong_count(&self.root_node);
        assert_eq!(strong_count, root_node_count);
        let measurements = Measurements {
            name: modifier.measurement_name.to_string(),
            node_count,
            build_view_took: t2 - t1,
            total_patches,
            dom_update_took: t3 - t2,
            total_time: t3 - t1,
            strong_count,
            weak_count,
        };
        if measurements.total_time > 16.0 {
            #[cfg(all(feature = "with-measure", feature = "with-debug"))]
            {
                log::warn!("dispatch took {}ms", measurements.total_time.round());
            }
        }
        Ok(measurements)
    }
    fn create_dom_patch(&self, new_vdom: &vdom::Node<MSG>) -> Vec<DomPatch<MSG>> {
        let current_vdom = self.app_context.current_vdom();
        let patches = diff(¤t_vdom, &new_vdom);
        #[cfg(all(feature = "with-debug", feature = "log-patches"))]
        {
            log::debug!("There are {} patches", patches.len());
            log::debug!("patches: {patches:#?}");
        }
        let dom_patches = self
            .convert_patches(&patches)
            .expect("must convert patches");
        dom_patches
    }
    pub fn update_dom_with_vdom(&mut self, new_vdom: vdom::Node<MSG>) -> Result<usize, JsValue> {
        let dom_patches = self.create_dom_patch(&new_vdom);
        let total_patches = dom_patches.len();
        self.pending_patches.borrow_mut().extend(dom_patches);
        #[cfg(feature = "with-raf")]
        self.apply_pending_patches_with_raf().expect("raf");
        #[cfg(not(feature = "with-raf"))]
        self.apply_pending_patches().expect("raf");
        self.app_context.set_current_dom(new_vdom);
        Ok(total_patches)
    }
    #[cfg(feature = "with-raf")]
    fn apply_pending_patches_with_raf(&mut self) -> Result<(), JsValue> {
        let program = Program::downgrade(&self);
        let handle = request_animation_frame(move || {
            let mut program = program.upgrade().expect("must upgrade");
            program.apply_pending_patches().expect("must not error");
        })
        .expect("must execute");
        self.animation_frame_handles.borrow_mut().push(handle);
        Ok(())
    }
    fn apply_pending_patches(&mut self) -> Result<(), JsValue> {
        if self.pending_patches.borrow().is_empty() {
            return Ok(());
        }
        let dom_patches: Vec<DomPatch<MSG>> = self.pending_patches.borrow_mut().drain(..).collect();
        for dom_patch in dom_patches {
            self.apply_dom_patch(dom_patch)
                .expect("must apply dom patch");
        }
        Ok(())
    }
    fn dispatch_dom_changes(&mut self, modifier: &Modifier) {
        #[allow(unused_variables)]
        let measurements = self.update_dom(modifier).expect("must update dom");
        #[cfg(feature = "with-measure")]
        if modifier.log_measurements && measurements.total_patches > 0 {
            let cmd_measurement = self.app_context.measurements(measurements);
            cmd_measurement.emit(self.clone());
        }
    }
    #[cfg(feature = "with-ric")]
    fn dispatch_inner_with_ric(&self) {
        let program = Program::downgrade(&self);
        let handle = request_idle_callback(move |deadline| {
            if let Some(mut program) = program.upgrade() {
                program.dispatch_inner(Some(deadline));
            } else {
                log::warn!("unable to upgrade program.. maybe try again next time..");
            }
        })
        .expect("must execute");
        self.idle_callback_handles.borrow_mut().push(handle);
    }
    #[allow(unused)]
    #[cfg(feature = "with-raf")]
    fn dispatch_inner_with_raf(&self) {
        let program = Program::downgrade(&self);
        let handle = request_animation_frame(move || {
            let mut program = program.upgrade().expect("must upgrade");
            program.dispatch_inner(None);
        })
        .expect("must execute");
        self.animation_frame_handles.borrow_mut().push(handle);
    }
    fn dispatch_inner_with_priority_ric(&self) {
        #[cfg(feature = "with-ric")]
        self.dispatch_inner_with_ric();
        #[cfg(not(feature = "with-ric"))]
        {
            #[cfg(feature = "with-raf")]
            self.dispatch_inner_with_raf();
            #[cfg(not(feature = "with-raf"))]
            {
                let program = Program::downgrade(&self);
                wasm_bindgen_futures::spawn_local(async move {
                    if let Some(mut program) = program.upgrade() {
                        program.dispatch_inner(None);
                    } else {
                        log::warn!(
                            "unable to upgrade program here, in dispatch_inner_with_priority_ric"
                        );
                    }
                })
            }
        }
    }
    fn dispatch_inner(&mut self, deadline: Option<IdleDeadline>) {
        self.dispatch_pending_msgs(deadline)
            .expect("must dispatch msgs");
        if self.app_context.has_pending_msgs() {
            self.dispatch_pending_msgs(None)
                .expect("must dispatch all pending msgs");
        }
        if self.app_context.has_pending_msgs() {
            panic!("Can not proceed until previous pending msgs are dispatched..");
        }
        let cmd = self.app_context.batch_pending_cmds();
        if !self.pending_patches.borrow().is_empty() {
            log::error!(
                "BEFORE DOM updates there are still Remaining pending patches: {}",
                self.pending_patches.borrow().len()
            );
        }
        if cmd.modifier.should_update_view {
            self.dispatch_dom_changes(&cmd.modifier);
        }
        if !self.pending_patches.borrow().is_empty() {
            self.apply_pending_patches()
                .expect("applying pending patches..");
        }
        if !self.pending_patches.borrow().is_empty() {
            log::error!(
                "Remaining pending patches: {}",
                self.pending_patches.borrow().len()
            );
            panic!(
                "There are still pending patches.. can not emit cmd, if all pending patches
            has not been applied yet!"
            );
        }
        cmd.emit(self.clone());
    }
    fn inject_style(&mut self, class_names: String, style: &str) {
        let style_node = html::tags::style([class(class_names)], [text(style)]);
        let created_node = self.create_dom_node(&style_node);
        let head = document().head().expect("must have a head");
        head.append_child(&created_node).expect("must append style");
    }
    pub fn inject_style_to_mount(&mut self, style: &str) {
        let style_node = html::tags::style([], [text(style)]);
        let created_node = self.create_dom_node(&style_node);
        self.mount_node
            .borrow_mut()
            .append_child(&created_node)
            .expect("could not append child to mount shadow");
    }
    pub fn dispatch_multiple(&mut self, msgs: impl IntoIterator<Item = MSG>) {
        self.app_context.push_msgs(msgs);
        self.dispatch_inner_with_priority_ric();
    }
    pub fn dispatch(&mut self, msg: MSG) {
        self.dispatch_multiple([msg])
    }
}