use crate::{arena::ElementId, AttributeValue, Template};

/// Something that can handle the mutations that are generated by the diffing process and apply them to the Real DOM
///
/// This object provides a bunch of important information for a renderer to use patch the Real Dom with the state of the
/// VirtualDom. This includes the scopes that were modified, the templates that were discovered, and a list of changes
/// in the form of a [`Mutation`].
///
/// These changes are specific to one subtree, so to patch multiple subtrees, you'd need to handle each set separately.
///
/// Templates, however, apply to all subtrees, not just target subtree.
///
/// Mutations are the only link between the RealDOM and the VirtualDOM.
pub trait WriteMutations {
    /// Add these m children to the target element
    ///
    /// Id: The ID of the element being mounted to
    /// M: The number of nodes on the stack to append to the target element
    fn append_children(&mut self, id: ElementId, m: usize);

    /// Assign the element at the given path the target ElementId.
    ///
    /// The path is in the form of a list of indices based on children. Templates cannot have more than 255 children per
    /// element, hence the use of a single byte.
    ///
    /// Path: The path of the child of the topmost node on the stack. A path of `[]` represents the topmost node. A path of `[0]` represents the first child. `[0,1,2]` represents 1st child's 2nd child's 3rd child.
    /// Id: The ID we're assigning to this element/placeholder. This will be used later to modify the element or replace it with another element.
    fn assign_node_id(&mut self, path: &'static [u8], id: ElementId);

    /// Create a placeholder in the DOM that we will use later.
    ///
    /// Dioxus currently requires the use of placeholders to maintain a re-entrance point for things like list diffing
    ///
    /// Id: The ID we're assigning to this element/placeholder. This will be used later to modify the element or replace it with another element.
    fn create_placeholder(&mut self, id: ElementId);

    /// Create a node specifically for text with the given value
    ///
    /// Value: The text content of this text node
    /// Id: The ID we're assigning to this specific text nodes. This will be used later to modify the element or replace it with another element.
    fn create_text_node(&mut self, value: &str, id: ElementId);

    /// Load and clone an existing node from a template saved under that specific name
    ///
    /// Dioxus guarantees that the renderer will have already been provided the template.
    /// When the template is picked up in the template list, it should be saved under its "name" - here, the name
    ///
    /// Name: The unique "name" of the template based on the template location. When paired with `rsx!`, this is autogenerated
    /// Index: The index root we loading from the template. The template is stored as a list of nodes. This index represents the position of that root
    /// Id: The ID we're assigning to this element being loaded from the template (This will be used later to move the element around in lists)
    fn load_template(&mut self, template: Template, index: usize, id: ElementId);

    /// Replace the target element (given by its ID) with the topmost m nodes on the stack
    ///
    /// id: The ID of the node we're going to replace with new nodes
    /// m: The number of nodes on the stack to replace the target element with
    fn replace_node_with(&mut self, id: ElementId, m: usize);

    /// Replace an existing element in the template at the given path with the m nodes on the stack
    ///
    /// Path: The path of the child of the topmost node on the stack. A path of `[]` represents the topmost node. A path of `[0]` represents the first child. `[0,1,2]` represents 1st child's 2nd child's 3rd child.
    /// M: The number of nodes on the stack to replace the target element with
    fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize);

    /// Insert a number of nodes after a given node.
    ///
    /// Id: The ID of the node to insert after.
    /// M: The number of nodes on the stack to insert after the target node.
    fn insert_nodes_after(&mut self, id: ElementId, m: usize);

    /// Insert a number of nodes before a given node.
    ///
    /// Id: The ID of the node to insert before.
    /// M: The number of nodes on the stack to insert before the target node.
    fn insert_nodes_before(&mut self, id: ElementId, m: usize);

    /// Set the value of a node's attribute.
    ///
    /// Name: The name of the attribute to set.
    /// NS: The (optional) namespace of the attribute. For instance, "style" is in the "style" namespace.
    /// Value: The value of the attribute.
    /// Id: The ID of the node to set the attribute of.
    fn set_attribute(
        &mut self,
        name: &'static str,
        ns: Option<&'static str>,
        value: &AttributeValue,
        id: ElementId,
    );

    /// Set the text content of a node.
    ///
    /// Value: The textcontent of the node
    /// Id: The ID of the node to set the textcontent of.
    fn set_node_text(&mut self, value: &str, id: ElementId);

