// Copyright 2026 Ling71671
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// Standalone code editor widget supporting fluent configuration.
pub struct CodeEditor {
  id_salt : String
  text : String
  size : @math.Vec2?
  show_line_numbers : Bool
  read_only : Bool
} derive(Eq, Debug)

///|
pub fn CodeEditor::new(id_salt : String, text : String) -> CodeEditor {
  { id_salt, text, size: None, show_line_numbers: true, read_only: false, }
}

///|
pub fn CodeEditor::size(self : CodeEditor, size : @math.Vec2) -> CodeEditor {
  { ..self, size: Some(size), }
}

///|
pub fn CodeEditor::show_line_numbers(
  self : CodeEditor,
  show : Bool,
) -> CodeEditor {
  { ..self, show_line_numbers: show, }
}

///|
pub fn CodeEditor::read_only(self : CodeEditor, read_only : Bool) -> CodeEditor {
  { ..self, read_only, }
}

///|
pub fn CodeEditor::show(
  self : CodeEditor,
  ctx : UIContext,
) -> (String, Response) {
  match self.size {
    Some(s) =>
      code_editor(
        ctx,
        self.id_salt,
        self.text,
        size=s,
        show_line_numbers=self.show_line_numbers,
        read_only=self.read_only,
      )
    None =>
      code_editor(
        ctx,
        self.id_salt,
        self.text,
        show_line_numbers=self.show_line_numbers,
        read_only=self.read_only,
      )
  }
}

///|
pub struct CodeEditorRef {
  id_salt : String
  target : Ref[String]
  size : @math.Vec2?
  show_line_numbers : Bool
  read_only : Bool
}

///|
pub fn CodeEditor::from_ref(
  id_salt : String,
  target : Ref[String],
) -> CodeEditorRef {
  { id_salt, target, size: None, show_line_numbers: true, read_only: false, }
}

///|
pub impl @core.Widget for CodeEditorRef with fn ui(self, ctx : UIContext) -> Response {
  let (new_text, resp) = match self.size {
    Some(s) =>
      code_editor(
        ctx,
        self.id_salt,
        self.target.val,
        size=s,
        show_line_numbers=self.show_line_numbers,
        read_only=self.read_only,
      )
    None =>
      code_editor(
        ctx,
        self.id_salt,
        self.target.val,
        show_line_numbers=self.show_line_numbers,
        read_only=self.read_only,
      )
  }
  self.target.val = new_text
  resp
}

// Copyright 2026 Ling71671
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// Component style descriptor for CodeEditor.
pub struct CodeEditorStyle {
  font_size : Double
  gutter_font_size : Double
  line_h : Double
  gutter_w : Double
  radius : Double
} derive(Eq, Debug)

///|
pub fn CodeEditorStyle::default() -> CodeEditorStyle {
  {
    font_size: 12.5,
    gutter_font_size: 11.0,
    line_h: 20.0,
    gutter_w: 44.0,
    radius: 6.0,
  }
}

///|
fn split_lines(text : String) -> Array[String] {
  let lines : Array[String] = []
  let mut start = 0
  let len = text.length()
  for i = 0; i < len; i = i + 1 {
    if text[i] == '\n' {
      lines.push(string_slice(text, start, i))
      start = i + 1
    }
  }
  lines.push(string_slice(text, start, len))
  lines
}

///|
fn cursor_to_line_col(text : String, cursor_pos : Int) -> (Int, Int) {
  let mut line = 0
  let mut col = 0
  let limit = if cursor_pos > text.length() {
    text.length()
  } else if cursor_pos < 0 {
    0
  } else {
    cursor_pos
  }
  for i = 0; i < limit; i = i + 1 {
    if text[i] == '\n' {
      line = line + 1
      col = 0
    } else {
      col = col + 1
    }
  }
  (line, col)
}

///|
fn line_col_to_cursor(lines : Array[String], line : Int, col : Int) -> Int {
  let num_lines = lines.length()
  if num_lines == 0 {
    return 0
  }
  let safe_line = if line < 0 {
    0
  } else if line >= num_lines {
    num_lines - 1
  } else {
    line
  }
  let mut pos = 0
  for i = 0; i < safe_line; i = i + 1 {
    pos = pos + lines[i].length() + 1
  }
  let line_len = lines[safe_line].length()
  let safe_col = if col < 0 {
    0
  } else if col > line_len {
    line_len
  } else {
    col
  }
  pos + safe_col
}

