// =============================================================================
// html.mbt — standalone Html/Attr/Event types + render to string
//
// Modelled after rabbita's html API and luna's render output.
// No external dependencies. Works on all MoonBit targets (JS, wasm, native).
//
// Overview
// --------
// - `Html` is the node tree (elements, text, fragments, raw/unsafe html).
// - `Attr` is anything that can sit inside a tag's opening ``,
//   including both static attributes/styles and event handlers.
// - `Event` enumerates the DOM events an `Attr::On(_)` can carry; each
//   variant holds the JS to run as a plain string (no closures — this is a
//   pure string-templating DSL, not a virtual DOM).
// - Element builder functions (`div`, `p`, `a`, ...) are thin, labeled-arg
//   wrappers around `Node(tag, attrs, children)` that mirror common HTML
//   attributes as keyword parameters so call sites read like HTML.
// - `render` walks an `Html` tree and produces the final HTML string,
//   escaping text content and static attribute values, and skipping
//   nothing silently — event handlers ARE rendered as inline `on*` attrs.
//
// =============================================================================

///|
/// The `type` attribute value for `` elements.
/// Passed to `input(input_type~)`; rendered as the matching HTML type string
/// (e.g. `Email` -> `type="email"`).
pub(all) enum InputType {
  Text
  Password
  Email
  Number
  Checkbox
  Radio
  File
  Hidden
  Submit
  Reset
  Button
  Range
  Date
  Color
  Search
  Tel
  Url
}

///|
/// The `target` attribute value for `` (and other linking elements).
/// `Self_` is the default browsing context; the others map 1:1 to the
/// standard `_blank` / `_parent` / `_top` keywords.
pub(all) enum Target {
  Self_
  Blank
  Parent
  Top
}

///|
/// All DOM events that can be attached to an element via `Attr::On`.
/// Each variant carries the JS to run (as a raw string, e.g.
/// `"console.log('hi')"`) which is rendered verbatim as an inline
/// `on*="..."` attribute — it is NOT escaped, so callers are responsible
/// for producing valid/safe JS.
pub(all) enum Event {
  /// mouse / pointer
  Click(String)
  DblClick(String)
  MouseEnter(String)
  MouseLeave(String)
  /// form
  Change(String)
  Input(String)
  Submit(String)
  Focus(String)
  Blur(String)
  /// keyboard
  KeyDown(String)
  KeyUp(String)
  KeyPress(String)
}

///|
/// HTML attribute — either a static property/style or a event handler.
/// `Attr` values are order-independent; `render_attr` decides how each one
/// is serialized inside a tag's opening bracket.
pub(all) enum Attr {
  // ── static string attributes ─────────────────────────────────────────
  Id(String)
  Class(String)
  Href(String)
  Src(String)
  Alt(String)
  Type_(String) // "type" is a keyword so we use Type_
  Value(String)
  Placeholder(String)
  Name(String)
  For_(String) // "for" is a keyword
  Action(String)
  Method(String)
  Charset(String)
  Rel(String)
  Lang(String)
  Disabled(Bool)
  Checked(Bool)
  Required(Bool)
  Readonly(Bool)
  AutoFocus(Bool)
  Multiple(Bool)
  Target_(Target)
  Defer(Bool)
  Async(Bool)

  // ── extra static attributes (forms, media, tables, misc) ─────────────
  Rows(Int)
  Cols(Int)
  Controls(Bool)
  Autoplay(Bool)
  Loop_(Bool) // "loop" reads oddly as a bare identifier, so we use Loop_
  Muted(Bool)
  Poster(String)
  Min(String)
  Max(String)
  Step(String)
  Selected(Bool)
  Label_(String) // clashes with the `label` element builder otherwise
  DateTime(String)
  Colspan(Int)
  Rowspan(Int)
  Width(Int)
  Height(Int)
  Title_(String) // "title" clashes with the `title` element builder
  Cite(String)
  // ── style ─────────────────────────────────────────────────────────────
  // inline styles as (property, value) pairs, e.g. [("color","red")]
  Style(Array[(String, String)])
  // Shortcuts to prefixxed attributes
  Aria(String, String)
  Data(String, String)
  // arbitrary attribute for anything not listed above
  Prop(String, String)
  // ── event handlers ────────────────────────────────────────────────────
  On(Event)
  OnRawEvent(String, String)
}

///|
/// HTML node — the tree that `render` turns into a string.
pub enum Html {
  /// An element: tag name, attributes, children
  Node(String, Array[Attr], Array[Html])
  /// Raw escaped text content
  Text(String)
  /// Multiple nodes with no wrapping element
  Fragment(Array[Html])
  /// Raw, un-escaped HTML/JS dropped verbatim into the output. Use with
  /// care — nothing here is sanitized, so never build this from untrusted
  /// input.
  Unsafe(String)
  /// Renders nothing (useful as a conditional placeholder)
  Nothing
  /// An HTML comment; the data is sanitized by `comment` so the rendered
  /// output is always a valid comment.
  Comment(String)
}

// =============================================================================
// Constructor helpers — mirrors rabbita's public API style
// =============================================================================

