import pyray as rl
import time
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.lib.widget import Widget

PASSWORD_MASK_CHAR = "•"
PASSWORD_MASK_DELAY = 1.5  # Seconds to show character before masking


class InputBox(Widget):
  def __init__(self, max_text_size=255, password_mode=False):
    super().__init__()
    self._max_text_size = max_text_size
    self._input_text = ""
    self._cursor_position = 0
    self._password_mode = password_mode
    self._blink_counter = 0
    self._show_cursor = False
    self._last_key_pressed = 0
    self._key_press_time = 0
    self._repeat_delay = 30
    self._repeat_rate = 4
    self._text_offset = 0
    self._visible_width = 0
    self._last_char_time = 0  # Track when last character was added
    self._masked_length = 0  # How many characters are currently masked

  @property
  def text(self):
    return self._input_text

  @text.setter
  def text(self, value):
    self._input_text = value[: self._max_text_size]
    self._cursor_position = len(self._input_text)
    self._update_text_offset()

  def set_password_mode(self, password_mode):
    self._password_mode = password_mode

  def clear(self):
    self._input_text = ''
    self._cursor_position = 0
    self._text_offset = 0

  def set_cursor_position(self, position):
    """Set the cursor position and reset the blink counter."""
    if 0 <= position <= len(self._input_text):
      self._cursor_position = position
      self._blink_counter = 0
      self._show_cursor = True
      self._update_text_offset()

  def _update_text_offset(self):
    """Ensure the cursor is visible by adjusting text offset."""
    if self._visible_width == 0:
      return

    font = gui_app.font()
    display_text = self._get_display_text()
    padding = 10

    if self._cursor_position > 0:
      cursor_x = measure_text_cached(font, display_text[: self._cursor_position], self._font_size).x
    else:
      cursor_x = 0

    visible_width = self._visible_width - (padding * 2)

    # Adjust offset if cursor would be outside visible area
    if cursor_x < self._text_offset:
      self._text_offset = max(0, cursor_x - padding)
    elif cursor_x > self._text_offset + visible_width:
      self._text_offset = cursor_x - visible_width + padding

  def add_char_at_cursor(self, char):
    """Add a character at the current cursor position."""
    if len(self._input_text) < self._max_text_size:
      self._input_text = self._input_text[: self._cursor_position] + char + self._input_text[self._cursor_position:]
      self.set_cursor_position(self._cursor_position + 1)

      if self._password_mode:
        self._last_char_time = time.time()

      return True
    return False

  def delete_char_before_cursor(self):
    """Delete the character before the cursor position (backspace)."""
    if self._cursor_position > 0:
      self._input_text = self._input_text[: self._cursor_position - 1] + self._input_text[self._cursor_position:]
      self.set_cursor_position(self._cursor_position - 1)
      return True
    return False

  def delete_char_at_cursor(self):
    """Delete the character at the cursor position (delete)."""
    if self._cursor_position < len(self._input_text):
      self._input_text = self._input_text[: self._cursor_position] + self._input_text[self._cursor_position + 1:]
      self.set_cursor_position(self._cursor_position)
      return True
    return False

  def _render(self, rect, color=rl.BLACK, border_color=rl.DARKGRAY, text_color=rl.WHITE, font_size=80):
    # Store dimensions for text offset calculations
    self._visible_width = rect.width
    self._font_size = font_size

    # Handle mouse input
    self._handle_mouse_input(rect, font_size)

    # Draw input box
    rl.draw_rectangle_rec(rect, color)

    # Process keyboard input
    self._handle_keyboard_input()

    # Update cursor blink
    self._blink_counter += 1
    if self._blink_counter >= 30:
      self._show_cursor = not self._show_cursor
      self._blink_counter = 0

    # Display text
    font = gui_app.font()
    display_text = self._get_display_text()
    padding = 10

    # Clip text within input box bounds
    buffer = 2
    rl.begin_scissor_mode(int(rect.x + padding - buffer), int(rect.y), int(rect.width - padding * 2 + buffer * 2), int(rect.height))
    rl.draw_text_ex(
      font,
      display_text,
      rl.Vector2(int(rect.x + padding - self._text_offset), int(rect.y + rect.height / 2 - font_size / 2)),
      font_size,
      0,
      text_color,
    )

    # Draw cursor
    if self._show_cursor:
      cursor_x = rect.x + padding
      if len(display_text) > 0 and self._cursor_position > 0:
        cursor_x += measure_text_cached(font, display_text[: self._cursor_position], font_size).x

      # Apply text offset to cursor position
      cursor_x -= self._text_offset

      cursor_height = font_size + 4
      cursor_y = rect.y + rect.height / 2 - cursor_height / 2
      rl.draw_line(int(cursor_x), int(cursor_y), int(cursor_x), int(cursor_y + cursor_height), rl.WHITE)

    rl.end_scissor_mode()

  def _get_display_text(self):
    """Get text to display, applying password masking with delay if needed."""
    if not self._password_mode:
      return self._input_text

    # Show character at last edited position if within delay window
    masked_text = PASSWORD_MASK_CHAR * len(self._input_text)
    recent_edit = time.time() - self._last_char_time < PASSWORD_MASK_DELAY
    if recent_edit and self._input_text:
      last_pos = max(0, self._cursor_position - 1)
      if last_pos < len(self._input_text):
        return masked_text[:last_pos] + self._input_text[last_pos] + masked_text[last_pos + 1:]

    return masked_text

  def _handle_mouse_input(self, rect, font_size):
    """Handle mouse clicks to position cursor."""
    mouse_pos = rl.get_mouse_position()
    if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT) and rl.check_collision_point_rec(mouse_pos, rect):
      # Calculate cursor position from click
      if len(self._input_text) > 0:
        font = gui_app.font()
        display_text = self._get_display_text()

        # Find the closest character position to the click
        relative_x = mouse_pos.x - (rect.x + 10) + self._text_offset
        best_pos = 0
        min_distance = float('inf')

        for i in range(len(self._input_text) + 1):
          char_width = measure_text_cached(font, display_text[:i], font_size).x
          distance = abs(relative_x - char_width)
          if distance < min_distance:
            min_distance = distance
            best_pos = i

        self.set_cursor_position(best_pos)
      else:
        self.set_cursor_position(0)

  def _handle_keyboard_input(self):
    # Handle navigation keys
    key = rl.get_key_pressed()
    if key != 0:
      self._process_key(key)
      if key in (rl.KEY_LEFT, rl.KEY_RIGHT, rl.KEY_BACKSPACE, rl.KEY_DELETE):
        self._last_key_pressed = key
        self._key_press_time = 0

    # Handle repeats for held keys
    elif self._last_key_pressed != 0:
      if rl.is_key_down(self._last_key_pressed):
        self._key_press_time += 1
        if self._key_press_time > self._repeat_delay and self._key_press_time % self._repeat_rate == 0:
          self._process_key(self._last_key_pressed)
      else:
        self._last_key_pressed = 0

    # Handle text input
    char = rl.get_char_pressed()
    if char != 0 and char >= 32:  # Filter out control characters
      self.add_char_at_cursor(chr(char))

  def _process_key(self, key):
    if key == rl.KEY_LEFT:
      if self._cursor_position > 0:
        self.set_cursor_position(self._cursor_position - 1)
    elif key == rl.KEY_RIGHT:
      if self._cursor_position < len(self._input_text):
        self.set_cursor_position(self._cursor_position + 1)
    elif key == rl.KEY_BACKSPACE:
      self.delete_char_before_cursor()
    elif key == rl.KEY_DELETE:
      self.delete_char_at_cursor()
    elif key == rl.KEY_HOME:
      self.set_cursor_position(0)
    elif key == rl.KEY_END:
      self.set_cursor_position(len(self._input_text))