//! Input module.
//!
//! This public module implements the Liora text input component with native editing, selection, password, and addon support. It keeps the reusable
//! component logic inside `liora-components` rather than host applications so
//! downstream GPUI applications can compose the same behavior with their own
//! app state, assets, and release policy.
//!
//! ## Usage model
//!
//! Components in this module render native GPUI element trees. Stateless builder
//! values can be constructed inline, while controls with focus, selection,
//! popup, drag, or editing state should be stored as `gpui::Entity<T>` fields in
//! the parent view so state survives GPUI render passes.
//!
//! ## Design contract
//!
//! The implementation should use Liora theme tokens from `liora-core` and
//! `liora-theme`, keep accessibility-oriented keyboard/pointer behavior close to
//! the component, and avoid app-specific host-application resources in this SDK
//! crate.

use gpui::{
    AnyElement, App, Bounds, Context, Element, ElementId, ElementInputHandler, Entity,
    EntityInputHandler, FocusHandle, Focusable, GlobalElementId, InspectorElementId, IntoElement,
    KeyBinding, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, Pixels, Point, Render,
    ShapedLine, SharedString, Style, TextRun, UTF16Selection, Window, actions, fill, point,
    prelude::*, px, size,
};
use liora_core::{Config, LocalizedText, ui_font_family, ui_font_weight};
use liora_icons::Icon;
use liora_icons_lucide::IconName;
use std::ops::{Add, Range};

fn clamp_to_char_boundary(text: &str, mut offset: usize) -> usize {
    offset = offset.min(text.len());
    while offset > 0 && !text.is_char_boundary(offset) {
        offset -= 1;
    }
    offset
}

fn normalize_replace_range(text: &str, range: Range<usize>) -> Range<usize> {
    let start = clamp_to_char_boundary(text, range.start);
    let end = clamp_to_char_boundary(text, range.end);
    start.min(end)..start.max(end)
}

actions!(
    input,
    [
        #[doc = "Keyboard action that deletes the character before the caret."]
        Backspace,
        #[doc = "Keyboard action that deletes the character after the caret."]
        Delete,
        #[doc = "Keyboard action that moves the caret one position left."]
        Left,
        #[doc = "Keyboard action that moves the caret one position right."]
        Right,
        #[doc = "Keyboard action that moves the caret to the start of the input."]
        Home,
        #[doc = "Keyboard action that moves the caret to the end of the input."]
        End,
        #[doc = "Keyboard action that selects the full input value."]
        SelectAll,
        #[doc = "Keyboard action that commits the current input value."]
        Enter,
        #[doc = "Keyboard action that moves input-associated popup focus upward."]
        InputUp,
        #[doc = "Keyboard action that moves input-associated popup focus downward."]
        InputDown,
        #[doc = "Keyboard action that copies the selected input text."]
        Copy,
        #[doc = "Keyboard action that pastes clipboard text into the input."]
        Paste,
        #[doc = "Keyboard action that cuts the selected input text."]
        Cut,
        #[doc = "Keyboard action that extends selection one position left."]
        SelectLeft,
        #[doc = "Keyboard action that extends selection one position right."]
        SelectRight,
        #[doc = "Keyboard action that extends selection to the previous logical line."]
        SelectUp,
        #[doc = "Keyboard action that extends selection to the next logical line."]
        SelectDown,
        #[doc = "Keyboard action that extends selection to the start of the input."]
        SelectHome,
        #[doc = "Keyboard action that extends selection to the end of the input."]
        SelectEnd,
        #[doc = "Keyboard action that toggles password visibility for password inputs."]
        TogglePassword,
    ]
);

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// Options that control input type behavior.
pub enum InputType {
    /// Accepts plain text input.
    Text,
    /// Masks input text until password visibility is toggled.
    Password,
}

/// Fluent native GPUI component for rendering Liora input.
pub struct Input {
    value: SharedString,
    placeholder: LocalizedText,
    disabled: bool,
    clearable: bool,
    icon_prefix: Option<IconName>,
    icon_suffix: Option<IconName>,
    focus_handle: FocusHandle,
    selected_range: Range<usize>,
    selection_reversed: bool,
    marked_range: Option<Range<usize>>,
    last_line_layouts: Vec<(ShapedLine, Pixels)>,
    last_layout_text: SharedString,
    last_bounds: Option<Bounds<Pixels>>,
    last_layout_is_masked: bool,
    cursor_visible: bool,
    blink_task: Option<gpui::Task<()>>,
    filter: Option<Box<dyn Fn(&str) -> bool + 'static>>,
    max_length: Option<usize>,
    input_type: InputType,
    password_visible: bool,
    mask_char: char,
    prepend: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>>,
    append: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>>,
    width: Option<Pixels>,
    height: Option<Pixels>,
    text_align: gpui::TextAlign,
    /// Minimum number of text rows reserved by a multiline input.
    pub min_rows: usize,
    on_enter: Option<Box<dyn Fn(&mut Self, &str, &mut Window, &mut Context<Self>) + 'static>>,
    on_change: Option<Box<dyn Fn(&str, &mut Context<Self>) + 'static>>,
    on_clear: Option<Box<dyn Fn(&mut Context<Self>) + 'static>>,
}