    /// Create a new Event Listener.
    ///
    /// Name: The name of the event to listen for.
    /// Id: The ID of the node to attach the listener to.
    fn create_event_listener(&mut self, name: &'static str, id: ElementId);

    /// Remove an existing Event Listener.
    ///
    /// Name: The name of the event to remove.
    /// Id: The ID of the node to remove.
    fn remove_event_listener(&mut self, name: &'static str, id: ElementId);

    /// Remove a particular node from the DOM
    ///
    /// Id: The ID of the node to remove.
    fn remove_node(&mut self, id: ElementId);

    /// Push the given root node onto our stack.
    ///
    /// Id: The ID of the root node to push.
    fn push_root(&mut self, id: ElementId);
}

/// A `Mutation` represents a single instruction for the renderer to use to modify the UI tree to match the state
/// of the Dioxus VirtualDom.
///
/// These edits can be serialized and sent over the network or through any interface
#[derive(Debug, PartialEq)]
pub enum Mutation {
    /// Add these m children to the target element
    AppendChildren {
        /// The ID of the element being mounted to
        id: ElementId,

        /// The number of nodes on the stack to append to the target element
        m: usize,
    },

    /// Assign the element at the given path the target ElementId.
    ///
    /// The path is in the form of a list of indices based on children. Templates cannot have more than 255 children per
    /// element, hence the use of a single byte.
    AssignId {
        /// The path of the child of the topmost node on the stack
        ///
        /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
        /// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
        path: &'static [u8],

        /// The ID we're assigning to this element/placeholder.
        ///
        /// This will be used later to modify the element or replace it with another element.
        id: ElementId,
    },

    /// Create a placeholder in the DOM that we will use later.
    ///
    /// Dioxus currently requires the use of placeholders to maintain a re-entrance point for things like list diffing
    CreatePlaceholder {
        /// The ID we're assigning to this element/placeholder.
        ///
        /// This will be used later to modify the element or replace it with another element.
        id: ElementId,
    },

    /// Create a node specifically for text with the given value
    CreateTextNode {
        /// The text content of this text node
        value: String,

        /// The ID we're assigning to this specific text nodes
        ///
        /// This will be used later to modify the element or replace it with another element.
        id: ElementId,
    },

    /// Load and clone an existing node from a template with a given ID
    ///
    /// Dioxus guarantees that the renderer will have already been provided the template.
    /// When the template is picked up in the template list, it should be saved under its "name" - here, the name
    LoadTemplate {
        /// Which root are we loading from the template?
        ///
        /// The template is stored as a list of nodes. This index represents the position of that root
        index: usize,

        /// The ID we're assigning to this element being loaded from the template
        ///
        /// This will be used later to move the element around in lists
        id: ElementId,
    },

    /// Replace the target element (given by its ID) with the topmost m nodes on the stack
    ReplaceWith {
        /// The ID of the node we're going to replace with
        id: ElementId,

        /// The number of nodes on the stack to replace the target element with
        m: usize,
    },

    /// Replace an existing element in the template at the given path with the m nodes on the stack
    ReplacePlaceholder {
        /// The path of the child of the topmost node on the stack
        ///
        /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
        /// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
        path: &'static [u8],

        /// The number of nodes on the stack to replace the target element with
        m: usize,
    },

    /// Insert a number of nodes after a given node.
    InsertAfter {
        /// The ID of the node to insert after.
        id: ElementId,

        /// The number of nodes on the stack to insert after the target node.
        m: usize,
    },

    /// Insert a number of nodes before a given node.
    InsertBefore {
        /// The ID of the node to insert before.
        id: ElementId,

        /// The number of nodes on the stack to insert before the target node.
        m: usize,
    },

    /// Set the value of a node's attribute.
    SetAttribute {
        /// The name of the attribute to set.
        name: &'static str,

        /// The (optional) namespace of the attribute.
        /// For instance, "style" is in the "style" namespace.
        ns: Option<&'static str>,

        /// The value of the attribute.
        value: AttributeValue,

        /// The ID of the node to set the attribute of.
        id: ElementId,
    },

    /// Set the textcontent of a node.
    SetText {
        /// The textcontent of the node
        value: String,

        /// The ID of the node to set the textcontent of.
        id: ElementId,
    },

    /// Create a new Event Listener.
    NewEventListener {
        /// The name of the event to listen for.
        name: String,

        /// The ID of the node to attach the listener to.
        id: ElementId,
    },

    /// Remove an existing Event Listener.
    RemoveEventListener {
        /// The name of the event to remove.
        name: String,

        /// The ID of the node to remove.
        id: ElementId,
    },

    /// Remove a particular node from the DOM
    Remove {
        /// The ID of the node to remove.
        id: ElementId,
    },

    /// Push the given root node onto our stack.
    PushRoot {
        /// The ID of the root node to push.
        id: ElementId,
    },
}

/// A static list of mutations that can be applied to the DOM. Note: this list does not contain any `Any` attribute values
#[derive(Debug, PartialEq, Default)]
pub struct Mutations {
    /// Any mutations required to patch the renderer to match the layout of the VirtualDom
    pub edits: Vec<Mutation>,
}

impl WriteMutations for Mutations {
    fn append_children(&mut self, id: ElementId, m: usize) {
        self.edits.push(Mutation::AppendChildren { id, m })
    }

    fn assign_node_id(&mut self, path: &'static [u8], id: ElementId) {
        self.edits.push(Mutation::AssignId { path, id })
    }

    fn create_placeholder(&mut self, id: ElementId) {
        self.edits.push(Mutation::CreatePlaceholder { id })
    }

    fn create_text_node(&mut self, value: &str, id: ElementId) {
        self.edits.push(Mutation::CreateTextNode {
            value: value.into(),
            id,
        })
    }

    fn load_template(&mut self, _template: Template, index: usize, id: ElementId) {
        self.edits.push(Mutation::LoadTemplate { index, id })
    }

    fn replace_node_with(&mut self, id: ElementId, m: usize) {
        self.edits.push(Mutation::ReplaceWith { id, m })
    }

    fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) {
        self.edits.push(Mutation::ReplacePlaceholder { path, m })
    }

    fn insert_nodes_after(&mut self, id: ElementId, m: usize) {
        self.edits.push(Mutation::InsertAfter { id, m })
    }

    fn insert_nodes_before(&mut self, id: ElementId, m: usize) {
        self.edits.push(Mutation::InsertBefore { id, m })
    }

    fn set_attribute(
        &mut self,
        name: &'static str,
        ns: Option<&'static str>,
        value: &AttributeValue,
        id: ElementId,
    ) {
        self.edits.push(Mutation::SetAttribute {
            name,
            ns,
            value: match value {
                AttributeValue::Text(s) => AttributeValue::Text(s.clone()),
                AttributeValue::Bool(b) => AttributeValue::Bool(*b),
                AttributeValue::Float(n) => AttributeValue::Float(*n),
                AttributeValue::Int(n) => AttributeValue::Int(*n),
                AttributeValue::None => AttributeValue::None,
                _ => panic!("Cannot serialize attribute value"),
            },
            id,
        })
    }

    fn set_node_text(&mut self, value: &str, id: ElementId) {
        self.edits.push(Mutation::SetText {
            value: value.into(),
            id,
        })
    }

    fn create_event_listener(&mut self, name: &'static str, id: ElementId) {
        self.edits.push(Mutation::NewEventListener {
            name: name.into(),
            id,
        })
    }

    fn remove_event_listener(&mut self, name: &'static str, id: ElementId) {
        self.edits.push(Mutation::RemoveEventListener {
            name: name.into(),
            id,
        })
    }

    fn remove_node(&mut self, id: ElementId) {
        self.edits.push(Mutation::Remove { id })
    }

    fn push_root(&mut self, id: ElementId) {
        self.edits.push(Mutation::PushRoot { id })
    }
}

/// A struct that ignores all mutations
pub struct NoOpMutations;

impl WriteMutations for NoOpMutations {
    fn append_children(&mut self, _: ElementId, _: usize) {}

    fn assign_node_id(&mut self, _: &'static [u8], _: ElementId) {}

    fn create_placeholder(&mut self, _: ElementId) {}

    fn create_text_node(&mut self, _: &str, _: ElementId) {}

    fn load_template(&mut self, _: Template, _: usize, _: ElementId) {}

    fn replace_node_with(&mut self, _: ElementId, _: usize) {}

    fn replace_placeholder_with_nodes(&mut self, _: &'static [u8], _: usize) {}

    fn insert_nodes_after(&mut self, _: ElementId, _: usize) {}

    fn insert_nodes_before(&mut self, _: ElementId, _: usize) {}

    fn set_attribute(
        &mut self,
        _: &'static str,
        _: Option<&'static str>,
        _: &AttributeValue,
        _: ElementId,
    ) {
    }

    fn set_node_text(&mut self, _: &str, _: ElementId) {}

    fn create_event_listener(&mut self, _: &'static str, _: ElementId) {}

    fn remove_event_listener(&mut self, _: &'static str, _: ElementId) {}

    fn remove_node(&mut self, _: ElementId) {}

    fn push_root(&mut self, _: ElementId) {}
}
