///|
pub(all) struct TapGestureResult {
  next_state : ButtonState
  activated : Bool
} derive(Eq, Debug, ToJson)

///|
pub(all) struct HitTestEntry {
  element_id : ElementId
  local_position : Point
  frame : Rect
} derive(Eq, Debug, ToJson)

///|
pub(all) struct HitTestResult {
  path : Array[HitTestEntry]
} derive(Eq, Debug, ToJson)

///|
pub(all) enum GestureKind {
  Tap
  Drag
  LongPress
  PanZoom
  Wheel
  Hover
} derive(Eq, Debug, ToJson)

///|
pub(all) enum GestureArenaDisposition {
  Pending
  Accepted
  Rejected
} derive(Eq, Debug, ToJson)

///|
pub(all) struct GestureArenaMember {
  element_id : ElementId
  kind : GestureKind
  priority : Int
  mut disposition : GestureArenaDisposition
} derive(Eq, Debug, ToJson)

///|
pub(all) struct GestureArenaResult {
  winner : GestureArenaMember?
  members : Array[GestureArenaMember]
} derive(Eq, Debug, ToJson)

///|
pub(all) enum CommandIntent {
  Activate
  Submit
  Cancel
  Copy
  Cut
  Paste
  Undo
  Redo
  SelectAll
  OpenContextMenu
} derive(Eq, Debug, ToJson)

///|
pub(all) struct ActionCommand {
  intent : CommandIntent
  shortcut : KeyboardShortcut?
  label : String
  enabled : Bool
} derive(Eq, Debug, ToJson)

///|
pub(all) struct CommandBinding {
  command : ActionCommand
  handle : () -> Unit
}

///|
pub(all) struct ActionCommandMap {
  bindings : Array[CommandBinding]
}

///|
pub fn recognize_button_tap(
  current : ButtonState,
  inside : Bool,
  phase : PointerPhase,
) -> TapGestureResult {
  let next_state = match phase {
    Move =>
      if current == Pressed && inside {
        Pressed
      } else if inside {
        Hovered
      } else {
        Normal
      }
    Down => if inside { Pressed } else { Normal }
    Up => if inside { Hovered } else { Normal }
    Cancel => Normal
  }
  { next_state, activated: phase == Up && inside && current == Pressed }
}

///|
pub fn GestureArenaMember::new(
  element_id~ : ElementId,
  kind~ : GestureKind,
  priority? : Int = 0,
) -> GestureArenaMember {
  { element_id, kind, priority, disposition: GestureArenaDisposition::Pending }
}

///|
pub fn GestureArenaMember::accept(self : GestureArenaMember) -> Unit {
  self.disposition = GestureArenaDisposition::Accepted
}

///|
pub fn GestureArenaMember::reject(self : GestureArenaMember) -> Unit {
  self.disposition = GestureArenaDisposition::Rejected
}

///|
pub fn GestureArenaMember::is_pending(self : GestureArenaMember) -> Bool {
  self.disposition == GestureArenaDisposition::Pending
}

///|
pub fn resolve_gesture_arena(
  members : Array[GestureArenaMember],
) -> GestureArenaResult {
  let mut winner : GestureArenaMember? = None
  for candidate in members {
    if candidate.disposition != GestureArenaDisposition::Rejected {
      match winner {
        Some(current) =>
          if candidate.priority > current.priority {
            winner = Some(candidate)
          }
        None => winner = Some(candidate)
      }
    }
  }
  match winner {
    Some(winner_member) =>
      for candidate in members {
        if candidate.element_id == winner_member.element_id &&
          candidate.kind == winner_member.kind {
          candidate.accept()
        } else {
          candidate.reject()
        }
      }
    None => ()
  }
  { winner, members }
}

///|
pub fn ActionCommand::new(
  intent~ : CommandIntent,
  label? : String = "",
  shortcut? : KeyboardShortcut? = None,
  enabled? : Bool = true,
) -> ActionCommand {
  { intent, shortcut, label, enabled }
}

///|
pub fn ActionCommand::matches(
  self : ActionCommand,
  event : KeyboardEvent,
) -> Bool {
  if !self.enabled {
    false
  } else {
    match self.shortcut {
      Some(shortcut) => shortcut.matches(event)
      None => false
    }
  }
}

///|
pub fn CommandBinding::new(
  command~ : ActionCommand,
  handle~ : () -> Unit,
) -> CommandBinding {
  { command, handle }
}

///|
pub fn ActionCommandMap::new(
  bindings? : Array[CommandBinding] = [],
) -> ActionCommandMap {
  { bindings, }
}

///|
pub fn ActionCommandMap::commands(
  self : ActionCommandMap,
) -> Array[ActionCommand] {
  self.bindings.map(binding => binding.command)
}

///|
pub fn ActionCommandMap::find_shortcut(
  self : ActionCommandMap,
  event : KeyboardEvent,
) -> ActionCommand? {
  for binding in self.bindings {
    if binding.command.matches(event) {
      return Some(binding.command)
    }
  }
  None
}

///|
pub fn ActionCommandMap::dispatch_shortcut(
  self : ActionCommandMap,
  event : KeyboardEvent,
) -> CommandIntent? {
  for binding in self.bindings {
    if binding.command.matches(event) {
      (binding.handle)()
      return Some(binding.command.intent)
    }
  }
  None
}

///|
pub fn ActionCommandMap::dispatch_intent(
  self : ActionCommandMap,
  intent : CommandIntent,
) -> Bool {
  for binding in self.bindings {
    if binding.command.enabled && binding.command.intent == intent {
      (binding.handle)()
      return true
    }
  }
  false
}

///|
pub fn default_text_commands() -> Array[ActionCommand] {
  [
    ActionCommand::new(
      intent=CommandIntent::Copy,
      label="Copy",
      shortcut=Some(
        KeyboardShortcut::new(key="c", modifiers=KeyModifiers::new(meta=true)),
      ),
    ),
    ActionCommand::new(
      intent=CommandIntent::Cut,
      label="Cut",
      shortcut=Some(
        KeyboardShortcut::new(key="x", modifiers=KeyModifiers::new(meta=true)),
      ),
    ),
    ActionCommand::new(
      intent=CommandIntent::Paste,
      label="Paste",
      shortcut=Some(
        KeyboardShortcut::new(key="v", modifiers=KeyModifiers::new(meta=true)),
      ),
    ),
    ActionCommand::new(
      intent=CommandIntent::Undo,
      label="Undo",
      shortcut=Some(
        KeyboardShortcut::new(key="z", modifiers=KeyModifiers::new(meta=true)),
      ),
    ),
    ActionCommand::new(
      intent=CommandIntent::Redo,
      label="Redo",
      shortcut=Some(
        KeyboardShortcut::new(
          key="z",
          modifiers=KeyModifiers::new(shift=true, meta=true),
        ),
      ),
    ),
    ActionCommand::new(
      intent=CommandIntent::SelectAll,
      label="Select All",
      shortcut=Some(
        KeyboardShortcut::new(key="a", modifiers=KeyModifiers::new(meta=true)),
      ),
    ),
  ]
}