simi 0.2.1

A framework for building wasm front-end web application in Rust
//! Supports keyed item in a for-loop
use super::{Element, Node, Text};
/// Holds all items created by a for-loop
pub struct KeyedFor {
    nodes: Vec<(u64, Node)>,
}

#[allow(clippy::new_without_default)]
impl KeyedFor {
    /// Create an empty KeyedFor node
    pub fn new() -> Self {
        Self { nodes: Vec::new() }
    }

    /// No node?
    pub fn is_empty(&self) -> bool {
        self.nodes.is_empty()
    }

    /// Get item at
    pub fn get(&self, index: usize) -> Option<&Node> {
        self.nodes.get(index).as_ref().map(|node| &node.1)
    }

    /// Remove item at index if key mismatch
    pub fn remove_if_key_mismatch(&mut self, index: usize, key: u64, real_parent: &web_sys::Node) {
        if index >= self.nodes.len() {
            return;
        }

        // It is safe here because
        //      index < self.nodes.len()
        // is true here
        let ckey = unsafe { self.nodes.get_unchecked(index).0 };
        if ckey != key {
            let mut node = self.nodes.remove(index).1;
            node.remove_real_node(real_parent);
        }
    }

    /// Create a text node at the given index if keys mismatch
    pub fn add_text_if_key_mismatch(
        &mut self,
        index: usize,
        key: u64,
        value: &impl ToString,
    ) -> Option<(&Text, Option<&web_sys::Node>)> {
        if index >= self.nodes.len() {
            let text = Node::TextExpression(Text::new(value.to_string()));
            self.nodes.push((key, text));
        } else {
            let ckey = unsafe { self.nodes.get_unchecked(index).0 };
            if ckey != key {
                let text = Node::TextExpression(Text::new(value.to_string()));
                self.nodes.insert(index, (key, text));
            } else {
                return None;
            }
        }
        let (p1, p2) = self.nodes.split_at(index + 1);
        let text = match p1.last().expect("Node at given index").1 {
            Node::TextExpression(ref text) => text,
            _ => panic!("Expected Node::TextExpression"),
        };
        let next_sibling = p2
            .first()
            .map(|n| n.1.get_first_real_node())
            .unwrap_or_else(|| None);
        Some((text, next_sibling))
    }

    /// Create element at the given index if keys mismatch, return newly created element or None
    /// The caller must (always) add the returned element to real DOM
    pub fn add_element_if_key_mismatch(
        &mut self,
        index: usize,
        key: u64,
        tag: &str,
    ) -> Option<(&mut Element, bool, Option<&web_sys::Node>)> {
        let len = self.nodes.len();
        let new = if len == 0 {
            self.nodes.push((key, Node::Element(Element::new(tag))));
            true
        } else if index >= len {
            let new = self.nodes[0].1.start_clone();
            self.nodes.push((key, new));
            false
        } else {
            // It is safe here because
            //      index < self.nodes.len()
            // is true here
            let ckey = unsafe { self.nodes.get_unchecked_mut(index).0 };
            if ckey != key {
                let new = self.nodes[0].1.start_clone();
                self.nodes.insert(index, (key, new));
                false
            } else {
                return None;
            }
        };
        let (p1, p2) = self.nodes.split_at_mut(index + 1);
        let element = match p1.last_mut().expect("Node at given index").1 {
            Node::Element(ref mut e) => e,
            _ => panic!("Expect Node::Element"),
        };
        let next_sibling = p2
            .first()
            .map(|n| n.1.get_first_real_node())
            .unwrap_or_else(|| None);
        Some((element, new, next_sibling))
    }

    /// Create/update text at given index. Return a reference to the text if it is new.
    /// For now, all NoHint, ItemValueChangeOnly and MixedChange
    /// will call to this method.
    pub fn text(&mut self, index: usize, key: u64, value: &impl ToString) -> Option<&Text> {
        if index >= self.nodes.len() {
            let text = Node::TextExpression(Text::new(value.to_string()));
            self.nodes.push((key, text));
        } else {
            let (ckey, text) = unsafe { self.nodes.get_unchecked_mut(index) };
            if *ckey != key {
                // For now, just change to the expected key and pretend that the content has changed
                *ckey = key;
            }
            match text {
                Node::TextExpression(text) => text.update_text(value.to_string()),
                _ => panic!("Expected Node::TextExpression"),
            }
            return None;
        }
        // Currently, if index is at the middle of this will always return None
        debug_assert_eq!(index + 1, self.nodes.len(), "Item must be the last");
        match self.nodes.get(index).expect("Node at given index").1 {
            Node::TextExpression(ref text) => Some(text),
            _ => panic!("Expected Node::TextExpression"),
        }
    }