///|
/// on_click("console.log(`Hello`)")
pub fn on_click(msg : String) -> Attr {
  On(Event::Click(msg))
}

///|
/// on_dblclick("console.log(`Hello`)")
pub fn on_dblclick(msg : String) -> Attr {
  On(Event::DblClick(msg))
}

///|
/// on_mouseenter("console.log(`Hello`)")
pub fn on_mouseenter(msg : String) -> Attr {
  On(Event::MouseEnter(msg))
}

///|
/// on_mouseleave("console.log(`Hello`)")
pub fn on_mouseleave(msg : String) -> Attr {
  On(Event::MouseLeave(msg))
}

///|
/// on_change("console.log(`Hello`)")
pub fn on_change(msg : String) -> Attr {
  On(Event::Change(msg))
}

///|
/// on_input("console.log(`Hello`)")
pub fn on_input(msg : String) -> Attr {
  On(Event::Input(msg))
}

///|
/// on_submit("console.log(`Hello`)")
pub fn on_submit(msg : String) -> Attr {
  On(Event::Submit(msg))
}

///|
/// on_focus("console.log(`Hello`)")
pub fn on_focus(msg : String) -> Attr {
  On(Event::Focus(msg))
}

///|
/// on_blur("console.log(`Hello`)")
pub fn on_blur(msg : String) -> Attr {
  On(Event::Blur(msg))
}

///|
/// on_keydown("console.log(`Hello`)")
pub fn on_keydown(msg : String) -> Attr {
  On(Event::KeyDown(msg))
}

///|
/// on_keyup("console.log(`Hello`)")
pub fn on_keyup(msg : String) -> Attr {
  On(Event::KeyUp(msg))
}

///|
/// on_keypress("console.log(`Hello`)")
pub fn on_keypress(msg : String) -> Attr {
  On(Event::KeyPress(msg))
}

// =============================================================================
// Element builders — one per common HTML tag
// mirrors rabbita's approach: labeled args for attrs, positional for children
//
// Unless noted otherwise, every "block-style" builder below shares the same
// four optional keyword parameters: `id`, `class`, `style`, and `attrs`
// (for anything not otherwise covered), followed by a positional
// `children : Array[Html]`.
// =============================================================================

///|
/// `
` — generic block-level container; the catch-all building block /// when no more specific element applies. /// /// Example: /// `div(class="card", [text("Hello")])` -> `
Hello
` pub fn div( id? : String = "", class? : String = "", attrs? : Array[Attr] = [], children : Array[Html], ) -> Html { Node("div", build_attrs(id~, class~, attrs~), children) } ///| /// `` — generic inline container, e.g. for styling a run of text. /// /// Example: /// `span(class="highlight", [text("new")])` /// -> `new` pub fn span( id? : String = "", class? : String = "", attrs? : Array[Attr] = [], children : Array[Html], ) -> Html { Node("span", build_attrs(id~, class~, attrs~), children) } ///| /// `

` — a paragraph of text. /// /// Example: /// `p([text("Hello, world.")])` -> `

Hello, world.

` pub fn p( id? : String = "", class? : String = "", attrs? : Array[Attr] = [], children : Array[Html], ) -> Html { Node("p", build_attrs(id~, class~, attrs~), children) } ///| /// `

` — the top-level (most important) section heading. /// /// Example: /// `h1([text("Welcome")])` -> `

Welcome

` pub fn h1( id? : String = "", class? : String = "", attrs? : Array[Attr] = [], children : Array[Html], ) -> Html { Node("h1", build_attrs(id~, class~, attrs~), children) } ///| /// `

` — second-level section heading. /// /// Example: /// `h2([text("About")])` -> `

About

` pub fn h2( id? : String = "", class? : String = "", attrs? : Array[Attr] = [], children : Array[Html], ) -> Html { Node("h2", build_attrs(id~, class~, attrs~), children) } ///| /// `

` — third-level section heading. /// /// Example: /// `h3([text("Details")])` -> `

Details

` pub fn h3( id? : String = "", class? : String = "", attrs? : Array[Attr] = [], children : Array[Html], ) -> Html { Node("h3", build_attrs(id~, class~, attrs~), children) } ///| /// `

` — fourth-level section heading. /// /// Example: /// `h4([text("Notes")])` -> `

Notes

` pub fn h4( id? : String = "", class? : String = "", attrs? : Array[Attr] = [], children : Array[Html], ) -> Html { Node("h4", build_attrs(id~, class~, attrs~), children) } ///| /// `
` — fifth-level section heading. /// /// Example: /// `h5([text("Fine print")])` -> `
Fine print
` pub fn h5( id? : String = "", class? : String = "", attrs? : Array[Attr] = [], children : Array[Html], ) -> Html { Node("h5", build_attrs(id~, class~, attrs~), children) } ///| /// `
` — sixth-level (lowest) section heading. /// /// Example: /// `h6([text("Footnote")])` -> `
Footnote
` pub fn h6( id? : String = "", class? : String = "", attrs? : Array[Attr] = [], children : Array[Html], ) -> Html { Node("h6", build_attrs(id~, class~, attrs~), children) } ///| /// `