///|
/// Accessibility tree extraction for vnode layouts

///|
pub struct A11yRect {
  x : Int
  y : Int
  width : Int
  height : Int
} derive(Debug)

///|
fn[T : Debug] show_debug(value : T, logger : &Logger) -> Unit {
  value.to_repr().output(logger)
}

///|
pub impl Show for A11yRect with fn output(self : A11yRect, logger : &Logger) -> Unit {
  show_debug(self, logger)
}

///|
pub struct A11yNode {
  id : String
  role : @core.Role
  name : String
  rect : A11yRect
  children : Array[A11yNode]
} derive(Debug)

///|
pub impl Show for A11yNode with fn output(self : A11yNode, logger : &Logger) -> Unit {
  show_debug(self, logger)
}

///|
priv struct A11yBuildResult {
  nodes : Array[A11yNode]
  text_for_parent : String
}

///|
fn normalize_text(s : String) -> String {
  s.trim(chars=" \n\t").to_owned()
}

///|
fn merge_text(a : String, b : String) -> String {
  if a == "" {
    b
  } else if b == "" {
    a
  } else {
    a + " " + b
  }
}

///|
fn infer_role(ctx : RenderContext, id : String) -> @core.Role? {
  match ctx.roles.get(id) {
    Some(role) => Some(role)
    None =>
      match ctx.handlers.get(id) {
        Some(_) => Some(@core.Role::Button)
        None => None
      }
  }
}

///|
fn build_a11y(
  layout : @ccore.Layout,
  ctx : RenderContext,
  offset_x : Double,
  offset_y : Double,
) -> A11yBuildResult {
  let abs_x = offset_x + layout.x
  let abs_y = offset_y + layout.y
  let rect = @core.normalize_grid_rect(
    abs_x,
    abs_y,
    layout.width,
    layout.height,
  )
  let x = rect.x
  let y = rect.y
  let w = rect.width
  let h = rect.height
  let own_text = match ctx.texts.get(layout.id) {
    Some(text) => normalize_text(text)
    None => ""
  }
  let mut name_text = own_text
  let mut text_for_parent = own_text
  let child_nodes : Array[A11yNode] = []
  for child in layout.children {
    let result = build_a11y(child, ctx, abs_x, abs_y)
    for n in result.nodes {
      child_nodes.push(n)
    }
    name_text = merge_text(name_text, result.text_for_parent)
    text_for_parent = merge_text(text_for_parent, result.text_for_parent)
  }
  match infer_role(ctx, layout.id) {
    Some(role) => {
      let node : A11yNode = {
        id: layout.id,
        role,
        name: normalize_text(name_text),
        rect: { x, y, width: w, height: h },
        children: child_nodes,
      }
      { nodes: [node], text_for_parent: "" }
    }
    None => { nodes: child_nodes, text_for_parent }
  }
}

///|
pub fn build_a11y_tree(
  layout : @ccore.Layout,
  ctx : RenderContext,
) -> Array[A11yNode] {
  let result = build_a11y(layout, ctx, 0.0, 0.0)
  result.nodes
}

///|
fn dump_a11y_node(node : A11yNode, depth : Int) -> String {
  let indent = "  ".repeat(depth)
  let role = node.role.to_string()
  let name = if node.name == "" { "" } else { " name=\"" + node.name + "\"" }
  let rect = " rect=(" +
    node.rect.x.to_string() +
    "," +
    node.rect.y.to_string() +
    ") " +
    node.rect.width.to_string() +
    "x" +
    node.rect.height.to_string()
  let mut result = indent + role + " id=" + node.id + name + rect + "\n"
  for child in node.children {
    result = result + dump_a11y_node(child, depth + 1)
  }
  result
}

///|
pub fn dump_a11y_tree(nodes : Array[A11yNode]) -> String {
  let mut result = ""
  for node in nodes {
    result = result + dump_a11y_node(node, 0)
  }
  result
}