// DomNode - Browser-specific DOM node types
//
// DomNode is designed for client-side rendering with full interactivity:
// - Event handlers (click, input, etc.)
// - Reactive updates (signals, effects)
// - DOM manipulation
//
// Unlike ServerDomNode, DomNode cannot contain ServerDomNode children.
// This enforces the Server Component / Client Component boundary.
//

///|
/// DomElement wraps a DOM element (internal @js_dom.Element)
pub struct DomElement {
  priv inner : @js_dom.Element
}

///|
/// Create DomElement from @js_dom.Element
pub fn DomElement::from_dom(elem : @js_dom.Element) -> DomElement {
  { inner: elem }
}

///|
/// Get the inner @js_dom.Element
pub fn DomElement::to_dom(self : DomElement) -> @js_dom.Element {
  self.inner
}

///|
/// Convert DomElement to DomNode
pub fn DomElement::to_node(self : DomElement) -> DomNode {
  El(self)
}

///|
/// DomNode represents any DOM node for client-side rendering
///
/// Variants:
/// - `El(DomElement)`: Element managed by Luna (reconciled, hydrated)
/// - `Txt(@js_dom.Text)`: Text node managed by Luna (reconciled)
/// - `Raw(@js_dom.Node)`: External node included in reconciliation
///   Use for external DOM that may change (e.g., third-party widgets)
/// - `Static(@js_dom.Node)`: Pre-rendered node skipped during reconciliation/hydration
///   Use for content that never changes (e.g., SSR output, static SVG)
pub enum DomNode {
  /// Element managed by Luna
  El(DomElement)
  /// Text node managed by Luna
  Txt(@js_dom.Text)
  /// External node - included in reconciliation
  Raw(@js_dom.Node)
  /// Static node - skipped during reconciliation/hydration
  /// Use for pre-rendered content that never changes
  Static(@js_dom.Node)
}

///|
/// Get the inner @js_dom.Node
pub fn DomNode::to_dom(self : DomNode) -> @js_dom.Node {
  match self {
    El(elem) => elem.inner.as_node()
    Txt(text) => text.as_node()
    Raw(node) => node
    Static(node) => node
  }
}

///|
/// ToDomNode trait - types that can be converted to DomNode
pub trait ToDomNode {
  to_dom_node(Self) -> DomNode
}

///|
/// DomElement -> DomNode
pub impl ToDomNode for DomElement with to_dom_node(self) {
  El(self)
}

///|
/// String -> DomNode (creates text node)
pub impl ToDomNode for String with to_dom_node(self) {
  let doc = @js_dom.document()
  Txt(doc.createTextNode(self))
}

///|
/// @js_dom.Node -> DomNode (raw node)
pub impl ToDomNode for @js_dom.Node with to_dom_node(self) {
  Raw(self)
}

///|
/// @js_dom.Element -> DomNode
pub impl ToDomNode for @js_dom.Element with to_dom_node(self) {
  El(DomElement::from_dom(self))
}

///|
/// @js_dom.Text -> DomNode
pub impl ToDomNode for @js_dom.Text with to_dom_node(self) {
  Txt(self)
}

///|
/// DomNode -> DomNode (identity)
pub impl ToDomNode for DomNode with to_dom_node(self) {
  self
}

///|
/// Convert any ToDomNode value to DomNode
///
/// Supported types:
/// - `@js_dom.Node` -> Raw node (included in reconciliation)
/// - `@js_dom.Element` -> Element node (wrapped as El)
/// - `@js_dom.Text` -> Text node
/// - `String` -> Text node (creates new text node)
/// - `DomNode` -> Identity (returns as-is)
///
/// Use this function to integrate external DOM nodes (e.g., SVG created
/// with `createElementNS`) into Luna's DOM tree.
///
/// Example - integrating SVG created with createElementNS:
/// ```moonbit nocheck
/// // Create SVG with namespace
/// let svg = create_element_ns(svg_ns, "svg", [
///   ("width", Static("200")),
///   ("height", Static("200")),
/// ], [
///   create_element_ns(svg_ns, "circle", [
///     ("cx", Static("100")),
///     ("cy", Static("100")),
///     ("r", Static("50")),
///     ("fill", Static("blue")),
///   ], [])
/// ])
///
/// // Use in a div
/// div(children=[svg])
/// ```
///
/// Example - integrating raw @js_dom.Node:
/// ```moonbit nocheck
/// // Get external node from JS
/// let external_node : @js_dom.Node = ...
/// // Wrap and use in Luna tree
/// div(children=[dom_node(external_node)])
/// ```
pub fn[T : ToDomNode] dom_node(value : T) -> DomNode {
  ToDomNode::to_dom_node(value)
}

///|
/// Collect children with ToDomNode conversion
pub fn[T : ToDomNode] dom_children(items : Array[T]) -> Array[DomNode] {
  items.map(ToDomNode::to_dom_node)
}

///|
/// Create a Static DomNode from a @js_dom.Node
/// Static nodes are skipped during reconciliation and hydration
pub fn static_node(node : @js_dom.Node) -> DomNode {
  Static(node)
}