//! Platform-agnostic node tree.
//!
//! `PlatformNode` is the universal representation of a UI element.
//! Each backend converts this to its native equivalent.

/// An attribute on a platform node.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Attribute {
    pub name: String,
    pub value: String,
}

impl Attribute {
    pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
        Self { name: name.into(), value: value.into() }
    }
}

/// A boxed event handler function.
/// Using `Box<dyn Fn(String)>` so platform nodes can store handlers without
/// knowing the native event type.
pub type EventHandler = Box<dyn Fn(String) + Send + Sync>;

/// The kind of a platform node.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PlatformNodeKind {
    /// An element node: `<div>`, `<button>`, etc.
    Element { tag: String },
    /// A plain text node.
    Text { content: String },
    /// A component boundary marker (used for patch reconciliation).
    Component { name: String },
    /// A fragment — groups children without a wrapper element.
    Fragment,
}

/// A platform-agnostic UI node.
///
/// This is the universal intermediate representation. Each backend renders
/// `PlatformNode` trees to its native format.
#[derive(Debug)]
pub struct PlatformNode {
    pub kind: PlatformNodeKind,
    pub attributes: Vec<Attribute>,
    pub children: Vec<PlatformNode>,
    /// Opaque platform handle — the backend stores its native pointer/reference
    /// here after mounting. `None` before mount.
    pub native_handle: Option<usize>,
}

impl PlatformNode {
    /// Create an element node.
    pub fn element(tag: impl Into<String>) -> Self {
        Self {
            kind: PlatformNodeKind::Element { tag: tag.into() },
            attributes: Vec::new(),
            children: Vec::new(),
            native_handle: None,
        }
    }

    /// Create a text node.
    pub fn text(content: impl Into<String>) -> Self {
        Self {
            kind: PlatformNodeKind::Text { content: content.into() },
            attributes: Vec::new(),
            children: Vec::new(),
            native_handle: None,
        }
    }

    /// Create a fragment node.
    pub fn fragment() -> Self {
        Self {
            kind: PlatformNodeKind::Fragment,
            attributes: Vec::new(),
            children: Vec::new(),
            native_handle: None,
        }
    }

    /// Add an attribute.
    pub fn with_attr(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
        self.attributes.push(Attribute::new(name, value));
        self
    }

    /// Add a child node.
    pub fn with_child(mut self, child: PlatformNode) -> Self {
        self.children.push(child);
        self
    }

    /// Get the tag name if this is an element node.
    pub fn tag(&self) -> Option<&str> {
        match &self.kind {
            PlatformNodeKind::Element { tag } => Some(tag),
            _ => None,
        }
    }

    /// Get the text content if this is a text node.
    pub fn text_content(&self) -> Option<&str> {
        match &self.kind {
            PlatformNodeKind::Text { content } => Some(content),
            _ => None,
        }
    }

    /// Render the node tree to an HTML string.
    /// This is used by the server backend and for testing all backends.
    pub fn to_html(&self) -> String {
        match &self.kind {
            PlatformNodeKind::Text { content } => html_escape(content),
            PlatformNodeKind::Fragment => {
                self.children.iter().map(|c| c.to_html()).collect()
            }
            PlatformNodeKind::Component { name } => {
                format!("<!-- component:{} -->", name)
            }
            PlatformNodeKind::Element { tag } => {
                let mut out = format!("<{}", tag);
                for attr in &self.attributes {
                    out.push_str(&format!(" {}=\"{}\"", attr.name, html_escape_attr(&attr.value)));
                }
                // Void elements — no closing tag
                if is_void_element(tag) {
                    out.push_str(" />");
                    return out;
                }
                out.push('>');
                for child in &self.children {
                    out.push_str(&child.to_html());
                }
                out.push_str(&format!("</{}>", tag));
                out
            }
        }
    }
}

fn html_escape(s: &str) -> String {
    s.replace('&', "&amp;")
        .replace('<', "&lt;")
        .replace('>', "&gt;")
}

fn html_escape_attr(s: &str) -> String {
    html_escape(s).replace('"', "&quot;")
}

fn is_void_element(tag: &str) -> bool {
    matches!(
        tag,
        "area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input"
        | "link" | "meta" | "param" | "source" | "track" | "wbr"
    )
}