///|
fn is_comment_line(line : String) -> Bool {
  let len = line.length()
  let mut i = 0
  while i < len && (line[i] == ' ' || line[i] == '\t') {
    i = i + 1
  }
  if i + 1 < len && line[i] == '/' && line[i + 1] == '/' {
    true
  } else {
    false
  }
}

///|
fn hit_test_char_pos(
  ctx : UIContext,
  text : String,
  click_x : Double,
  font_size : Double,
) -> Int {
  let len = text.length()
  if len == 0 || click_x <= 0.0 {
    return 0
  }
  let total_w = ctx.measure_text(text, font_size).x
  if click_x >= total_w {
    return len
  }
  let boundaries : Array[Int] = [0]
  let mut idx = 0
  while idx < len {
    idx = next_char_boundary(text, idx)
    boundaries.push(idx)
  }
  let num_b = boundaries.length()
  let mut low = 0
  let mut high = num_b - 1
  while low < high {
    let mid = (low + high) / 2
    let sub = string_slice(text, 0, boundaries[mid])
    let w = ctx.measure_text(sub, font_size).x
    if w < click_x {
      low = mid + 1
    } else {
      high = mid
    }
  }
  let c1 = boundaries[low]
  let w1 = ctx.measure_text(string_slice(text, 0, c1), font_size).x
  let d1 = if click_x > w1 { click_x - w1 } else { w1 - click_x }
  if low > 0 {
    let c0 = boundaries[low - 1]
    let w0 = ctx.measure_text(string_slice(text, 0, c0), font_size).x
    let d0 = if click_x > w0 { click_x - w0 } else { w0 - click_x }
    if d0 <= d1 {
      c0
    } else {
      c1
    }
  } else {
    c1
  }
}

