//! Server backend — SSR: render to HTML string, served by axum.
//!
//! This is the primary SSR backend. An axum handler calls `render_to_string()`
//! on the component tree and returns the result as an HTTP response.
//!
//! The same component code runs server-side without any changes. Only the
//! backend (chosen by `el.toml`) differs.

use crate::{EventHandler, PlatformBackend, PlatformError, PlatformNode, PlatformResult};

/// Server-side rendering backend.
///
/// Renders component trees to full HTML strings. No DOM, no browser APIs.
/// An axum handler uses this backend to generate the initial page HTML.
pub struct ServerBackend {
    /// Whether to emit hydration markers (`data-el-hydrate`) for client takeover.
    pub hydration_markers: bool,
}

impl ServerBackend {
    pub fn new() -> Self {
        Self { hydration_markers: true }
    }

    /// Disable hydration markers (pure static HTML, no client-side takeover).
    pub fn static_only() -> Self {
        Self { hydration_markers: false }
    }

    /// Wrap rendered HTML in a full HTML document skeleton.
    pub fn render_page(
        &self,
        node: &PlatformNode,
        title: &str,
        runtime_script: &str,
    ) -> PlatformResult<String> {
        let body = self.render_to_string(node)?;
        Ok(format!(
            r#"<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>{title}</title>
</head>
<body>
    <div id="app" data-el-ssr="true">
        {body}
    </div>
    <script type="module" src="{runtime_script}"></script>
</body>
</html>"#
        ))
    }
}

impl Default for ServerBackend {
    fn default() -> Self {
        Self::new()
    }
}

impl PlatformBackend for ServerBackend {
    fn name(&self) -> &'static str {
        "server"
    }

    fn create_element(&self, tag: &str) -> PlatformResult<PlatformNode> {
        Ok(PlatformNode::element(tag))
    }

    fn create_text(&self, content: &str) -> PlatformResult<PlatformNode> {
        Ok(PlatformNode::text(content))
    }

    fn set_attribute(
        &self,
        node: &mut PlatformNode,
        name: &str,
        value: &str,
    ) -> PlatformResult<()> {
        node.attributes.retain(|a| a.name != name);
        node.attributes.push(crate::Attribute::new(name, value));
        Ok(())
    }

    fn remove_attribute(&self, node: &mut PlatformNode, name: &str) -> PlatformResult<()> {
        node.attributes.retain(|a| a.name != name);
        Ok(())
    }

    fn append_child(
        &self,
        parent: &mut PlatformNode,
        child: PlatformNode,
    ) -> PlatformResult<()> {
        parent.children.push(child);
        Ok(())
    }

    fn remove_child(&self, parent: &mut PlatformNode, child_index: usize) -> PlatformResult<()> {
        if child_index >= parent.children.len() {
            return Err(PlatformError::Render(format!(
                "server: child index {} out of bounds",
                child_index
            )));
        }
        parent.children.remove(child_index);
        Ok(())
    }

    fn replace_child(
        &self,
        parent: &mut PlatformNode,
        index: usize,
        new_child: PlatformNode,
    ) -> PlatformResult<()> {
        if index >= parent.children.len() {
            return Err(PlatformError::Render(format!(
                "server: replace_child index {} out of bounds",
                index
            )));
        }
        parent.children[index] = new_child;
        Ok(())
    }

    fn bind_event(
        &self,
        node: &mut PlatformNode,
        event: &str,
        _handler: EventHandler,
    ) -> PlatformResult<()> {
        // On the server, event handlers are emitted as data attributes.
        // The client-side hydration pass picks them up and binds real listeners.
        if self.hydration_markers {
            node.attributes
                .push(crate::Attribute::new(format!("data-el-{}", event), "hydrate"));
        }
        Ok(())
    }

    fn render_to_string(&self, node: &PlatformNode) -> PlatformResult<String> {
        Ok(node.to_html())
    }

    fn mount(&self, _root: PlatformNode, _container_id: &str) -> PlatformResult<()> {
        // Server has no mount concept — rendering is one-shot.
        Ok(())
    }

    fn patch(&self, _old: &PlatformNode, _new: &PlatformNode) -> PlatformResult<()> {
        // Server rendering is stateless — no patch needed.
        Ok(())
    }

    fn supports_ssr(&self) -> bool {
        true
    }
}