impl Input {
    /// Creates `Input` initialized from the supplied value.
    pub fn new(value: impl Into<SharedString>, cx: &mut Context<Self>) -> Self {
        Self {
            value: value.into(),
            placeholder: LocalizedText::literal(""),
            disabled: false,
            clearable: false,
            icon_prefix: None,
            icon_suffix: None,
            focus_handle: cx.focus_handle(),
            selected_range: 0..0,
            selection_reversed: false,
            marked_range: None,
            last_line_layouts: Vec::new(),
            last_layout_text: SharedString::default(),
            last_bounds: None,
            last_layout_is_masked: false,
            cursor_visible: true,
            blink_task: None,
            filter: None,
            max_length: None,
            input_type: InputType::Text,
            password_visible: false,
            mask_char: '•',
            prepend: None,
            append: None,
            width: None,
            height: None,
            min_rows: 1,
            text_align: gpui::TextAlign::Left,
            on_enter: None,
            on_change: None,
            on_clear: None,
        }
    }
    /// Uses the supplied placeholder text when the value is empty.
    pub fn placeholder(mut self, p: impl Into<LocalizedText>) -> Self {
        self.placeholder = p.into();
        self
    }
    /// Toggles the disabled state and suppresses user interaction when enabled.
    pub fn disabled(mut self, d: bool) -> Self {
        self.disabled = d;
        self
    }
    /// Toggles whether the component renders a clear affordance.
    pub fn clearable(mut self, c: bool) -> Self {
        self.clearable = c;
        self
    }
    /// Sets the icon rendered before the main input content.
    pub fn icon_prefix(mut self, icon: IconName) -> Self {
        self.icon_prefix = Some(icon);
        self
    }
    /// Sets the icon rendered after the main input content.
    pub fn icon_suffix(mut self, icon: IconName) -> Self {
        self.icon_suffix = Some(icon);
        self
    }
    /// Updates the stored icon suffix value and keeps the existing component identity.
    pub fn set_icon_suffix(&mut self, icon: Option<IconName>, cx: &mut Context<Self>) {
        if self.icon_suffix == icon {
            return;
        }
        self.icon_suffix = icon;
        cx.notify();
    }
    /// Updates the stored clearable value and keeps the existing component identity.
    pub fn set_clearable(&mut self, clearable: bool, cx: &mut Context<Self>) {
        if self.clearable == clearable {
            return;
        }
        self.clearable = clearable;
        cx.notify();
    }
    /// Returns whether filter is currently enabled or available.
    pub fn filter(mut self, f: impl Fn(&str) -> bool + 'static) -> Self {
        self.filter = Some(Box::new(f));
        self
    }
    /// Limits the number of characters accepted by the input.
    pub fn max_length(mut self, max: usize) -> Self {
        self.max_length = Some(max);
        self
    }
    /// Sets the password value used by the component.
    pub fn password(mut self) -> Self {
        self.input_type = InputType::Password;
        self
    }
    /// Sets the mask char value used by the component.
    pub fn mask_char(mut self, c: char) -> Self {
        self.mask_char = c;
        self
    }
    /// Sets the minimum rows limit.
    pub fn min_rows(mut self, rows: usize) -> Self {
        self.min_rows = rows;
        self
    }
    /// Sets the component height token used during GPUI layout.
    pub fn height(mut self, h: impl Into<Pixels>) -> Self {
        self.height = Some(h.into());
        self
    }
    /// Sets the component width token used during GPUI layout.
    pub fn width(mut self, w: impl Into<Pixels>) -> Self {
        self.width = Some(w.into());
        self
    }
    /// Applies the predefined width sm sizing preset.
    pub fn width_sm(self) -> Self {
        self.width(px(96.0))
    }
    /// Updates the stored width value and keeps the existing component identity.
    pub fn set_width(&mut self, w: impl Into<Pixels>, cx: &mut Context<Self>) {
        self.width = Some(w.into());
        cx.notify();
    }
    /// Updates the stored width sm value and keeps the existing component identity.
    pub fn set_width_sm(&mut self, cx: &mut Context<Self>) {
        self.set_width(px(96.0), cx);
    }
    /// Sets the text align value used by the component.
    pub fn text_align(mut self, align: gpui::TextAlign) -> Self {
        self.text_align = align;
        self
    }
    /// Registers a callback that runs when enter occurs.
    pub fn on_enter(
        mut self,
        f: impl Fn(&mut Self, &str, &mut Window, &mut Context<Self>) + 'static,
    ) -> Self {
        self.on_enter = Some(Box::new(f));
        self
    }

    /// Updates the stored on enter value and keeps the existing component identity.
    pub fn set_on_enter(
        &mut self,
        f: impl Fn(&mut Self, &str, &mut Window, &mut Context<Self>) + 'static,
        cx: &mut Context<Self>,
    ) {
        self.on_enter = Some(Box::new(f));
        cx.notify();
    }

    /// Registers a callback that runs when change occurs.
    pub fn on_change(mut self, f: impl Fn(&str, &mut Context<Self>) + 'static) -> Self {
        self.on_change = Some(Box::new(f));
        self
    }

    /// Updates the stored on change value and keeps the existing component identity.
    pub fn set_on_change(&mut self, f: impl Fn(&str, &mut Context<Self>) + 'static) {
        self.on_change = Some(Box::new(f));
    }

    /// Clears the current on change state.
    pub fn clear_on_change(&mut self) {
        self.on_change = None;
    }

    /// Registers a callback that runs after the clear affordance clears the value.
    pub fn on_clear(mut self, f: impl Fn(&mut Context<Self>) + 'static) -> Self {
        self.on_clear = Some(Box::new(f));
        self
    }

    /// Updates the stored on clear value and keeps the existing component identity.
    pub fn set_on_clear(&mut self, f: impl Fn(&mut Context<Self>) + 'static) {
        self.on_clear = Some(Box::new(f));
    }

    fn emit_change(&mut self, cx: &mut Context<Self>) {
        if let Some(on_change) = self.on_change.take() {
            let value = self.value.to_string();
            on_change(&value, cx);
            self.on_change = Some(on_change);
        }
    }

    /// Updates the stored placeholder value and keeps the existing component identity.
    pub fn set_placeholder(&mut self, p: impl Into<LocalizedText>, cx: &mut Context<Self>) {
        let p = p.into();
        if self.placeholder == p {
            return;
        }
        self.placeholder = p;
        cx.notify();
    }

    /// Updates the stored disabled value and keeps the existing component identity.
    pub fn set_disabled(&mut self, d: bool, cx: &mut Context<Self>) {
        if self.disabled == d {
            return;
        }
        self.disabled = d;
        cx.notify();
    }

    /// Updates the stored value value and keeps the existing component identity.
    pub fn set_value(&mut self, value: impl Into<SharedString>, cx: &mut Context<Self>) {
        let value = value.into();
        if self.value == value {
            return;
        }
        self.value = value;
        self.selected_range = self.value.len()..self.value.len();
        cx.notify();
    }

    /// Updates the stored min rows value and keeps the existing component identity.
    pub fn set_min_rows(&mut self, rows: usize, cx: &mut Context<Self>) {
        self.min_rows = rows;
        cx.notify();
    }

    /// Returns the serialized value used by forms, configuration, or persistence.
    pub fn value(&self) -> SharedString {
        self.value.clone()
    }

    /// Performs the selected range operation used by this component.
    pub fn selected_range(&self) -> Range<usize> {
        self.selected_range.clone()
    }

    /// Updates the current caret/selection range and keeps it inside valid UTF-8 boundaries.
    pub fn set_selection(&mut self, range: Range<usize>, cx: &mut Context<Self>) {
        let start = clamp_to_char_boundary(self.value.as_ref(), range.start);
        let end = clamp_to_char_boundary(self.value.as_ref(), range.end);
        self.selected_range = start.min(end)..start.max(end);
        self.selection_reversed = false;
        self.reset_blink(cx);
    }

    /// Performs the insert text operation used by this component.
    pub fn insert_text(&mut self, text: &str, cx: &mut Context<Self>) {
        self.internal_replace(text, cx);
    }

    /// Performs the indent selection operation used by this component.
    pub fn indent_selection(&mut self, indent: &str, cx: &mut Context<Self>) {
        if indent.is_empty() {
            return;
        }
        if self.selected_range.is_empty() {
            self.internal_replace(indent, cx);
            return;
        }
        self.reindent_selected_lines(indent, true, cx);
    }

    /// Performs the outdent selection operation used by this component.
    pub fn outdent_selection(&mut self, indent: &str, cx: &mut Context<Self>) {
        self.reindent_selected_lines(indent, false, cx);
    }