///|
/// Render a multi-line code and script editor in Studio Light porcelain style.
/// Features a left line-number gutter with active line highlighting,
/// monospace font baseline alignment, 2D arrow key navigation, soft Tab
/// indentation, range selection, vertical scrolling, and character clipping.
pub fn code_editor(
  ctx : UIContext,
  id_salt : String,
  text : String,
  size? : @math.Vec2,
  show_line_numbers? : Bool = true,
  read_only? : Bool = false,
) -> (String, Response) {
  let default_w = if ctx.available_width() > 0.0 {
    ctx.available_width()
  } else {
    480.0
  }
  let default_h = 220.0
  let editor_size = match size {
    Some(s) => s
    None => @math.Vec2::new(default_w, default_h)
  }

  let id = ctx.id_stack.derive_id("code_editor::" + id_salt)
  let (_anon_id, rect, resp) = ctx.allocate_space(editor_size)
  if resp.hovered {
    ctx.set_cursor_icon("text")
  }

  ctx.register_focusable(id)
  if resp.pressed() || resp.clicked() {
    ctx.request_focus(id)
    ctx.reset_caret_blink()
  }
  let is_focused = ctx.has_focus(id)

  let mut cursor_pos = ctx.get_text_cursor_pos(id, text.length())
  let mut selection_anchor = ctx.get_text_selection_anchor(id)
  let mut current_text = text
  let mut lines = split_lines(current_text)

  let scale = ctx.style.scale
  let ce_style = CodeEditorStyle::default()
  let code_font_sz = ce_style.font_size * scale
  let gutter_font_sz = ce_style.gutter_font_size * scale
  let gutter_w = if show_line_numbers { ce_style.gutter_w * scale } else { 0.0 }
  let line_h = ce_style.line_h * scale
  let pad_top = 8.0 * scale
  let pad_code_x = 10.0 * scale

  // 1. Vertical Scrolling
  let mut scroll_y = ctx.get_scroll_offset(id)
  if (resp.hovered() || resp.pressed()) && ctx.input.scroll_delta.y != 0.0 {
    scroll_y = scroll_y - ctx.input.scroll_delta.y
  }
  let total_lines = lines.length()
  let content_h = pad_top * 2.0 + total_lines.to_double() * line_h
  let max_scroll = if content_h > rect.h { content_h - rect.h } else { 0.0 }

  // 2. Mouse click & drag hit testing
  let code_x = rect.x + gutter_w + pad_code_x
  let click_x = ctx.input.mouse_pos.x - code_x
  let my = ctx.input.mouse_pos.y - (rect.y + pad_top) + scroll_y
  let click_line = if my < 0.0 {
    0
  } else {
    let l = (my / line_h).to_int()
    if l >= lines.length() {
      lines.length() - 1
    } else {
      l
    }
  }
  let target_line = if click_line < 0 { 0 } else { click_line }
  let line_str = if target_line < lines.length() {
    lines[target_line]
  } else {
    ""
  }
  let target_col = hit_test_char_pos(ctx, line_str, click_x, code_font_sz)
  let hit_cursor = line_col_to_cursor(lines, target_line, target_col)

  if ctx.input.mouse_pressed && (resp.hovered() || resp.pressed()) {
    ctx.request_focus(id)
    ctx.reset_caret_blink()
    if ctx.input.modifiers.shift {
      if selection_anchor is None {
        selection_anchor = Some(cursor_pos)
      }
    } else {
      selection_anchor = None
    }
    cursor_pos = hit_cursor
  } else if resp.dragged() {
    if selection_anchor is None {
      selection_anchor = Some(cursor_pos)
    }
    cursor_pos = hit_cursor
  }

  // 3. Keyboard input and navigation
  if is_focused {
    if ctx.input.key_pressed(Key::ArrowLeft) ||
      ctx.input.key_pressed(Key::ArrowRight) ||
      ctx.input.key_pressed(Key::ArrowUp) ||
      ctx.input.key_pressed(Key::ArrowDown) ||
      ctx.input.key_pressed(Key::Home) ||
      ctx.input.key_pressed(Key::End) ||
      ctx.input.key_pressed(Key::Enter) ||
      ctx.input.key_pressed(Key::Tab) ||
      ctx.input.key_pressed(Key::Backspace) ||
      ctx.input.key_pressed(Key::Delete) ||
      !ctx.input.text_input().is_empty() {
      ctx.reset_caret_blink()
    }

    let shift_held = ctx.input.modifiers.shift
    let ctrl_held = ctx.input.modifiers.ctrl || ctx.input.modifiers.meta

    // Select All: Ctrl+A
    if ctrl_held && ctx.input.key_pressed(Key::A) {
      ctx.input.consume_key(Key::A)
      selection_anchor = Some(0)
      cursor_pos = current_text.length()
    }

    let (cur_line, cur_col) = cursor_to_line_col(current_text, cursor_pos)

    // Horizontal character navigation
    if ctx.input.key_pressed(Key::ArrowLeft) {
      ctx.input.consume_key(Key::ArrowLeft)
      if shift_held && selection_anchor is None {
        selection_anchor = Some(cursor_pos)
      } else if !shift_held {
        selection_anchor = None
      }
      cursor_pos = prev_char_boundary(current_text, cursor_pos)
    }
    if ctx.input.key_pressed(Key::ArrowRight) {
      ctx.input.consume_key(Key::ArrowRight)
      if shift_held && selection_anchor is None {
        selection_anchor = Some(cursor_pos)
      } else if !shift_held {
        selection_anchor = None
      }
      cursor_pos = next_char_boundary(current_text, cursor_pos)
    }

    // Vertical line navigation
    if ctx.input.key_pressed(Key::ArrowUp) {
      ctx.input.consume_key(Key::ArrowUp)
      if shift_held && selection_anchor is None {
        selection_anchor = Some(cursor_pos)
      } else if !shift_held {
        selection_anchor = None
      }
      if cur_line > 0 {
        cursor_pos = line_col_to_cursor(lines, cur_line - 1, cur_col)
      } else {
        cursor_pos = 0
      }
    }
    if ctx.input.key_pressed(Key::ArrowDown) {
      ctx.input.consume_key(Key::ArrowDown)
      if shift_held && selection_anchor is None {
        selection_anchor = Some(cursor_pos)
      } else if !shift_held {
        selection_anchor = None
      }
      if cur_line < lines.length() - 1 {
        cursor_pos = line_col_to_cursor(lines, cur_line + 1, cur_col)
      } else {
        cursor_pos = current_text.length()
      }
    }

    // Line boundary and document boundary jumps
    if ctx.input.key_pressed(Key::Home) {
      ctx.input.consume_key(Key::Home)
      if shift_held && selection_anchor is None {
        selection_anchor = Some(cursor_pos)
      } else if !shift_held {
        selection_anchor = None
      }
      if ctrl_held {
        cursor_pos = 0
      } else {
        cursor_pos = line_col_to_cursor(lines, cur_line, 0)
      }
    }
    if ctx.input.key_pressed(Key::End) {
      ctx.input.consume_key(Key::End)
      if shift_held && selection_anchor is None {
        selection_anchor = Some(cursor_pos)
      } else if !shift_held {
        selection_anchor = None
      }
      if ctrl_held {
        cursor_pos = current_text.length()
      } else {
        cursor_pos = line_col_to_cursor(
          lines,
          cur_line,
          lines[cur_line].length(),
        )
      }
    }

    if !read_only {
      // Determine active selection range
      let (has_sel, s_start, s_end) = match selection_anchor {
        Some(a) =>
          if a != cursor_pos {
            let s = if a < cursor_pos { a } else { cursor_pos }
            let e = if a > cursor_pos { a } else { cursor_pos }
            (true, s, e)
          } else {
            (false, 0, 0)
          }
        None => (false, 0, 0)
      }

      // Enter key (newline insertion)
      if ctx.input.key_pressed(Key::Enter) {
        ctx.input.consume_key(Key::Enter)
        let (insert_at, left, right) = if has_sel {
          (
            s_start,
            string_slice(current_text, 0, s_start),
            string_slice(current_text, s_end, current_text.length()),
          )
        } else {
          (
            cursor_pos,
            string_slice(current_text, 0, cursor_pos),
            string_slice(current_text, cursor_pos, current_text.length()),
          )
        }
        current_text = left + "\n" + right
        cursor_pos = insert_at + 1
        selection_anchor = None
        lines = split_lines(current_text)
      }

      // Tab key (soft indentation: 2 spaces)
      if ctx.input.key_pressed(Key::Tab) {
        ctx.input.consume_key(Key::Tab)
        let (insert_at, left, right) = if has_sel {
          (
            s_start,
            string_slice(current_text, 0, s_start),
            string_slice(current_text, s_end, current_text.length()),
          )
        } else {
          (
            cursor_pos,
            string_slice(current_text, 0, cursor_pos),
            string_slice(current_text, cursor_pos, current_text.length()),
          )
        }
        current_text = left + "  " + right
        cursor_pos = insert_at + 2
        selection_anchor = None
        lines = split_lines(current_text)
      }

      // Backspace deletion
      if ctx.input.key_pressed(Key::Backspace) {
        ctx.input.consume_key(Key::Backspace)
        if has_sel {
          let left = string_slice(current_text, 0, s_start)
          let right = string_slice(current_text, s_end, current_text.length())
          current_text = left + right
          cursor_pos = s_start
          selection_anchor = None
          lines = split_lines(current_text)
        } else if cursor_pos > 0 && current_text.length() > 0 {
          let (new_text, new_pos) = delete_prev_char(current_text, cursor_pos)
          current_text = new_text
          cursor_pos = new_pos
          lines = split_lines(current_text)
        }
      }

      // Delete key
      if ctx.input.key_pressed(Key::Delete) {
        ctx.input.consume_key(Key::Delete)
        if has_sel {
          let left = string_slice(current_text, 0, s_start)
          let right = string_slice(current_text, s_end, current_text.length())
          current_text = left + right
          cursor_pos = s_start
          selection_anchor = None
          lines = split_lines(current_text)
        } else if cursor_pos < current_text.length() {
          current_text = delete_next_char(current_text, cursor_pos)
          lines = split_lines(current_text)
        }
      }

      // Text input insertion
      let input_str = ctx.input.text_input()
      if !input_str.is_empty() {
        let (insert_at, left, right) = if has_sel {
          (
            s_start,
            string_slice(current_text, 0, s_start),
            string_slice(current_text, s_end, current_text.length()),
          )
        } else {
          (
            cursor_pos,
            string_slice(current_text, 0, cursor_pos),
            string_slice(current_text, cursor_pos, current_text.length()),
          )
        }
        current_text = left + input_str + right
        cursor_pos = insert_at + input_str.length()
        selection_anchor = None
        lines = split_lines(current_text)
      }
    }
  }

  // 4. Auto-scroll to keep caret in view
  let (active_line, active_col) = cursor_to_line_col(current_text, cursor_pos)
  if is_focused {
    let caret_rel_y = pad_top + active_line.to_double() * line_h
    if caret_rel_y - scroll_y < pad_top {
      scroll_y = caret_rel_y - pad_top
    } else if caret_rel_y + line_h - scroll_y > rect.h - pad_top {
      scroll_y = caret_rel_y + line_h - (rect.h - pad_top)
    }
  }
  if scroll_y > max_scroll {
    scroll_y = max_scroll
  }
  if scroll_y < 0.0 {
    scroll_y = 0.0
  }
  ctx.set_scroll_offset(id, scroll_y)

  ctx.set_text_cursor_pos(id, cursor_pos)
  ctx.set_text_selection_anchor(id, selection_anchor)

  // 5. Active selection intervals for rendering
  let (has_selection, sel_start, sel_end) = match selection_anchor {
    Some(a) =>
      if a != cursor_pos {
        let s = if a < cursor_pos { a } else { cursor_pos }
        let e = if a > cursor_pos { a } else { cursor_pos }
        (true, s, e)
      } else {
        (false, 0, 0)
      }
    None => (false, 0, 0)
  }

  // 6. Rendering phase
  let border_col = if is_focused {
    @color.Color::border_focus()
  } else if resp.hovered() {
    @color.Color::border_strong()
  } else {
    @color.Color::border_default()
  }

  let editor_radius = ce_style.radius * scale

  // Editor frame background & border
  ctx.painter().add_rect(rect, @color.Color::bg_window(), editor_radius)
  ctx.painter().add_rect_stroke(rect, border_col, 1.0, editor_radius)

  // Scissor clip for editor interior
  let clip_rect = @math.Rect::new(
    rect.x + 1.0,
    rect.y + 1.0,
    rect.w - 2.0,
    rect.h - 2.0,
  )
  ctx.push_clip(clip_rect)

  // Draw Gutter
  if show_line_numbers {
    let gutter_rect = @math.Rect::new(rect.x, rect.y, gutter_w, rect.h)
    ctx.painter().add_rect(gutter_rect, @color.Color::bg_subtle(), 0.0)
    ctx
    .painter()
    .add_line(
      @math.Vec2::new(rect.x + gutter_w, rect.y),
      @math.Vec2::new(rect.x + gutter_w, rect.y + rect.h),
      @color.Color::border_muted(),
      1.0,
    )
  }

  // Active Line Highlight Wash (rendered only when no range selection exists)
  if is_focused && !has_selection && active_line < lines.length() {
    let active_y = rect.y +
      pad_top +
      active_line.to_double() * line_h -
      scroll_y
    if active_y + line_h >= rect.y && active_y <= rect.y + rect.h {
      let wash_rect = @math.Rect::new(
        rect.x + gutter_w + 1.0,
        active_y,
        rect.w - gutter_w - 2.0,
        line_h,
      )
      ctx.painter().add_rect(wash_rect, @color.Color::bg_surface(), 0.0)
    }
  }

  // Selection Highlight Wash
  if has_selection {
    let mut offset = 0
    for idx = 0; idx < lines.length(); idx = idx + 1 {
      let line_len = lines[idx].length()
      let line_start = offset
      let line_end = offset + line_len
      offset = line_end + 1 // +1 for newline separator
      let o_start = if sel_start > line_start { sel_start } else { line_start }
      let o_end = if sel_end < line_end { sel_end } else { line_end }
      if o_start < o_end {
        let line_y = rect.y + pad_top + idx.to_double() * line_h - scroll_y
        if line_y + line_h >= rect.y && line_y <= rect.y + rect.h {
          let pre = string_slice(lines[idx], 0, o_start - line_start)
          let sel = string_slice(
            lines[idx],
            o_start - line_start,
            o_end - line_start,
          )
          let pre_w = ctx.measure_text(pre, code_font_sz).x
          let sel_w = ctx.measure_text(sel, code_font_sz).x
          let sel_rect = @math.Rect::new(code_x + pre_w, line_y, sel_w, line_h)
          ctx.painter().add_rect(sel_rect, @color.Color::accent_soft(), 0.0)
        }
      }
    }
  }

  // Render each visible line
  for idx = 0; idx < lines.length(); idx = idx + 1 {
    let line_text = lines[idx]
    let line_y = rect.y + pad_top + idx.to_double() * line_h - scroll_y

    // Skip lines culled above
    if line_y + line_h < rect.y {
      continue
    }
    // Stop rendering lines that fall completely below the visible clip rect
    if line_y > rect.y + rect.h {
      break
    }

    // Line number in gutter
    if show_line_numbers {
      let num_str = (idx + 1).to_string()
      let num_size = ctx.measure_text(num_str, gutter_font_sz)
      let num_x = rect.x + gutter_w - num_size.x - 8.0 * scale
      let num_col = if idx == active_line && is_focused {
        @color.Color::accent_primary()
      } else {
        @color.Color::text_disabled()
      }
      ctx
      .painter()
      .add_text(
        @math.Vec2::new(num_x, line_y + 3.0 * scale),
        num_str,
        gutter_font_sz,
        num_col,
        font_family="monospace",
      )
    }

    // Code text
    let text_col = if is_comment_line(line_text) {
      @color.Color::text_muted()
    } else {
      @color.Color::text_primary()
    }

    ctx
    .painter()
    .add_text(
      @math.Vec2::new(code_x, line_y + 3.0 * scale),
      line_text,
      code_font_sz,
      text_col,
      font_family="monospace",
    )
  }

  // Render Caret
  if is_focused && ctx.is_caret_visible() && active_line < lines.length() {
    let active_str = lines[active_line]
    let prefix = string_slice(active_str, 0, active_col)
    let prefix_w = ctx.measure_text(prefix, code_font_sz).x
    let caret_x = rect.x + gutter_w + pad_code_x + prefix_w
    let caret_y = rect.y +
      pad_top +
      active_line.to_double() * line_h -
      scroll_y +
      3.0 * scale

    let caret_h = ctx.style.spacing_lg * scale
    if caret_y + caret_h >= rect.y && caret_y <= rect.y + rect.h {
      ctx
      .painter()
      .add_line(
        @math.Vec2::new(caret_x, caret_y),
        @math.Vec2::new(caret_x, caret_y + caret_h),
        @color.Color::accent_primary(),
        1.5 * scale,
      )
    }
  }

  // Scrollbar thumb indicator
  if max_scroll > 0.0 && content_h > 0.0 {
    let bar_w = 4.0 * scale
    let bar_x = rect.x + rect.w - bar_w - 2.0 * scale
    let track_h = rect.h - 4.0 * scale
    let thumb_ratio = rect.h / content_h
    let raw_thumb_h = track_h * thumb_ratio
    let thumb_h = if raw_thumb_h < 20.0 * scale {
      20.0 * scale
    } else {
      raw_thumb_h
    }
    let scroll_ratio = scroll_y / max_scroll
    let thumb_y = rect.y + 2.0 * scale + (track_h - thumb_h) * scroll_ratio
    let thumb_rect = @math.Rect::new(bar_x, thumb_y, bar_w, thumb_h)
    ctx
    .painter()
    .add_rect(
      thumb_rect,
      @color.Color::border_default(),
      ctx.style.radius_sm * scale,
    )
  }

  ctx.pop_clip()

  let editor_resp = Response::with_focus(
    id,
    rect,
    resp.hovered,
    resp.clicked,
    resp.pressed,
    resp.dragged,
    is_focused,
    is_focused && ctx.prev_focused_id() != id,
    ctx.lost_focus(id),
    secondary_clicked=resp.secondary_clicked,
  )

  (current_text, editor_resp)
}