///|
/// A name and a value together, templ's `templ.KeyValue`. A spread attribute and
/// a CSS class both pair a name with a flag that says whether it applies, which
/// is what templ's `templ.KV` builds.
pub(all) struct KV[K, V] {
  key : K
  value : V
}

///|
pub fn[K, V] kv(key : K, value : V) -> KV[K, V] {
  { key, value, }
}

///|
/// The value of one attribute in a `{ attrs... }` spread.
///
/// templ carries these in a `map[string]any` and switches on the dynamic type
/// while rendering. MoonBit has no such type, so the cases templ's switch held
/// are the variants here.
pub(all) enum AttributeValue {
  /// `name="value"`, with the value escaped.
  Value(String)
  /// The bare name when `true`, nothing at all when `false`. templ's bool
  /// pointer and its `func() bool` both land here: either one only decides
  /// whether the name stands alone.
  Flag(Bool)
  /// `name="value"` when the flag holds, nothing at all when it does not —
  /// templ's `templ.KV("lorem", true)`, which renders `text="lorem"` when the
  /// attribute is named `text`.
  Conditional(KV[String, Bool])
}

///|
/// A set of spread attributes, which is what `{ attrs... }` hands to
/// `render_attributes`.
///
/// templ models this as an interface over either a map or a slice. MoonBit does
/// not let a package implement a trait for a type it does not own, and `Map`
/// belongs to the core library, so those two shapes meet in one struct built by
/// one of two constructors rather than in two implementations of `Attributer`.
pub(all) struct Attributes {
  priv items : Array[KV[String, AttributeValue]]
}

///|
/// The attributes of a map, rendered in name order — templ's `templ.Attributes`.
/// Which order the map was written in does not reach the output: a map has none
/// of its own, and templ sorts one by key for the same reason.
///
/// templ sorts at render time; here the sort happens once, at construction,
/// because nothing can reach `items` afterwards.
pub fn Attributes::from_map(
  attributes : Map[String, AttributeValue],
) -> Attributes {
  let items : Array[KV[String, AttributeValue]] = []
  for name, value in attributes {
    items.push({ key: name, value, })
  }
  // templ sorts with Go's `<`, which compares byte by byte. MoonBit's own
  // `Compare for String` compares length first, so sorting by it would put `zz`
  // before `aaa`.
  items.sort_by(fn(a, b) { a.key[:].lexical_compare(b.key[:]) })
  { items, }
}

///|
/// The attributes in the order they were given — templ's
/// `templ.OrderedAttributes`.
pub fn Attributes::ordered(
  items : Array[KV[String, AttributeValue]],
) -> Attributes {
  { items, }
}

///|
/// What `{ attrs... }` can spread. The generated call takes anything that can
/// name its items, which is how a caller with attributes of its own can spread
/// them: `Map` is the core library's type, so a map is wrapped in `Attributes`
/// rather than made an `Attributer` itself.
pub(open) trait Attributer {
  fn items(Self) -> Array[KV[String, AttributeValue]]
}

///|
pub impl Attributer for Attributes with fn items(self) {
  self.items
}

///|
pub extend Attributes with Attributer::{items}

///|
/// `{ attrs... }` writes the whole of each attribute, separator included, so the
/// literal run of the start tag breaks around it. Names and values are escaped
/// here, the way templ's `RenderAttributes` escapes them.
pub fn[A : Attributer] render_attributes(w : &Logger, attributes : A) -> Unit {
  for item in attributes.items() {
    render_attribute(w, item.key, item.value)
  }
}

///|
fn render_attribute(w : &Logger, name : String, value : AttributeValue) -> Unit {
  match value {
    Value(v) => w.write_string(" " + escape(name) + "=\"" + escape(v) + "\"")
    Flag(true) => w.write_string(" " + escape(name))
    Flag(false) => ()
    Conditional(pair) =>
      if pair.value {
        w.write_string(" " + escape(name) + "=\"" + escape(pair.key) + "\"")
      }
  }
}