    /// Registers GPUI key bindings required for keyboard interaction.
    pub fn register_key_bindings(cx: &mut App) {
        cx.bind_keys([
            KeyBinding::new("backspace", Backspace, None),
            KeyBinding::new("delete", Delete, None),
            KeyBinding::new("left", Left, None),
            KeyBinding::new("shift-left", SelectLeft, None),
            KeyBinding::new("right", Right, None),
            KeyBinding::new("shift-right", SelectRight, None),
            KeyBinding::new("home", Home, None),
            KeyBinding::new("shift-home", SelectHome, None),
            KeyBinding::new("end", End, None),
            KeyBinding::new("shift-end", SelectEnd, None),
            KeyBinding::new("cmd-a", SelectAll, None),
            KeyBinding::new("ctrl-a", SelectAll, None),
            KeyBinding::new("cmd-c", Copy, None),
            KeyBinding::new("ctrl-c", Copy, None),
            KeyBinding::new("cmd-v", Paste, None),
            KeyBinding::new("ctrl-v", Paste, None),
            KeyBinding::new("cmd-x", Cut, None),
            KeyBinding::new("ctrl-x", Cut, None),
            KeyBinding::new("enter", Enter, None),
            KeyBinding::new("up", InputUp, None),
            KeyBinding::new("shift-up", SelectUp, None),
            KeyBinding::new("down", InputDown, None),
            KeyBinding::new("shift-down", SelectDown, None),
        ]);
    }

    /// Clears the currently stored state or selection.
    pub fn clear(&mut self, cx: &mut Context<Self>) {
        self.value = SharedString::default();
        self.selected_range = 0..0;
        self.emit_change(cx);
        if let Some(on_clear) = self.on_clear.take() {
            on_clear(cx);
            self.on_clear = Some(on_clear);
        }
        cx.notify();
    }

    fn cursor_offset(&self) -> usize {
        if self.selection_reversed {
            self.selected_range.start
        } else {
            self.selected_range.end
        }
    }

    fn prev_char(&self, offset: usize) -> usize {
        if offset == 0 {
            return 0;
        }
        let mut p = offset - 1;
        while p > 0 && !self.value.is_char_boundary(p) {
            p -= 1;
        }
        p
    }
    fn next_char(&self, offset: usize) -> usize {
        if offset >= self.value.len() {
            return self.value.len();
        }
        let mut n = offset + 1;
        while n < self.value.len() && !self.value.is_char_boundary(n) {
            n += 1;
        }
        n
    }
    fn move_to(&mut self, offset: usize, cx: &mut Context<Self>) {
        self.selected_range = offset..offset;
        self.reset_blink(cx);
    }
    fn select_to(&mut self, offset: usize, cx: &mut Context<Self>) {
        if self.selection_reversed {
            self.selected_range.start = offset
        } else {
            self.selected_range.end = offset
        }
        if self.selected_range.end < self.selected_range.start {
            self.selection_reversed = !self.selection_reversed;
            self.selected_range = self.selected_range.end..self.selected_range.start;
        }
        self.reset_blink(cx);
    }