    /// Create/get element at given index.
    /// For now, all NoHint, ItemValueChangeOnly and MixedChange
    /// will call to this method
    pub fn element(&mut self, index: usize, key: u64, tag: &str) -> (&mut Element, bool, bool) {
        let len = self.nodes.len();
        let (new, need_insert) = if len == 0 {
            self.nodes.push((key, Node::Element(Element::new(tag))));
            (true, true)
        } else if index >= len {
            let new = self.nodes[0].1.start_clone();
            self.nodes.push((key, new));
            (false, true)
        } else {
            // It is safe here because
            //      index < self.nodes.len()
            // is true here
            let (ckey, node) = unsafe { self.nodes.get_unchecked_mut(index) };
            if *ckey != key {
                // For now, just change to the expected key and pretend that everything has changed
                *ckey = key;
            }
            match node {
                Node::Element(e) => return (e, false, false),
                _ => panic!("Expect a Node::Element at the given index"),
            }
        };
        // Currently, if index is at the middle of this will always return need_insert=false
        let (ckey, node) = self.nodes.get_mut(index).expect("Element added");
        debug_assert_eq!(*ckey, key, "Key mismatch");
        match node {
            Node::Element(e) => (e, new, need_insert),
            _ => panic!("Expect a Node::Element at the given index"),
        }
    }

    /// Remove real nodes from real dom and return the next real sibling of the last real node of this list
    pub(crate) fn remove_and_get_next_sibling(
        &mut self,
        real_parent: &web_sys::Node,
    ) -> Option<web_sys::Node> {
        if self.nodes.is_empty() {
            return None;
        }
        let mut last = self.nodes.pop().expect("Simi bug: Arm must not be empty").1;
        let next_sibling = last.remove_and_get_next_sibling(real_parent);
        self.remove_real_node(real_parent);
        next_sibling
    }

    /// Remove real node from real dom
    pub(crate) fn remove_real_node(&mut self, real_parent: &web_sys::Node) {
        while let Some(mut node) = self.nodes.pop() {
            node.1.remove_real_node(real_parent);
        }
    }

    /// Remove all nodes after the given size from both real and virtual dom
    pub fn truncate(&mut self, size_after_removal: usize, real_parent: &web_sys::Node) {
        if size_after_removal == 0 {
            real_parent.set_text_content(None);
            self.nodes.truncate(0);
            return;
        }
        while self.nodes.len() > size_after_removal {
            let mut node = self.nodes.pop().expect("No more node to remove");
            node.1.remove_real_node(real_parent);
        }
    }

    /// Get next sibling of the last node
    pub fn get_next_sibling(&self) -> Option<web_sys::Node> {
        self.nodes
            .last()
            .map(|node| node.1.get_next_sibling())
            .unwrap_or_else(|| None)
    }

    /// Get first real node
    pub fn get_first_real_node(&self) -> Option<&web_sys::Node> {
        self.nodes
            .first()
            .map(|node| node.1.get_first_real_node())
            .unwrap_or_else(|| None)
    }

    /// Get first real node
    pub fn get_first_real_node_at(&self, index: usize) -> Option<web_sys::Node> {
        self.nodes
            .get(index)
            .map(|node| node.1.get_first_real_node())
            .unwrap_or_else(|| None)
            .cloned()
    }

    /// Clone everything except for tracked attributes, and for-loop content
    pub(super) fn clone(&self, _real_parent: &web_sys::Node) -> Self {
        // Content of a for-loop is not cloned
        Self { nodes: Vec::new() }
    }

    /// Start cloning self and all childs
    pub(super) fn start_clone(&self) -> Self {
        Self { nodes: Vec::new() }
    }
}