kinesis 0.0.1

A *blazingly fast* web framework built for Rust.
Documentation
use js_sys::Function;
use std::{
    cell::RefCell,
    collections::{HashMap, VecDeque},
    rc::Rc,
};
use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
use web_sys::{console, Document, Element, Event};

use super::{identifier::Identifier, Component, EventType, RenderType};
use crate::dom::{
    render_position::RenderPosition,
    renderable::{DynamicContent, Renderable, RenderedNode},
};
use crate::util::HashMapList;

/// Wrapper around a component, used to provide additional functionality and assist with rendering
/// the component to the DOM.
pub struct ComponentController {
    /// The component to be rendered
    component: Rc<RefCell<dyn Component>>,

    parent: Element,
    mounted_elements: Option<Vec<RenderedNode>>,

    /// A reference to the document in order to create elements
    document: Document,

    callbacks: Rc<RefCell<HashMap<(Identifier, EventType), Function>>>,

    dependency_registrations: Rc<RefCell<HashMapList<usize, usize>>>,

    identifier: Identifier,

    rendered_elements: Rc<RefCell<HashMap<RenderType, Vec<RenderedNode>>>>,
}

/// Wrapper for component controller, allowing a reference to be passed to closures as required
pub struct ComponentControllerRef(Rc<RefCell<ComponentController>>);

struct RenderQueueEntry {
    renderable: Box<dyn Renderable>,
    position: RenderPosition,
    identifier: Identifier,
}

impl ComponentControllerRef {
    pub fn new<C>(component: C, document: &Document, parent: Element) -> Self
    where
        C: Component + 'static,
    {
        Self(Rc::new(RefCell::new(ComponentController {
            component: Rc::new(RefCell::new(component)),
            parent,
            mounted_elements: None,
            document: document.clone(),
            callbacks: Rc::new(RefCell::new(HashMap::new())),
            dependency_registrations: Rc::new(RefCell::new(HashMapList::new())),
            identifier: Identifier::new(),

            rendered_elements: Rc::new(RefCell::new(HashMap::new())),
        })))
    }

    pub fn render(&self) -> Result<(), JsValue> {
        let (first_render, mut mounted_elements, mut element_queue) = {
            let mut controller = self.0.borrow_mut();

            // Take the mounted elements, so they can be passed into the DOM node render
            let first_render = controller.mounted_elements.is_none();
            let mounted_elements = controller.mounted_elements.take().into_iter().flatten();

            // Queue of elements to render (children will be pushed in same order)
            let element_queue = VecDeque::from_iter(
                controller
                    .component
                    .borrow()
                    .render(RenderType::Root)
                    .unwrap_or_default()
                    .into_iter()
                    .enumerate()
                    .map(|(index, node)| RenderQueueEntry {
                        renderable: node,
                        position: RenderPosition::Append(controller.parent.clone()),
                        identifier: controller.identifier.child(index),
                    }),
            );

            (first_render, mounted_elements, element_queue)
        };

        while let Some(RenderQueueEntry {
            renderable,
            position,
            identifier: renderable_identifier,
        }) = element_queue.pop_front()
        {
            let children_to_render = self.render_renderable(
                renderable,
                renderable_identifier,
                position,
                mounted_elements.next(),
            )?;

            if let Some(children_to_render) = children_to_render {
                for child in children_to_render {
                    element_queue.push_front(child);
                }
            }
        }

        // TODO: Maybe perform initial render? Or should this happen somewhere else?
        // let mut controller = self.0.borrow();
        // console::log_1(&"Running initial dependency render".into());
        // let mut dependency_registrations = controller.dependency_registrations.borrow_mut();
        // let component = controller.component.borrow();
        //
        // for (dependency, update_type) in dependency_registrations.iter() {
        //     console::log_1(&format!("rendering {} {}", dependency, update_type).into());
        //
        //     // Request the partial render from the component
        //     if let Some(renderables) = component.render(RenderType::Partial(*update_type)) {
        //         // Render each of the returned children
        //         for renderable in renderables {
        //             let position = todo!();
        //
        //             // TODO: How to properly identify content generated by a partial update?
        //             // Probably will be based off of the parent identifier, and the renderable's
        //             // position in the list.
        //             let renderable_identifier = todo!();
        //
        //             // TODO: How to store previously rendered dynamic value. Probably should group
        //             // them by the update type and parent. Something needs to handle whether the
        //             // element is replaced or not, and also how to make sure that they are
        //             // appropriately removed if required.
        //             let mounted_element = todo!();
        //
        //             self.render_renderable(
        //                 renderable,
        //                 renderable_identifier,
        //                 position,
        //                 mounted_element,
        //             );
        //         }
        //     }
        // }

        Ok(())
    }