    fn backspace(&mut self, _: &Backspace, _: &mut Window, cx: &mut Context<Self>) {
        if self.selected_range.is_empty() {
            let cursor = self.cursor_offset();
            let previous = self.prev_char(cursor);
            if previous == cursor {
                return;
            }
            self.selected_range = previous..cursor;
            self.selection_reversed = false;
        }
        self.internal_replace("", cx);
    }
    fn delete(&mut self, _: &Delete, _: &mut Window, cx: &mut Context<Self>) {
        if self.selected_range.is_empty() {
            let cursor = self.cursor_offset();
            let next = self.next_char(cursor);
            if next == cursor {
                return;
            }
            self.selected_range = cursor..next;
            self.selection_reversed = false;
        }
        self.internal_replace("", cx);
    }
    fn left(&mut self, _: &Left, _: &mut Window, cx: &mut Context<Self>) {
        self.move_to(self.prev_char(self.cursor_offset()), cx);
    }
    fn select_left(&mut self, _: &SelectLeft, _: &mut Window, cx: &mut Context<Self>) {
        self.select_to(self.prev_char(self.cursor_offset()), cx);
    }
    fn right(&mut self, _: &Right, _: &mut Window, cx: &mut Context<Self>) {
        self.move_to(self.next_char(self.cursor_offset()), cx);
    }
    fn select_right(&mut self, _: &SelectRight, _: &mut Window, cx: &mut Context<Self>) {
        self.select_to(self.next_char(self.cursor_offset()), cx);
    }
    fn home(&mut self, _: &Home, _: &mut Window, cx: &mut Context<Self>) {
        self.move_to(0, cx);
    }
    fn select_home(&mut self, _: &SelectHome, _: &mut Window, cx: &mut Context<Self>) {
        self.select_to(0, cx);
    }
    fn end(&mut self, _: &End, _: &mut Window, cx: &mut Context<Self>) {
        self.move_to(self.value.len(), cx);
    }
    fn select_end(&mut self, _: &SelectEnd, _: &mut Window, cx: &mut Context<Self>) {
        self.select_to(self.value.len(), cx);
    }
    fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context<Self>) {
        self.selected_range = 0..self.value.len();
        self.reset_blink(cx);
    }

    fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
        if !self.selected_range.is_empty()
            && (self.input_type != InputType::Password || self.password_visible)
        {
            let selected_text = self.value[self.selected_range.clone()].to_string();
            cx.write_to_clipboard(gpui::ClipboardItem::new_string(selected_text));
        }
    }

    fn paste(&mut self, _: &Paste, _: &mut Window, cx: &mut Context<Self>) {
        if let Some(clipboard) = cx.read_from_clipboard() {
            if let Some(text) = clipboard.text() {
                self.internal_replace(&text, cx);
            }
        }
    }

    fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
        if !self.selected_range.is_empty() {
            self.copy(&Copy, window, cx);
            self.internal_replace("", cx);
        }
    }

    fn enter(&mut self, _: &Enter, window: &mut Window, cx: &mut Context<Self>) {
        if let Some(on_enter) = self.on_enter.take() {
            let value = self.value.to_string();
            on_enter(self, &value, window, cx);
            self.on_enter = Some(on_enter);
        } else {
            self.internal_replace("\n", cx);
        }
    }

    fn up(&mut self, _: &InputUp, _: &mut Window, cx: &mut Context<Self>) {
        self.move_vertical(-1, false, cx);
    }
    fn select_up(&mut self, _: &SelectUp, _: &mut Window, cx: &mut Context<Self>) {
        self.move_vertical(-1, true, cx);
    }
    fn down(&mut self, _: &InputDown, _: &mut Window, cx: &mut Context<Self>) {
        self.move_vertical(1, false, cx);
    }
    fn select_down(&mut self, _: &SelectDown, _: &mut Window, cx: &mut Context<Self>) {
        self.move_vertical(1, true, cx);
    }

    fn toggle_password(&mut self, _: &TogglePassword, _: &mut Window, cx: &mut Context<Self>) {
        self.password_visible = !self.password_visible;
        cx.notify();
    }

    fn move_vertical(&mut self, delta: isize, select: bool, cx: &mut Context<Self>) {
        let text = self.value.clone();
        let offset = self.cursor_offset();
        let lines: Vec<&str> = text.split('\n').collect();
        let mut current_line = 0;
        let mut line_start = 0;
        for (i, line) in lines.iter().enumerate() {
            if offset >= line_start && offset <= line_start + line.len() {
                current_line = i;
                break;
            }
            line_start += line.len() + 1;
        }

        let col = offset - line_start;
        let target_line = (current_line as isize + delta).max(0) as usize;
        if target_line >= lines.len() {
            return;
        }

        let mut target_start = 0;
        for i in 0..target_line {
            target_start += lines[i].len() + 1;
        }
        let target_len = lines[target_line].len();
        let new_col = col.min(target_len);
        let new_offset = target_start + new_col;
        if select {
            self.select_to(new_offset, cx);
        } else {
            self.move_to(new_offset, cx);
        }
    }

    fn index_for_point(&self, pt: Point<Pixels>, window: &Window) -> usize {
        if let (Some(bounds), layouts) = (self.last_bounds.as_ref(), &self.last_line_layouts) {
            if layouts.is_empty() {
                return 0;
            }
            let line_height = window.line_height();
            let mut best_line = 0;
            let mut final_original_byte_offset = 0;
            let mut current_original_byte_offset = 0;
            for (i, (_layout, y_offset)) in layouts.iter().enumerate() {
                if pt.y >= *y_offset && pt.y < *y_offset + line_height {
                    best_line = i;
                    final_original_byte_offset = current_original_byte_offset;
                    break;
                }
                if pt.y >= *y_offset {
                    best_line = i;
                    final_original_byte_offset = current_original_byte_offset;
                }
                current_original_byte_offset += self
                    .value
                    .split('\n')
                    .nth(i)
                    .map(|l| l.len() + 1)
                    .unwrap_or(0);
            }
            let x = pt.x - bounds.left();
            let display_index = layouts[best_line]
                .0
                .index_for_x(x)
                .unwrap_or(layouts[best_line].0.len);

            if self.last_layout_is_masked {
                let char_count = display_index / self.mask_char.len_utf8();
                let original_line = self.value.split('\n').nth(best_line).unwrap_or("");
                let mut byte_idx = 0;
                for _ in 0..char_count {
                    if byte_idx >= original_line.len() {
                        break;
                    }
                    let Some(ch) = original_line[byte_idx..].chars().next() else {
                        break;
                    };
                    byte_idx += ch.len_utf8();
                }
                final_original_byte_offset + byte_idx
            } else {
                final_original_byte_offset + display_index
            }
        } else {
            self.value.len()
        }
    }

    fn on_mouse_down(
        &mut self,
        event: &MouseDownEvent,
        window: &mut Window,
        cx: &mut Context<Self>,
    ) {
        window.focus(&self.focus_handle, cx);
        if self.value.is_empty() {
            self.move_to(0, cx);
            return;
        }
        let idx = self.index_for_point(event.position, window);
        match event.click_count {
            1 => {
                if event.modifiers.shift {
                    self.select_to(idx, cx);
                } else {
                    self.move_to(idx, cx);
                }
            }
            2 => {
                let range = self.word_range_at(idx);
                self.selected_range = range;
                self.selection_reversed = false;
                self.reset_blink(cx);
            }
            3 => {
                self.selected_range = 0..self.value.len();
                self.selection_reversed = false;
                self.reset_blink(cx);
            }
            _ => {}
        }
    }

    fn word_range_at(&self, idx: usize) -> Range<usize> {
        let text = self.value.as_ref();
        if text.is_empty() {
            return 0..0;
        }
        let idx = idx.min(text.len());
        let mut start = idx;
        while start > 0 {
            let prev = self.prev_char(start);
            let Some(c) = text[prev..start].chars().next() else {
                break;
            };
            if !c.is_alphanumeric() && c != '_' {
                break;
            }
            start = prev;
        }
        let mut end = idx;
        while end < text.len() {
            let next = self.next_char(end);
            let Some(c) = text[end..next].chars().next() else {
                break;
            };
            if !c.is_alphanumeric() && c != '_' {
                break;
            }
            end = next;
        }
        start..end
    }

    fn on_mouse_move(
        &mut self,
        event: &MouseMoveEvent,
        window: &mut Window,
        cx: &mut Context<Self>,
    ) {
        if event.pressed_button == Some(MouseButton::Left) {
            let idx = self.index_for_point(event.position, window);
            self.select_to(idx, cx);
        }
    }

    fn start_blink(&mut self, cx: &mut Context<Self>) {
        self.cursor_visible = true;
        let executor = cx.background_executor().clone();
        self.blink_task = Some(cx.spawn(async move |this, cx| {
            loop {
                executor.timer(std::time::Duration::from_millis(500)).await;
                let res = this.update(cx, |this, cx| {
                    this.cursor_visible = !this.cursor_visible;
                    cx.notify();
                });
                if res.is_err() {
                    break;
                }
            }
        }));
    }

    fn reset_blink(&mut self, cx: &mut Context<Self>) {
        self.cursor_visible = true;
        if self.blink_task.is_none() {
            self.start_blink(cx);
        } else {
            cx.notify();
        }
    }

    fn internal_replace(&mut self, new_text: &str, cx: &mut Context<Self>) {
        let mut v = self.value.to_string();
        let range = self.selected_range.clone();
        let potential_v = {
            let mut temp = v.clone();
            temp.replace_range(range.clone(), new_text);
            temp
        };
        if let Some(ref filter) = self.filter {
            if !filter(&potential_v) {
                return;
            }
        }
        if let Some(max) = self.max_length {
            if potential_v.chars().count() > max {
                return;
            }
        }
        v.replace_range(range, new_text);
        self.value = SharedString::from(v);
        let pos = self.selected_range.start + new_text.len();
        self.selected_range = pos..pos;
        self.emit_change(cx);
        self.reset_blink(cx);
    }

    fn apply_value_with_selection(
        &mut self,
        next_value: String,
        next_selection: Range<usize>,
        cx: &mut Context<Self>,
    ) {
        if let Some(ref filter) = self.filter {
            if !filter(&next_value) {
                return;
            }
        }
        if let Some(max) = self.max_length {
            if next_value.chars().count() > max {
                return;
            }
        }
        self.value = SharedString::from(next_value);
        self.selected_range =
            next_selection.start.min(self.value.len())..next_selection.end.min(self.value.len());
        self.selection_reversed = false;
        self.emit_change(cx);
        self.reset_blink(cx);
    }

    fn reindent_selected_lines(&mut self, indent: &str, indenting: bool, cx: &mut Context<Self>) {
        let value = self.value.to_string();
        let selection = self.selected_range.clone();
        let line_start = line_start_at(&value, selection.start);
        let line_end = selected_line_end(&value, selection.clone());
        let mut changed = false;
        let mut next = String::with_capacity(value.len() + indent.len() * 4);
        next.push_str(&value[..line_start]);

        let mut selection_start_delta = 0isize;
        let mut selection_end_delta = 0isize;
        let mut cursor = line_start;

        for line in value[line_start..line_end].split_inclusive('\n') {
            let line_abs_start = cursor;
            let (line_body, line_ending) = line
                .strip_suffix('\n')
                .map_or((line, ""), |body| (body, "\n"));
            if indenting {
                next.push_str(indent);
                next.push_str(line_body);
                next.push_str(line_ending);
                changed = true;
                if line_abs_start <= selection.start {
                    selection_start_delta += indent.len() as isize;
                }
                if line_abs_start < selection.end || selection.is_empty() {
                    selection_end_delta += indent.len() as isize;
                }
            } else if let Some(remove_len) = removable_indent_len(line_body, indent) {
                next.push_str(&line_body[remove_len..]);
                next.push_str(line_ending);
                changed = true;
                if line_abs_start < selection.start {
                    selection_start_delta -= remove_len as isize;
                }
                if line_abs_start < selection.end {
                    selection_end_delta -= remove_len as isize;
                }
            } else {
                next.push_str(line_body);
                next.push_str(line_ending);
            }
            cursor += line.len();
        }

        if !changed {
            return;
        }

        next.push_str(&value[line_end..]);
        let start = apply_signed_delta(selection.start, selection_start_delta);
        let end = apply_signed_delta(selection.end, selection_end_delta).max(start);
        self.apply_value_with_selection(next, start..end, cx);
    }

    fn is_password(&self) -> bool {
        self.input_type == InputType::Password && !self.password_visible
    }

    /// Performs the prepend operation used by this component.
    pub fn prepend(
        mut self,
        render: impl Fn(&mut Window, &mut App) -> AnyElement + 'static,
    ) -> Self {
        self.prepend = Some(Box::new(render));
        self
    }

    /// Sets the prepend text value used by the component.
    pub fn prepend_text(self, text: impl Into<SharedString>) -> Self {
        let text = text.into();
        self.prepend(move |_, _| {
            gpui::div()
                .flex()
                .items_center()
                .px_3()
                .child(text.clone())
                .into_any_element()
        })
    }

    /// Sets the prepend icon rendered by the component.
    pub fn prepend_icon(self, icon: IconName) -> Self {
        self.prepend(move |_, _| {
            gpui::div()
                .flex()
                .items_center()
                .justify_center()
                .px_3()
                .child(Icon::new(icon).size(px(14.0)))
                .into_any_element()
        })
    }

    /// Performs the append operation used by this component.
    pub fn append(
        mut self,
        render: impl Fn(&mut Window, &mut App) -> AnyElement + 'static,
    ) -> Self {
        self.append = Some(Box::new(render));
        self
    }

    /// Sets the append text value used by the component.
    pub fn append_text(self, text: impl Into<SharedString>) -> Self {
        let text = text.into();
        self.append(move |_, _| {
            gpui::div()
                .flex()
                .items_center()
                .px_3()
                .child(text.clone())
                .into_any_element()
        })
    }
}

