///|
/// A named key binding with activation keys, display help, and enabled state.
pub(all) struct Binding {
  keys : Array[String]
  help_key : String
  help : String
  enabled : Bool
} derive(Debug, Eq)

///|
/// A named group of bindings, rendered as a section in full help.
pub(all) struct KeyMapGroup {
  title : String
  bindings : Array[Binding]
} derive(Debug, Eq)

///|
/// Ordered groups of key bindings for matching and help rendering.
pub(all) struct KeyMap {
  groups : Array[KeyMapGroup]
} derive(Debug, Eq)

///|
/// A help model for rendering keybinding hints at the bottom of a view.
pub(all) struct HelpModel {
  keymap : KeyMap
  show_full : Bool
  width : Int
} derive(Debug, Eq)

///|
pub fn help_model(
  keymap~ : KeyMap,
  width? : Int = 0,
  show_full? : Bool = false,
) -> HelpModel {
  { keymap, show_full, width }
}

///|
fn key_help_label(key : String) -> String {
  match key {
    "up" => "↑"
    "down" => "↓"
    "left" => "←"
    "right" => "→"
    "escape" => "esc"
    "backspace" => "⌫"
    "delete" => "⌦"
    _ => key
  }
}

///|
fn key_help_label_from_keys(keys : Array[String]) -> String {
  let parts : Array[String] = []
  for key in keys {
    parts.push(key_help_label(key))
  }
  parts.join("/")
}

///|
pub fn binding(
  keys? : Array[String] = [],
  help_key? : String = "",
  help? : String = "",
) -> Binding {
  {
    keys,
    help_key: if help_key == "" {
      key_help_label_from_keys(keys)
    } else {
      help_key
    },
    help,
    enabled: true,
  }
}

///|
pub fn set_keys(binding : Binding, keys : Array[String]) -> Binding {
  { ..binding, keys, }
}

///|
pub fn set_help(binding : Binding, help_key : String, help : String) -> Binding {
  { ..binding, help_key, help }
}

///|
pub fn set_disabled(binding : Binding, disabled : Bool) -> Binding {
  { ..binding, enabled: !disabled }
}

///|
pub fn unbind(binding : Binding) -> Binding {
  { ..binding, keys: [], enabled: false }
}

///|
pub fn keymap(bindings : Array[Binding]) -> KeyMap {
  { groups: [{ title: "", bindings }] }
}

///|
pub fn keymap_group(title : String, bindings : Array[Binding]) -> KeyMapGroup {
  { title, bindings }
}

///|
pub fn keymap_from_groups(groups : Array[KeyMapGroup]) -> KeyMap {
  { groups, }
}

///|
pub fn keymap_bindings(keymap : KeyMap) -> Array[Binding] {
  let bindings : Array[Binding] = []
  for group in keymap.groups {
    for binding in group.bindings {
      bindings.push(binding)
    }
  }
  bindings
}

///|
/// Check if a KeyMsg matches an enabled Binding.
pub fn matches(binding : Binding, key : KeyMsg) -> Bool {
  if !binding.enabled {
    return false
  }
  let key_str = match key {
    Char(c) => c.to_string()
    Text(s) => s
    Special(s) => s
    Modified(mod, k) => mod + "+" + k
  }
  binding.keys.contains(key_str)
}

///|
pub fn quit_binding() -> Binding {
  binding(keys=["q", "ctrl+c"], help_key="q", help="quit")
}

///|
pub fn up_binding() -> Binding {
  binding(keys=["up", "k"], help_key="↑/k", help="up")
}

///|
pub fn down_binding() -> Binding {
  binding(keys=["down", "j"], help_key="↓/j", help="down")
}

///|
pub fn left_binding() -> Binding {
  binding(keys=["left", "h"], help_key="←/h", help="left")
}

///|
pub fn right_binding() -> Binding {
  binding(keys=["right", "l"], help_key="→/l", help="right")
}

///|
pub fn enter_binding() -> Binding {
  binding(keys=["enter"], help_key="enter", help="enter")
}

///|
pub fn escape_binding() -> Binding {
  binding(keys=["escape"], help_key="esc", help="escape")
}

///|
pub fn help_binding() -> Binding {
  binding(keys=["?", "h"], help_key="?", help="help")
}

///|
pub fn toggle_help(model : HelpModel) -> HelpModel {
  { ..model, show_full: !model.show_full }
}

///|
fn help_visible(binding : Binding) -> Bool {
  binding.enabled && (binding.help_key != "" || binding.help != "")
}

///|
fn help_truncate(text : String, width : Int) -> String {
  if width <= 0 || @internal.display_width(text) <= width {
    return text
  }
  let ellipsis = "…"
  let ellipsis_width = @internal.display_width(ellipsis)
  if width <= ellipsis_width {
    return @internal.truncate_width(ellipsis, width)
  }
  @internal.truncate_width(text, width - ellipsis_width) + ellipsis
}

///|
fn help_short_binding(binding : Binding) -> String {
  if binding.help == "" {
    binding.help_key
  } else if binding.help_key == "" {
    binding.help
  } else {
    binding.help_key + " " + binding.help
  }
}

///|
fn help_view_short(model : HelpModel) -> String {
  let parts : Array[String] = []
  for binding in keymap_bindings(model.keymap) {
    if help_visible(binding) {
      parts.push(help_short_binding(binding))
    }
  }
  help_truncate(parts.join(" • "), model.width)
}

///|
fn help_group_key_width(bindings : Array[Binding]) -> Int {
  let mut width = 0
  for binding in bindings {
    if help_visible(binding) {
      width = width.max(@internal.display_width(binding.help_key))
    }
  }
  width
}

///|
fn help_view_group(group : KeyMapGroup, width : Int) -> String {
  let visible : Array[Binding] = []
  for binding in group.bindings {
    if help_visible(binding) {
      visible.push(binding)
    }
  }
  if visible.is_empty() {
    return ""
  }
  let lines : Array[String] = []
  if group.title != "" {
    lines.push(help_truncate(group.title, width))
  }
  let indent = if group.title == "" { "" } else { "  " }
  let key_width = help_group_key_width(visible)
  for binding in visible {
    let line = indent +
      @internal.pad_right_width(binding.help_key, key_width) +
      "  " +
      binding.help
    lines.push(help_truncate(line, width))
  }
  lines.join("\n")
}

///|
fn help_view_full(model : HelpModel) -> String {
  let sections : Array[String] = []
  for group in model.keymap.groups {
    let section = help_view_group(group, model.width)
    if section != "" {
      sections.push(section)
    }
  }
  sections.join("\n\n")
}

///|
pub fn help_view(model : HelpModel) -> String {
  if model.show_full {
    help_view_full(model)
  } else {
    help_view_short(model)
  }
}