///|
/// Input utility helpers (edge detection per tick).
///
/// Ebiten refs:
/// - inpututil/inpututil.go
/// - internal/inputstate/inputstate.go

///|
struct KeyDurationEntry {
  key : Int
  duration : Int
} derive(Debug)

///|
pub impl Show for KeyDurationEntry with fn output(self, logger) {
  logger.write_object(Repr(self))
}

///|
struct MouseButtonDurationEntry {
  button : Int
  duration : Int
} derive(Debug)

///|
pub impl Show for MouseButtonDurationEntry with fn output(self, logger) {
  logger.write_object(Repr(self))
}

///|
pub struct KeyInputState {
  mut prev_pressed_keys : Array[Int]
  mut pressed_keys : Array[Int]
  mut just_pressed_keys : Array[Int]
  mut just_released_keys : Array[Int]
  mut durations : Array[KeyDurationEntry]
}

///|
pub struct MouseButtonInputState {
  mut prev_pressed_buttons : Array[Int]
  mut pressed_buttons : Array[Int]
  mut just_pressed_buttons : Array[Int]
  mut just_released_buttons : Array[Int]
  mut durations : Array[MouseButtonDurationEntry]
}

///|
fn contains_key(keys : Array[Int], key : Int) -> Bool {
  let mut found = false
  for current in keys {
    if current == key {
      found = true
    }
  }
  found
}

///|
fn normalize_pressed_keys(keys : Array[Int]) -> Array[Int] {
  let out : Array[Int] = []
  for key in keys {
    if !contains_key(out, key) {
      out.push(key)
    }
  }
  out
}

///|
fn duration_for_key(entries : Array[KeyDurationEntry], key : Int) -> Int {
  let mut out = 0
  for entry in entries {
    if entry.key == key {
      out = entry.duration
    }
  }
  out
}

///|
pub fn new_key_input_state() -> KeyInputState {
  {
    prev_pressed_keys: [],
    pressed_keys: [],
    just_pressed_keys: [],
    just_released_keys: [],
    durations: [],
  }
}

///|
fn duration_for_mouse_button(
  entries : Array[MouseButtonDurationEntry],
  button : Int,
) -> Int {
  let mut out = 0
  for entry in entries {
    if entry.button == button {
      out = entry.duration
    }
  }
  out
}

///|
pub fn new_mouse_input_state() -> MouseButtonInputState {
  {
    prev_pressed_buttons: [],
    pressed_buttons: [],
    just_pressed_buttons: [],
    just_released_buttons: [],
    durations: [],
  }
}

///|
pub fn update_key_input_state(
  state : KeyInputState,
  snapshot : @core.InputSnapshot,
) -> Unit {
  let prev_pressed = state.pressed_keys
  let next_pressed = normalize_pressed_keys(snapshot.pressed_keys)
  let just_pressed : Array[Int] = []
  for key in next_pressed {
    if !contains_key(prev_pressed, key) {
      just_pressed.push(key)
    }
  }
  let just_released : Array[Int] = []
  for key in prev_pressed {
    if !contains_key(next_pressed, key) {
      just_released.push(key)
    }
  }
  let next_durations : Array[KeyDurationEntry] = []
  for key in next_pressed {
    let duration = if contains_key(prev_pressed, key) {
      duration_for_key(state.durations, key) + 1
    } else {
      1
    }
    next_durations.push({ key, duration, })
  }
  state.prev_pressed_keys = prev_pressed
  state.pressed_keys = next_pressed
  state.just_pressed_keys = just_pressed
  state.just_released_keys = just_released
  state.durations = next_durations
}

///|
pub fn update_mouse_input_state(
  state : MouseButtonInputState,
  snapshot : @core.InputSnapshot,
) -> Unit {
  let prev_pressed = state.pressed_buttons
  let next_pressed = normalize_pressed_keys(snapshot.pressed_mouse_buttons)
  let just_pressed : Array[Int] = []
  for button in next_pressed {
    if !contains_key(prev_pressed, button) {
      just_pressed.push(button)
    }
  }
  let just_released : Array[Int] = []
  for button in prev_pressed {
    if !contains_key(next_pressed, button) {
      just_released.push(button)
    }
  }
  let next_durations : Array[MouseButtonDurationEntry] = []
  for button in next_pressed {
    let duration = if contains_key(prev_pressed, button) {
      duration_for_mouse_button(state.durations, button) + 1
    } else {
      1
    }
    next_durations.push({ button, duration, })
  }
  state.prev_pressed_buttons = prev_pressed
  state.pressed_buttons = next_pressed
  state.just_pressed_buttons = just_pressed
  state.just_released_buttons = just_released
  state.durations = next_durations
}

///|
pub fn is_key_pressed(state : KeyInputState, key : Int) -> Bool {
  contains_key(state.pressed_keys, key)
}

///|
pub fn is_key_just_pressed(state : KeyInputState, key : Int) -> Bool {
  contains_key(state.just_pressed_keys, key)
}

///|
pub fn is_key_just_released(state : KeyInputState, key : Int) -> Bool {
  contains_key(state.just_released_keys, key)
}

///|
pub fn key_press_duration(state : KeyInputState, key : Int) -> Int {
  duration_for_key(state.durations, key)
}

///|
pub fn is_mouse_button_pressed(
  state : MouseButtonInputState,
  button : Int,
) -> Bool {
  contains_key(state.pressed_buttons, button)
}

///|
pub fn is_mouse_button_just_pressed(
  state : MouseButtonInputState,
  button : Int,
) -> Bool {
  contains_key(state.just_pressed_buttons, button)
}

///|
pub fn is_mouse_button_just_released(
  state : MouseButtonInputState,
  button : Int,
) -> Bool {
  contains_key(state.just_released_buttons, button)
}

///|
pub fn mouse_button_press_duration(
  state : MouseButtonInputState,
  button : Int,
) -> Int {
  duration_for_mouse_button(state.durations, button)
}

///|
pub fn append_pressed_keys(
  state : KeyInputState,
  dst : Array[Int],
) -> Array[Int] {
  let out = dst
  for key in state.pressed_keys {
    out.push(key)
  }
  out
}

///|
pub fn append_just_pressed_keys(
  state : KeyInputState,
  dst : Array[Int],
) -> Array[Int] {
  let out = dst
  for key in state.just_pressed_keys {
    out.push(key)
  }
  out
}

///|
pub fn append_just_released_keys(
  state : KeyInputState,
  dst : Array[Int],
) -> Array[Int] {
  let out = dst
  for key in state.just_released_keys {
    out.push(key)
  }
  out
}

///|
pub fn append_pressed_mouse_buttons(
  state : MouseButtonInputState,
  dst : Array[Int],
) -> Array[Int] {
  let out = dst
  for button in state.pressed_buttons {
    out.push(button)
  }
  out
}

///|
pub fn append_just_pressed_mouse_buttons(
  state : MouseButtonInputState,
  dst : Array[Int],
) -> Array[Int] {
  let out = dst
  for button in state.just_pressed_buttons {
    out.push(button)
  }
  out
}

///|
pub fn append_just_released_mouse_buttons(
  state : MouseButtonInputState,
  dst : Array[Int],
) -> Array[Int] {
  let out = dst
  for button in state.just_released_buttons {
    out.push(button)
  }
  out
}

///|
pub extend KeyDurationEntry with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend KeyDurationEntry with Show::{to_string, output}

///|
pub extend MouseButtonDurationEntry with @moonbitlang/core/debug.Debug::{
  to_repr,
}

///|
pub extend MouseButtonDurationEntry with Show::{to_string, output}