impl Focusable for Input {
    fn focus_handle(&self, _cx: &App) -> FocusHandle {
        self.focus_handle.clone()
    }
}

impl EntityInputHandler for Input {
    fn text_for_range(
        &mut self,
        range_utf16: Range<usize>,
        _: &mut Option<Range<usize>>,
        _: &mut Window,
        _: &mut Context<Self>,
    ) -> Option<String> {
        let start = self.offset_from_utf16(range_utf16.start);
        let end = self.offset_from_utf16(range_utf16.end);
        if start <= self.value.len() && end <= self.value.len() {
            Some(self.value[start..end].to_string())
        } else {
            None
        }
    }

    fn selected_text_range(
        &mut self,
        _: bool,
        _: &mut Window,
        _: &mut Context<Self>,
    ) -> Option<UTF16Selection> {
        Some(UTF16Selection {
            range: self.offset_to_utf16(self.selected_range.start)
                ..self.offset_to_utf16(self.selected_range.end),
            reversed: self.selection_reversed,
        })
    }

    fn marked_text_range(&self, _: &mut Window, _: &mut Context<Self>) -> Option<Range<usize>> {
        self.marked_range
            .as_ref()
            .map(|r| self.offset_to_utf16(r.start)..self.offset_to_utf16(r.end))
    }

    fn unmark_text(&mut self, _: &mut Window, _: &mut Context<Self>) {
        self.marked_range = None;
    }

    fn replace_text_in_range(
        &mut self,
        range_utf16: Option<Range<usize>>,
        new_text: &str,
        _: &mut Window,
        cx: &mut Context<Self>,
    ) {
        let range = normalize_replace_range(
            self.value.as_ref(),
            range_utf16
                .map(|r| self.offset_from_utf16(r.start)..self.offset_from_utf16(r.end))
                .or_else(|| self.marked_range.clone())
                .unwrap_or(self.selected_range.clone()),
        );
        let potential_v = {
            let mut temp = self.value.to_string();
            temp.replace_range(range.clone(), new_text);
            temp
        };
        if let Some(ref filter) = self.filter {
            if !filter(&potential_v) {
                return;
            }
        }
        if let Some(max) = self.max_length {
            if potential_v.chars().count() > max {
                return;
            }
        }
        let mut v = self.value.to_string();
        v.replace_range(range.clone(), new_text);
        self.value = SharedString::from(v);
        self.selected_range = range.start + new_text.len()..range.start + new_text.len();
        self.marked_range = None;
        self.emit_change(cx);
        cx.notify();
    }

    fn replace_and_mark_text_in_range(
        &mut self,
        range_utf16: Option<Range<usize>>,
        new_text: &str,
        new_selected: Option<Range<usize>>,
        _: &mut Window,
        cx: &mut Context<Self>,
    ) {
        let range = normalize_replace_range(
            self.value.as_ref(),
            range_utf16
                .map(|r| self.offset_from_utf16(r.start)..self.offset_from_utf16(r.end))
                .or(self.marked_range.clone())
                .unwrap_or(self.selected_range.clone()),
        );
        let potential_v = {
            let mut temp = self.value.to_string();
            temp.replace_range(range.clone(), new_text);
            temp
        };
        if let Some(ref filter) = self.filter {
            if !filter(&potential_v) {
                return;
            }
        }
        if let Some(max) = self.max_length {
            if potential_v.chars().count() > max {
                return;
            }
        }
        let mut v = self.value.to_string();
        v.replace_range(range.clone(), new_text);
        self.value = SharedString::from(v);
        if !new_text.is_empty() {
            self.marked_range = Some(range.start..range.start + new_text.len());
        } else {
            self.marked_range = None;
        }
        if let Some(sel) = new_selected {
            self.selected_range = normalize_replace_range(
                self.value.as_ref(),
                range.start + sel.start..range.start + sel.end,
            );
        } else {
            self.selected_range = range.start + new_text.len()..range.start + new_text.len();
        }
        self.emit_change(cx);
        cx.notify();
    }

    fn bounds_for_range(
        &mut self,
        range_utf16: Range<usize>,
        bounds: Bounds<Pixels>,
        window: &mut Window,
        _: &mut Context<Self>,
    ) -> Option<Bounds<Pixels>> {
        let layouts = &self.last_line_layouts;
        if layouts.is_empty() {
            return None;
        }
        let start = self.offset_from_utf16(range_utf16.start);
        let end = self.offset_from_utf16(range_utf16.end);
        let line_height = window.line_height();
        let mut original_byte_offset = 0;
        for (idx, (layout, y_offset)) in layouts.iter().enumerate() {
            let line_text = self.value.split('\n').nth(idx).unwrap_or("");
            let line_len = line_text.len();
            if start >= original_byte_offset && start <= original_byte_offset + line_len {
                let x_start = layout.x_for_index(
                    self.safe_display_offset_in_line(start - original_byte_offset, line_text),
                );
                let x_end = layout.x_for_index(self.safe_display_offset_in_line(
                    end.min(original_byte_offset + line_len) - original_byte_offset,
                    line_text,
                ));
                return Some(Bounds::from_corners(
                    point(bounds.left() + x_start, *y_offset),
                    point(bounds.left() + x_end, *y_offset + line_height),
                ));
            }
            original_byte_offset += line_len + 1;
        }
        None
    }

