///|
pub(all) struct RespoIndexKey(String) derive(Eq)

///|
pub fn RespoIndexKey::to_cirru(self : RespoIndexKey) -> Cirru {
  Leaf(self.0)
}

///|
pub impl Show for RespoIndexKey with output(self, logger) {
  logger.write_string(self.0)
}

///|
/// internal abstraction for an element
pub(all) struct RespoElement[T, G] {
  /// tagName
  name : String
  attrs : Map[String, String]
  event : Map[
    RespoEventType,
    (RespoEvent, DispatchFn[T]) -> Unit raise RespoCommonError,
  ]
  /// inlines styles, partially typed.
  /// there's also a macro called `static_styles` for inserting CSS rules
  style : RespoStyle
  /// each child as a key like a string, by default generated from index,
  /// they are used in diffing, so it's better to be distinct, although not required to be.
  children : Array[(RespoIndexKey, RespoNode[T, G])]
}

///|
pub fn[T, G] RespoElement::to_node(
  self : RespoElement[T, G],
) -> RespoNode[T, G] {
  Element(self)
}

///|
pub impl[T, G] Eq for RespoElement[T, G] with equal(
  self : RespoElement[T, G],
  other : RespoElement[T, G],
) -> Bool {
  if self.name != other.name {
    return false
  }
  if self.attrs != other.attrs {
    return false
  }
  if self.event.length() != other.event.length() {
    return false
  }

  // do not compare the function itself
  for pair in self.event {
    let (key, _value) = pair
    if !other.event.contains(key) {
      return false
    }
  }
  if !(self.style == other.style) {
    return false
  }
  if !(self.children == other.children) {
    return false
  }
  true
}

///|
pub impl[T, G] Show for RespoElement[T, G] with output(self, logger) {
  let ret = "(RespoElement \{self.name})"
  logger.write_string(ret)
}

///|
pub fn[T, G] RespoElement::set_attribute(
  self : RespoElement[T, G],
  key : String,
  value : String,
) -> Unit {
  self.attrs.set(key, value)
}

// TODO implement methods for RespoElement

///|
/// create attributes for an element
pub fn respo_attrs(
  id? : String,
  class_name? : String,
  // ~style? : RespoStyle,
  hidden? : Bool,
  disabled? : Bool,
  value? : String,
  src? : String,
  href? : String,
  alt? : String,
  title? : String,
  placeholder? : String,
  /// actually type
  type_? : String,
  name? : String,
  checked? : Bool,
  selected? : Bool,
  read_only? : Bool,
  max_length? : Int,
  min_length? : Int,
  tab_index? : Int,
  content_editable? : Bool,
  width? : String,
  height? : String,
  inner_text? : String,
  innerHTML? : String,
  /// draggable attribute for drag-and-drop
  draggable? : Bool,
  /// data attributes as a Map, keys will be prefixed with "data-"
  data? : Map[String, String],
) -> Map[String, String] {
  let result = {}
  if id is Some(value) {
    result.set("id", value)
  }
  if class_name is Some(value) {
    result.set("class", value)
  }
  // style properties at top level
  // match style {
  //   Some(value) => result.set("style", value.to_string())
  //   None => ()
  // }
  if hidden is Some(value) {
    result.set("hidden", value.to_string())
  }
  if disabled is Some(value) {
    result.set("disabled", value.to_string())
  }
  if value is Some(value) {
    result.set("value", value)
  }
  if src is Some(value) {
    result.set("src", value)
  }
  if href is Some(value) {
    result.set("href", value)
  }
  if alt is Some(value) {
    result.set("alt", value)
  }
  if title is Some(value) {
    result.set("title", value)
  }
  if placeholder is Some(value) {
    result.set("placeholder", value)
  }
  if type_ is Some(value) {
    result.set("type", value)
  }
  if name is Some(value) {
    result.set("name", value)
  }
  if checked is Some(value) {
    result.set("checked", value.to_string())
  }
  if selected is Some(value) {
    result.set("selected", value.to_string())
  }
  if read_only is Some(value) {
    result.set("readOnly", value.to_string())
  }
  if max_length is Some(value) {
    result.set("maxLength", value.to_string())
  }
  if min_length is Some(value) {
    result.set("minLength", value.to_string())
  }
  if tab_index is Some(value) {
    result.set("tabIndex", value.to_string())
  }
  if content_editable is Some(value) {
    result.set("contentEditable", value.to_string())
  }
  if width is Some(value) {
    result.set("width", value)
  }
  if height is Some(value) {
    result.set("height", value)
  }
  if inner_text is Some(value) {
    result.set("innerText", value)
  }
  if innerHTML is Some(value) {
    result.set("innerHTML", value)
  }
  if draggable is Some(value) {
    result.set("draggable", value.to_string())
  }
  // data attributes are prefixed with "data-"
  if data is Some(map) {
    for key, value in map {
      result.set("data-" + key, value)
    }
  }
  result
}

///|
pub fn[T, G] RespoElement::to_cirru(self : RespoElement[T, G]) -> Cirru {
  let attrs = self.attrs
  // let event = self.event
  // let style = self.style
  let children = self.children
  let attrs_in_list : Array[Cirru] = []
  for pair in attrs {
    let (key, value) = pair
    attrs_in_list.push(List([Leaf(key), Leaf(value)]))
  }
  let children_mark : Array[Cirru] = []
  for pair in children {
    let (key, child) = pair
    children_mark.push(List([key.to_cirru(), child.to_cirru()]))
  }
  List([Leaf(self.name), List(attrs_in_list), List(children_mark)])
}