    /// Generates a closure that can be attached to a DOM element, in order to respond to an event.
    /// The target element's identifier must be included, so that it can be sent back to the
    /// controller to identify the source of the event, and the event type is required in order to
    /// correctly trigger the relevant callback.
    fn create_event_callback_closure(
        &self,
        identifier: &Identifier,
        event_type: EventType,
    ) -> Box<dyn Fn(Event)> {
        let controller = self.0.borrow();
        let component = Rc::clone(&controller.component);
        let controller_rc = self.clone();

        let identifier = identifier.clone();

        Box::new(move |event: Event| {
            let changed =
                component
                    .borrow_mut()
                    .handle_event(identifier.clone(), event_type, event);

            // Check if anything was actually changed in the event handler
            if let Some(changed) = changed {
                let component = component.borrow();
                let controller = controller_rc.0.borrow();
                let dependency_registrations = controller.dependency_registrations.borrow();

                let mut debug_dep_counter = 0;

                for change in changed {
                    for update_type in dependency_registrations.get(&change).into_iter().flatten() {
                        // Perform a partial render of the component
                        let render_type = RenderType::Partial(*update_type);

                        // Fetch the previously rendered content
                        let mut rendered_elements = controller.rendered_elements.borrow_mut();
                        let existing = rendered_elements.get(&render_type);

                        let renderables = component.render(render_type.clone());

                        // Insert the newly rendered content
                        if let Some(renderables) = renderables {
                            // rendered_elements.insert(render_type, renderables);
                            // TODO: Build each of the renderables that are returned
                            // Need a recursive render call???

                            // TODO: Insert new renderables into the DOM
                        } else {
                            rendered_elements.remove(&render_type);
                        }

                        // TODO: Insert the generated content back into the DOM
                        debug_dep_counter += 1;
                    }
                }

                console::log_1(&format!("Updating {debug_dep_counter} deps").into());
            }
        })
    }

    /// Renders a renderable as indicated by the `position` argument.
    fn render_renderable(
        &self,
        renderable: Box<dyn Renderable>,
        renderable_identifier: Identifier,
        position: RenderPosition,
        mounted_element: Option<RenderedNode>,
    ) -> Result<Option<Vec<RenderQueueEntry>>, JsValue> {
        // A callback that can be used to generate a new event closure
        let generate_event_callback_closure = &|event_type| {
            let controller = self.0.borrow();
            let mut callbacks = controller.callbacks.borrow_mut();

            // Prepare event handler closure incase it needs to be re-created
            let event_closure =
                self.create_event_callback_closure(&renderable_identifier, event_type);

            // Cache the closures so they can be re-used
            callbacks
                .entry((renderable_identifier.clone(), event_type))
                .or_insert_with(|| {
                    // Create a closure to bind with JS for this specific handler
                    Closure::<dyn Fn(Event)>::new(event_closure)
                        .into_js_value()
                        .unchecked_into()
                })
                .clone()
        };

        // Build or update the element
        let document = self.0.borrow().document.clone();
        if let Some(build_result) =
            renderable.render(&document, mounted_element, generate_event_callback_closure)?
        {
            let mut controller = self.0.borrow_mut();

            {
                // Save dependencies
                let mut dependency_registrations = controller.dependency_registrations.borrow_mut();
                for (dependency, update_type) in build_result.dynamic_content.into_iter().flat_map(
                    |DynamicContent {
                         dependencies,
                         update_type,
                     }| {
                        dependencies
                            .into_iter()
                            .map(move |dependency| (dependency, update_type))
                    },
                ) {
                    dependency_registrations.insert(dependency, update_type);
                }
            }

            // Only perform render if a build result is returned and there's an element to
            // render
            if let Some(element) = &build_result.element {
                // Save the mounted element for this node
                // TODO: Change this to track by identifier
                controller
                    .mounted_elements
                    .get_or_insert(Vec::new())
                    .push(element.clone());

                // Render the element in its position
                position.render(element)?;

                // Line all of the children up for rendering
                if let RenderedNode::Element(element) = element {
                    let child_position = RenderPosition::Append(element.clone());

                    let children = build_result.children.map(|children| {
                        let children = children.into_iter().enumerate().map(|(index, child)| {
                            RenderQueueEntry {
                                renderable: child,
                                position: child_position.clone(),
                                identifier: renderable_identifier.child(index),
                            }
                        });

                        // TODO: Make sure order is correct here
                        children.collect()
                    });

                    // Only possible for the component to have children here
                    return Ok(children);
                }
            }
        }

        Ok(None)
    }
}

impl Clone for ComponentControllerRef {
    fn clone(&self) -> Self {
        Self(Rc::clone(&self.0))
    }
}