    fn character_index_for_point(
        &mut self,
        pt: Point<Pixels>,
        window: &mut Window,
        _: &mut Context<Self>,
    ) -> Option<usize> {
        Some(self.offset_to_utf16(self.index_for_point(pt, window)))
    }
}

impl Input {
    fn offset_to_utf16(&self, offset: usize) -> usize {
        if self.value.is_empty() {
            return 0;
        }
        self.value[..offset.min(self.value.len())]
            .chars()
            .map(|c| c.len_utf16())
            .sum()
    }
    fn offset_from_utf16(&self, target: usize) -> usize {
        let mut utf8 = 0;
        let mut utf16 = 0;
        for c in self.value.chars() {
            if utf16 >= target {
                break;
            }
            utf16 += c.len_utf16();
            utf8 += c.len_utf8();
        }
        utf8
    }
    fn text_for_display(&self, cx: &impl liora_core::LocalesContext) -> SharedString {
        if self.value.is_empty() {
            self.placeholder.resolve(cx)
        } else if self.is_password() {
            let masked = self
                .value
                .chars()
                .map(|c| if c == '\n' { '\n' } else { self.mask_char })
                .collect::<String>();
            SharedString::from(masked)
        } else {
            self.value.clone()
        }
    }
    fn safe_display_offset_in_line(&self, line_offset: usize, line_text: &str) -> usize {
        if !self.last_layout_is_masked {
            return line_offset;
        }
        let mut count = 0;
        let mut bytes = 0;
        for c in line_text.chars() {
            if bytes >= line_offset {
                break;
            }
            bytes += c.len_utf8();
            count += 1;
        }
        count * self.mask_char.len_utf8()
    }
}

fn line_start_at(value: &str, offset: usize) -> usize {
    value[..offset.min(value.len())]
        .rfind('\n')
        .map_or(0, |index| index + 1)
}

fn selected_line_end(value: &str, selection: Range<usize>) -> usize {
    if value.is_empty() {
        return 0;
    }
    let mut end = selection.end.min(value.len());
    if end > selection.start && end > 0 && value.as_bytes().get(end - 1) == Some(&b'\n') {
        end -= 1;
    }
    value[end..]
        .find('\n')
        .map_or(value.len(), |relative| end + relative + 1)
}

fn removable_indent_len(line: &str, indent: &str) -> Option<usize> {
    if line.starts_with(indent) {
        return Some(indent.len());
    }
    if indent.chars().all(|ch| ch == ' ') {
        let max_spaces = indent.len();
        let spaces = line
            .as_bytes()
            .iter()
            .take_while(|byte| **byte == b' ')
            .take(max_spaces)
            .count();
        if spaces > 0 {
            return Some(spaces);
        }
    }
    if indent == "\t" && line.starts_with('\t') {
        return Some(1);
    }
    None
}

fn apply_signed_delta(value: usize, delta: isize) -> usize {
    if delta.is_negative() {
        value.saturating_sub(delta.unsigned_abs())
    } else {
        value.saturating_add(delta as usize)
    }
}

struct InputElement {
    input: Entity<Input>,
    disabled: bool,
}

struct InputPrepaint {
    lines: Vec<(ShapedLine, Pixels)>,
    cursor: Option<gpui::PaintQuad>,
    selection: Vec<gpui::PaintQuad>,
    is_masked: bool,
}

impl IntoElement for InputElement {
    type Element = Self;
    fn into_element(self) -> Self::Element {
        self
    }
}

impl Element for InputElement {
    type RequestLayoutState = ();
    type PrepaintState = InputPrepaint;
    fn id(&self) -> Option<ElementId> {
        None
    }
    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
        None
    }
    fn request_layout(
        &mut self,
        _: Option<&GlobalElementId>,
        _: Option<&InspectorElementId>,
        window: &mut Window,
        cx: &mut App,
    ) -> (LayoutId, ()) {
        let input = self.input.read(cx);
        let line_count = input
            .text_for_display(cx)
            .split('\n')
            .count()
            .max(input.min_rows) as f32;
        let mut style = Style::default();
        style.size.width = gpui::relative(1.).into();
        style.size.height = (window.line_height() * line_count).into();
        (window.request_layout(style, [], cx), ())
    }

    fn prepaint(
        &mut self,
        _: Option<&GlobalElementId>,
        _: Option<&InspectorElementId>,
        bounds: Bounds<Pixels>,
        _: &mut (),
        window: &mut Window,
        cx: &mut App,
    ) -> InputPrepaint {
        let input = self.input.read(cx);
        let mut style = window.text_style();
        let ui_weight = ui_font_weight(cx);
        if let Some(family) = ui_font_family(cx) {
            style.font_family = family;
        }
        if let Some(weight) = ui_weight {
            style.font_weight = weight;
        }
        let theme = &cx.global::<Config>().theme;
        let text_c = if self.disabled {
            theme.neutral.text_disabled
        } else {
            theme.neutral.text_1
        };
        let font_size = style.font_size.to_pixels(window.rem_size());
        let line_height = window.line_height();
        let cursor_offset = input.cursor_offset();
        let text = input.text_for_display(cx);
        let is_masked = input.is_password();
        let text_align = input.text_align;
        let text_lines: Vec<String> = text.split('\n').map(str::to_owned).collect();

        let original_cursor_line = if input.value.is_empty() {
            0
        } else {
            let mut line = 0;
            let mut start = 0;
            for l in input.value.split('\n') {
                if cursor_offset >= start && cursor_offset <= start + l.len() {
                    break;
                }
                start += l.len() + 1;
                line += 1;
            }
            line
        };

        let mut lines = Vec::new();
        let mut y = bounds.top();
        let mut cursor_quad = None;
        let mut selection_quads = Vec::new();
        let mut original_byte_offset = 0;

        for (i, line_text) in text_lines.iter().enumerate() {
            let (display, color) = if input.value.is_empty() {
                (input.placeholder.resolve(cx), theme.neutral.placeholder)
            } else {
                (SharedString::from(line_text.clone()), text_c)
            };
            let run = TextRun {
                len: display.len(),
                font: style.font(),
                color,
                background_color: None,
                underline: None,
                strikethrough: None,
            };
            let shaped = window
                .text_system()
                .shape_line(display, font_size, &[run], None);

            let x_offset = match text_align {
                gpui::TextAlign::Left => px(0.0),
                gpui::TextAlign::Center => (bounds.size.width - shaped.width) / 2.0,
                gpui::TextAlign::Right => bounds.size.width - shaped.width,
            };

            if !input.selected_range.is_empty() && !input.value.is_empty() {
                let range = input.selected_range.clone();
                let original_line = input.value.split('\n').nth(i).unwrap_or("");
                let line_start = original_byte_offset;
                let line_end = original_byte_offset + original_line.len();
                let start = range.start.max(line_start);
                let end = range.end.min(line_end);
                if start < end {
                    let d_start =
                        input.safe_display_offset_in_line(start - line_start, original_line);
                    let d_end = input.safe_display_offset_in_line(end - line_start, original_line);
                    let x_start = shaped.x_for_index(d_start);
                    let x_end = shaped.x_for_index(d_end);
                    selection_quads.push(fill(
                        Bounds::new(
                            point(bounds.left() + x_offset + x_start, y),
                            size(x_end - x_start, line_height),
                        ),
                        theme.primary.base.opacity(0.3),
                    ));
                }
            }
            if i == original_cursor_line
                && input.selected_range.is_empty()
                && input.cursor_visible
                && !input.value.is_empty()
            {
                let original_line = input.value.split('\n').nth(i).unwrap_or("");
                let line_start = original_byte_offset;
                let col = cursor_offset - line_start;
                let d_col = if is_masked {
                    let mut count = 0;
                    let mut bytes = 0;
                    for c in original_line.chars() {
                        if bytes >= col {
                            break;
                        }
                        bytes += c.len_utf8();
                        count += 1;
                    }
                    count * input.mask_char.len_utf8()
                } else {
                    col
                };

                let x = shaped.x_for_index(d_col);
                let ch = font_size.add(px(6.0));
                let ct = y + (line_height - ch) / 2.0;
                cursor_quad = Some(fill(
                    Bounds::new(point(bounds.left() + x_offset + x, ct), size(px(2.), ch)),
                    theme.primary.base,
                ));
            } else if i == 0 && input.value.is_empty() && input.cursor_visible {
                let x = match text_align {
                    gpui::TextAlign::Left => px(0.0),
                    gpui::TextAlign::Center => (bounds.size.width - shaped.width) / 2.0,
                    gpui::TextAlign::Right => bounds.size.width - shaped.width,
                };
                let ch = font_size.add(px(6.0));
                let ct = y + (line_height - ch) / 2.0;
                cursor_quad = Some(fill(
                    Bounds::new(point(bounds.left() + x, ct), size(px(2.), ch)),
                    theme.primary.base,
                ));
            }

            lines.push((shaped, y));
            y = y + line_height;
            original_byte_offset += input
                .value
                .split('\n')
                .nth(i)
                .map(|l| l.len() + 1)
                .unwrap_or(0);
        }
        InputPrepaint {
            lines,
            cursor: cursor_quad,
            selection: selection_quads,
            is_masked,
        }
    }

    fn paint(
        &mut self,
        _: Option<&GlobalElementId>,
        _: Option<&InspectorElementId>,
        bounds: Bounds<Pixels>,
        _: &mut (),
        prepaint: &mut InputPrepaint,
        window: &mut Window,
        cx: &mut App,
    ) {
        let focus_handle = self.input.read(cx).focus_handle.clone();
        window.handle_input(
            &focus_handle,
            ElementInputHandler::new(bounds, self.input.clone()),
            cx,
        );
        for s in prepaint.selection.drain(..) {
            window.paint_quad(s);
        }
        for (line, y) in &prepaint.lines {
            let _ = line.paint(
                point(bounds.left(), *y),
                window.line_height(),
                gpui::TextAlign::Left,
                None,
                window,
                cx,
            );
        }
        if focus_handle.is_focused(window) {
            if let Some(c) = prepaint.cursor.take() {
                window.paint_quad(c);
            }
        }
        let line_layouts = prepaint.lines.clone();
        let layout_text = line_layouts
            .iter()
            .map(|(line, _)| line.text.as_ref())
            .collect::<Vec<_>>()
            .join("\n");
        let layout_text = SharedString::from(layout_text);
        let is_masked = prepaint.is_masked;
        let should_update_layout_cache = {
            let input = self.input.read(cx);
            input.last_bounds != Some(bounds)
                || input.last_layout_is_masked != is_masked
                || input.last_layout_text != layout_text
                || input.last_line_layouts.len() != line_layouts.len()
                || input
                    .last_line_layouts
                    .iter()
                    .zip(line_layouts.iter())
                    .any(|((_, previous_y), (_, next_y))| previous_y != next_y)
        };
        if should_update_layout_cache {
            self.input.update(cx, |input, _| {
                input.last_line_layouts = line_layouts;
                input.last_layout_text = layout_text;
                input.last_bounds = Some(bounds);
                input.last_layout_is_masked = is_masked;
            });
        }
    }
}

impl Render for Input {
    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        let focused = self.focus_handle(cx).is_focused(window);
        if focused && self.blink_task.is_none() {
            self.start_blink(cx);
        } else if !focused && self.blink_task.is_some() {
            self.blink_task = None;
        }

        let theme = cx.global::<Config>().theme.clone();
        let icon_sz = 16.0;
        let (bg, border_c) = if self.disabled {
            (theme.neutral.hover, theme.neutral.border)
        } else if focused {
            (theme.neutral.card, theme.primary.base)
        } else {
            (theme.neutral.card, theme.neutral.border)
        };
        let fh = self.focus_handle(cx);
        let line_height = window.line_height();

        let mut row = gpui::div()
            .flex()
            .flex_row()
            .when_some(self.width, |s, w| s.w(w))
            .when_some(self.height, |s, h| s.h(h))
            .when(self.height.is_none(), |s| {
                if self.min_rows > 1 {
                    s.h_auto()
                        .min_h(line_height * self.min_rows as f32 + px(16.0))
                } else {
                    s.min_h(px(34.0))
                }
            })
            .rounded(px(theme.radius.md))
            .bg(bg)
            .border_1()
            .border_color(border_c)
            .text_size(px(theme.font_size.md))
            .text_color(if self.disabled {
                theme.neutral.text_disabled
            } else {
                theme.neutral.text_1
            })
            .overflow_hidden();

        if self.min_rows > 1 {
            row = row.items_start();
        } else {
            row = row.items_center();
        }

        if !self.disabled {
            row = row.track_focus(&fh).cursor_text();
        } else {
            row = row.cursor_not_allowed();
        }

        if !self.disabled {
            row = row
                .on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down))
                .on_mouse_move(cx.listener(Self::on_mouse_move))
                .on_action(cx.listener(Self::backspace))
                .on_action(cx.listener(Self::delete))
                .on_action(cx.listener(Self::left))
                .on_action(cx.listener(Self::select_left))
                .on_action(cx.listener(Self::right))
                .on_action(cx.listener(Self::select_right))
                .on_action(cx.listener(Self::home))
                .on_action(cx.listener(Self::select_home))
                .on_action(cx.listener(Self::end))
                .on_action(cx.listener(Self::select_end))
                .on_action(cx.listener(Self::select_all))
                .on_action(cx.listener(Self::copy))
                .on_action(cx.listener(Self::paste))
                .on_action(cx.listener(Self::cut))
                .on_action(cx.listener(Self::enter))
                .on_action(cx.listener(Self::up))
                .on_action(cx.listener(Self::select_up))
                .on_action(cx.listener(Self::down))
                .on_action(cx.listener(Self::select_down));
        }

        if let Some(ref p_render) = self.prepend {
            row = row.child(
                gpui::div()
                    .flex_none()
                    .h_full()
                    .bg(theme.neutral.hover)
                    .border_r_1()
                    .border_color(theme.neutral.border)
                    .flex()
                    .items_center()
                    .justify_center()
                    .text_color(theme.neutral.text_3)
                    .child(p_render(window, cx)),
            );
        }

        let mut inner = gpui::div().flex_1().flex().flex_row().gap_2().px(px(12.0));
        if self.min_rows > 1 {
            inner = inner.items_start().py_2();
        } else {
            inner = inner.items_center();
        }

        if let Some(icon) = self.icon_prefix {
            inner = inner.child(Icon::new(icon).size(px(icon_sz)).color(theme.neutral.icon));
        }

        inner = inner.child(InputElement {
            input: cx.entity().clone(),
            disabled: self.disabled,
        });

        if self.clearable && !self.value.is_empty() && !self.disabled {
            inner = inner.child(
                gpui::div()
                    .flex_none()
                    .cursor_pointer()
                    .hover(|s| s.cursor_pointer())
                    .child(
                        Icon::new(IconName::X)
                            .size(px(14.0))
                            .color(theme.neutral.icon),
                    )
                    .on_mouse_down(
                        MouseButton::Left,
                        cx.listener(
                            move |this: &mut Self,
                                  _: &MouseDownEvent,
                                  _: &mut Window,
                                  cx: &mut Context<Self>| {
                                this.clear(cx);
                                cx.stop_propagation();
                            },
                        ),
                    ),
            );
        }

        if self.input_type == InputType::Password && !self.disabled {
            let visible = self.password_visible;
            inner = inner.child(
                gpui::div()
                    .cursor_pointer()
                    .flex_none()
                    .child(
                        Icon::new(if visible {
                            IconName::EyeOff
                        } else {
                            IconName::Eye
                        })
                        .size(px(14.0))
                        .color(theme.neutral.icon),
                    )
                    .on_mouse_down(
                        MouseButton::Left,
                        cx.listener(
                            move |this: &mut Self,
                                  _: &MouseDownEvent,
                                  window: &mut Window,
                                  cx: &mut Context<Self>| {
                                this.toggle_password(&TogglePassword, window, cx);
                            },
                        ),
                    ),
            );
        }

        if let Some(icon) = self.icon_suffix {
            inner = inner.child(Icon::new(icon).size(px(icon_sz)).color(theme.neutral.icon));
        }

        row = row.child(inner);

        if let Some(ref a_render) = self.append {
            row = row.child(
                gpui::div()
                    .flex_none()
                    .h_full()
                    .bg(theme.neutral.hover)
                    .border_l_1()
                    .border_color(theme.neutral.border)
                    .flex()
                    .items_center()
                    .justify_center()
                    .text_color(theme.neutral.text_3)
                    .child(a_render(window, cx)),
            );
        }

        row
    }
}

#[cfg(test)]
mod width_tests {

    #[test]
    fn platform_input_ranges_are_clamped_before_string_replacement() {
        let source = include_str!("input.rs")
            .split("#[cfg(test)]")
            .next()
            .unwrap();

        assert_eq!(super::normalize_replace_range("", 0..26), 0..0);
        assert_eq!(super::normalize_replace_range("abc", 1..99), 1..3);
        assert_eq!(super::normalize_replace_range("éx", 1..99), 0..3);
        assert_eq!(super::normalize_replace_range("abc", 3..1), 1..3);
        assert!(source.contains("fn normalize_replace_range"));
        assert!(source.contains(
            "let range = normalize_replace_range(
            self.value.as_ref(),
            range_utf16"
        ));
    }

    #[test]
    fn input_backspace_does_not_spawn_a_new_blink_task_per_keypress() {
        let source = include_str!("input.rs")
            .split("#[cfg(test)]")
            .next()
            .unwrap();

        assert!(source.contains("if self.blink_task.is_none()"));
        assert!(source.contains("self.start_blink(cx);"));
        assert!(source.contains("self.selected_range = previous..cursor"));
        assert!(!source.contains("self.select_to(p, cx);"));
    }

    #[test]
    fn prepend_and_append_shells_fill_input_height() {
        let source = include_str!("input.rs")
            .split("#[cfg(test)]")
            .next()
            .unwrap();
        let prepend_region = source
            .split("if let Some(ref p_render) = self.prepend")
            .nth(1)
            .and_then(|region| region.split("let mut inner =").next())
            .expect("prepend render region should exist");
        let append_region = source
            .split("if let Some(ref a_render) = self.append")
            .nth(1)
            .and_then(|region| {
                region
                    .split(
                        "row
    }",
                    )
                    .next()
            })
            .expect("append render region should exist");

        assert!(prepend_region.contains(".h_full()"));
        assert!(append_region.contains(".h_full()"));
        assert!(!prepend_region.contains(".items_start()"));
        assert!(!append_region.contains(".items_start()"));
    }

    #[test]
    fn input_paint_layout_cache_does_not_notify_every_frame() {
        let source = include_str!("input.rs")
            .split("#[cfg(test)]")
            .next()
            .unwrap();

        assert!(source.contains("should_update_layout_cache"));
        assert!(source.contains("last_layout_text"));
        assert!(source.contains("if should_update_layout_cache"));
        assert!(!source.contains(
            "self.input.update(cx, |input, _| {
            input.last_line_layouts = line_layouts;"
        ));
    }

    #[test]
    fn input_text_uses_theme_foreground_instead_of_window_default() {
        let source = include_str!("input.rs")
            .split("#[cfg(test)]")
            .next()
            .unwrap();

        assert!(source.contains("theme.neutral.text_1"));
        assert!(source.contains("theme.neutral.placeholder"));
        assert!(!source.contains("} else {\n            style.color\n        }"));
    }

    #[test]
    fn input_width_sm_sets_compact_width() {
        let source = include_str!("input.rs")
            .split("#[cfg(test)]")
            .next()
            .unwrap();

        assert!(source.contains("width: Option<Pixels>"));
        assert!(source.contains("pub fn width_sm(self) -> Self"));
        assert!(source.contains(".when_some(self.width, |s, w| s.w(w))"));
    }

    #[test]
    fn input_text_addons_are_available_for_self_bootstrapped_demos() {
        let source = include_str!("input.rs")
            .split("#[cfg(test)]")
            .next()
            .unwrap();

        assert!(source.contains("pub fn prepend_text"));
        assert!(source.contains("pub fn append_text"));
        assert!(source.contains("pub fn prepend_icon"));
    }

    #[test]
    fn input_exposes_code_editor_indentation_hooks() {
        let source = include_str!("input.rs")
            .split("#[cfg(test)]")
            .next()
            .unwrap();

        assert!(source.contains("pub fn indent_selection"));
        assert!(source.contains("pub fn outdent_selection"));
        assert!(source.contains("fn reindent_selected_lines"));
    }

    #[test]
    fn removable_indent_supports_partial_soft_tabs() {
        assert_eq!(
            super::removable_indent_len("    let x = 1;", "    "),
            Some(4)
        );
        assert_eq!(super::removable_indent_len("  let x = 1;", "    "), Some(2));
        assert_eq!(super::removable_indent_len("\tlet x = 1;", "\t"), Some(1));
        assert_eq!(super::removable_indent_len("let x = 1;", "    "), None);
    }
}
