// Copyright 2019 H�ctor Ram�n, Iced contributors
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MIT

//! Display fields that can be filled with text.
//!
//! A [`TextInput`] has some local [`State`].
use std::borrow::Cow;
use std::cell::{Cell, LazyCell};

use crate::ext::ColorExt;
use crate::theme::THEME;

use super::cursor;
pub use super::cursor::Cursor;
use super::editor::Editor;
use super::style::StyleSheet;
pub use super::value::Value;

use apply::Apply;
use cosmic_theme::Theme;
use iced::clipboard::dnd::{DndAction, DndEvent, OfferEvent, SourceEvent};
use iced::clipboard::mime::AsMimeTypes;
use iced::Limits;
use iced_core::event::{self, Event};
use iced_core::mouse::{self, click};
use iced_core::overlay::Group;
use iced_core::renderer::{self, Renderer as CoreRenderer};
use iced_core::text::{self, Paragraph, Renderer, Text};
use iced_core::time::{Duration, Instant};
use iced_core::touch;
use iced_core::widget::operation::{self, Operation};
use iced_core::widget::tree::{self, Tree};
use iced_core::widget::Id;
use iced_core::window;
use iced_core::{alignment, Background};
use iced_core::{keyboard, Border, Shadow};
use iced_core::{layout, overlay};
use iced_core::{
    Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size,
    Vector, Widget,
};
#[cfg(feature = "wayland")]
use iced_renderer::core::event::{wayland, PlatformSpecific};
#[cfg(feature = "wayland")]
use iced_runtime::platform_specific;
use iced_runtime::{task, Action, Task};

thread_local! {
    // Prevents two inputs from being focused at the same time.
    static LAST_FOCUS_UPDATE: LazyCell<Cell<Instant>> = LazyCell::new(|| Cell::new(Instant::now()));
}

/// Creates a new [`TextInput`].
///
/// [`TextInput`]: widget::TextInput
pub fn text_input<'a, Message>(
    placeholder: impl Into<Cow<'a, str>>,
    value: impl Into<Cow<'a, str>>,
) -> TextInput<'a, Message>
where
    Message: Clone + 'static,
{
    TextInput::new(placeholder, value)
}

/// A text label whiich can transform into a text input on activation.
pub fn editable_input<'a, Message: Clone + 'static>(
    placeholder: impl Into<Cow<'a, str>>,
    text: impl Into<Cow<'a, str>>,
    editing: bool,
    on_toggle_edit: impl Fn(bool) -> Message + 'a,
) -> TextInput<'a, Message> {
    let icon = crate::widget::icon::from_name(if editing {
        "edit-clear-symbolic"
    } else {
        "edit-symbolic"
    });

    TextInput::new(placeholder, text)
        .style(crate::theme::TextInput::EditableText)
        .editable()
        .editing(editing)
        .on_toggle_edit(on_toggle_edit)
        .trailing_icon(icon.size(16).into())
}

/// Creates a new search [`TextInput`].
///
/// [`TextInput`]: widget::TextInput
pub fn search_input<'a, Message>(
    placeholder: impl Into<Cow<'a, str>>,
    value: impl Into<Cow<'a, str>>,
) -> TextInput<'a, Message>
where
    Message: Clone + 'static,
{
    let spacing = THEME.lock().unwrap().cosmic().space_xxs();

    TextInput::new(placeholder, value)
        .padding([0, spacing])
        .style(crate::theme::TextInput::Search)
        .leading_icon(
            crate::widget::icon::from_name("system-search-symbolic")
                .size(16)
                .apply(crate::widget::container)
                .padding(8)
                .into(),
        )
}
/// Creates a new secure [`TextInput`].
///
/// [`TextInput`]: widget::TextInput
pub fn secure_input<'a, Message>(
    placeholder: impl Into<Cow<'a, str>>,
    value: impl Into<Cow<'a, str>>,
    on_visible_toggle: Option<Message>,
    hidden: bool,
) -> TextInput<'a, Message>
where
    Message: Clone + 'static,
{
    let spacing = THEME.lock().unwrap().cosmic().space_xxs();
    let mut input = TextInput::new(placeholder, value)
        .padding([0, spacing])
        .style(crate::theme::TextInput::Default)
        .leading_icon(
            crate::widget::icon::from_name("system-lock-screen-symbolic")
                .size(16)
                .apply(crate::widget::container)
                .padding(8)
                .into(),
        );
    if hidden {
        input = input.password();
    }
    if let Some(msg) = on_visible_toggle {
        input.trailing_icon(
            crate::widget::icon::from_name(if hidden {
                "document-properties-symbolic"
            } else {
                "image-red-eye-symbolic"
            })
            .size(16)
            .apply(crate::widget::button::custom)
            .class(crate::theme::Button::Icon)
            .on_press(msg)
            .padding(8)
            .into(),
        )
    } else {
        input
    }
}

/// Creates a new inline [`TextInput`].
///
/// [`TextInput`]: widget::TextInput
pub fn inline_input<'a, Message>(
    placeholder: impl Into<Cow<'a, str>>,
    value: impl Into<Cow<'a, str>>,
) -> TextInput<'a, Message>
where
    Message: Clone + 'static,
{
    let spacing = THEME.lock().unwrap().cosmic().space_xxs();

    TextInput::new(placeholder, value)
        .style(crate::theme::TextInput::Inline)
        .padding(spacing)
}

pub(crate) const SUPPORTED_TEXT_MIME_TYPES: &[&str; 6] = &[
    "text/plain;charset=utf-8",
    "text/plain;charset=UTF-8",
    "UTF8_STRING",
    "STRING",
    "text/plain",
    "TEXT",
];

/// A field that can be filled with text.
#[allow(missing_debug_implementations)]
#[must_use]
pub struct TextInput<'a, Message> {
    id: Id,
    placeholder: Cow<'a, str>,
    value: Value,
    is_secure: bool,
    is_editable: bool,
    is_read_only: bool,
    select_on_focus: bool,
    font: Option<<crate::Renderer as iced_core::text::Renderer>::Font>,
    width: Length,
    padding: Padding,
    size: Option<f32>,
    helper_size: f32,
    label: Option<Cow<'a, str>>,
    helper_text: Option<Cow<'a, str>>,
    error: Option<Cow<'a, str>>,
    on_input: Option<Box<dyn Fn(String) -> Message + 'a>>,
    on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
    on_submit: Option<Message>,
    on_toggle_edit: Option<Box<dyn Fn(bool) -> Message + 'a>>,
    leading_icon: Option<Element<'a, Message, crate::Theme, crate::Renderer>>,
    trailing_icon: Option<Element<'a, Message, crate::Theme, crate::Renderer>>,
    style: <crate::Theme as StyleSheet>::Style,
    on_create_dnd_source: Option<Box<dyn Fn(State) -> Message + 'a>>,
    surface_ids: Option<(window::Id, window::Id)>,
    dnd_icon: bool,
    line_height: text::LineHeight,
    helper_line_height: text::LineHeight,
    always_active: bool,
}

impl<'a, Message> TextInput<'a, Message>
where
    Message: Clone + 'static,
{
    /// Creates a new [`TextInput`].
    ///
    /// It expects:
    /// - a placeholder,
    /// - the current value
    pub fn new(placeholder: impl Into<Cow<'a, str>>, value: impl Into<Cow<'a, str>>) -> Self {
        let spacing = THEME.lock().unwrap().cosmic().space_xxs();

        let v: Cow<'a, str> = value.into();
        TextInput {
            id: Id::unique(),
            placeholder: placeholder.into(),
            value: Value::new(v.as_ref()),
            is_secure: false,
            is_editable: false,
            is_read_only: false,
            select_on_focus: false,
            font: None,
            width: Length::Fill,
            padding: spacing.into(),
            size: None,
            helper_size: 10.0,
            helper_line_height: text::LineHeight::Absolute(14.0.into()),
            on_input: None,
            on_paste: None,
            on_submit: None,
            on_toggle_edit: None,
            leading_icon: None,
            trailing_icon: None,
            error: None,
            style: crate::theme::TextInput::default(),
            on_create_dnd_source: None,
            surface_ids: None,
            dnd_icon: false,
            line_height: text::LineHeight::default(),
            label: None,
            helper_text: None,
            always_active: false,
        }
    }

    fn dnd_id(&self) -> u128 {
        match &self.id.0 {
            iced_core::id::Internal::Custom(id, _) | iced_core::id::Internal::Unique(id) => {
                *id as u128
            }
            _ => unreachable!(),
        }
    }

    /// Sets the input to be always active.
    /// This makes it behave as if it was always focused.
    pub fn always_active(mut self) -> Self {
        self.always_active = true;
        self
    }

    /// Sets the text of the [`TextInput`].
    pub fn label(mut self, label: impl Into<Cow<'a, str>>) -> Self {
        self.label = Some(label.into());
        self
    }

    /// Sets the helper text of the [`TextInput`].
    pub fn helper_text(mut self, helper_text: impl Into<Cow<'a, str>>) -> Self {
        self.helper_text = Some(helper_text.into());
        self
    }

    /// Sets the [`Id`] of the [`TextInput`].
    pub fn id(mut self, id: Id) -> Self {
        self.id = id;
        self
    }

    /// Sets the error message of the [`TextInput`].
    pub fn error(mut self, error: impl Into<Cow<'a, str>>) -> Self {
        self.error = Some(error.into());
        self
    }

    /// Sets the [`LineHeight`] of the [`TextInput`].
    pub fn line_height(mut self, line_height: impl Into<text::LineHeight>) -> Self {
        self.line_height = line_height.into();
        self
    }

    /// Converts the [`TextInput`] into a secure password input.
    pub fn password(mut self) -> Self {
        self.is_secure = true;
        self
    }

    pub fn editable(mut self) -> Self {
        self.is_editable = true;
        self
    }

    pub fn editing(mut self, enable: bool) -> Self {
        self.is_read_only = !enable;
        self
    }

    /// Selects all text when the text input is focused
    pub fn select_on_focus(mut self, select_on_focus: bool) -> Self {
        self.select_on_focus = select_on_focus;
        self
    }

    /// Sets the message that should be produced when some text is typed into
    /// the [`TextInput`].
    ///
    /// If this method is not called, the [`TextInput`] will be disabled.
    pub fn on_input<F>(mut self, callback: F) -> Self
    where
        F: 'a + Fn(String) -> Message,
    {
        self.on_input = Some(Box::new(callback));
        self
    }

    /// Sets the message that should be produced when the [`TextInput`] is
    /// focused and the enter key is pressed.
    pub fn on_submit(self, message: Message) -> Self {
        self.on_submit_maybe(Some(message))
    }

    /// Maybe sets the message that should be produced when the [`TextInput`] is
    /// focused and the enter key is pressed.
    pub fn on_submit_maybe(mut self, message: Option<Message>) -> Self {
        self.on_submit = message;
        self
    }

    pub fn on_toggle_edit<F>(mut self, callback: F) -> Self
    where
        F: 'a + Fn(bool) -> Message,
    {
        self.on_toggle_edit = Some(Box::new(callback));
        self
    }

    /// Sets the message that should be produced when some text is pasted into
    /// the [`TextInput`].
    pub fn on_paste(mut self, on_paste: impl Fn(String) -> Message + 'a) -> Self {
        self.on_paste = Some(Box::new(on_paste));
        self
    }

    /// Sets the [`Font`] of the [`TextInput`].
    ///
    /// [`Font`]: text::Renderer::Font
    pub fn font(mut self, font: <crate::Renderer as iced_core::text::Renderer>::Font) -> Self {
        self.font = Some(font);
        self
    }

    /// Sets the start [`Icon`] of the [`TextInput`].
    pub fn leading_icon(
        mut self,
        icon: Element<'a, Message, crate::Theme, crate::Renderer>,
    ) -> Self {
        self.leading_icon = Some(icon);
        self
    }

    /// Sets the end [`Icon`] of the [`TextInput`].
    pub fn trailing_icon(
        mut self,
        icon: Element<'a, Message, crate::Theme, crate::Renderer>,
    ) -> Self {
        self.trailing_icon = Some(icon);
        self
    }

    /// Sets the width of the [`TextInput`].
    pub fn width(mut self, width: impl Into<Length>) -> Self {
        self.width = width.into();
        self
    }

    /// Sets the [`Padding`] of the [`TextInput`].
    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
        self.padding = padding.into();
        self
    }

    /// Sets the text size of the [`TextInput`].
    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
        self.size = Some(size.into().0);
        self
    }

    /// Sets the style of the [`TextInput`].
    pub fn style(mut self, style: impl Into<<crate::Theme as StyleSheet>::Style>) -> Self {
        self.style = style.into();
        self
    }

    /// Draws the [`TextInput`] with the given [`Renderer`], overriding its
    /// [`Value`] if provided.
    ///
    /// [`Renderer`]: text::Renderer
    #[allow(clippy::too_many_arguments)]
    pub fn draw(
        &self,
        tree: &Tree,
        renderer: &mut crate::Renderer,
        theme: &crate::Theme,
        layout: Layout<'_>,
        cursor_position: mouse::Cursor,
        value: Option<&Value>,
        style: &renderer::Style,
    ) {
        let text_layout = self.text_layout(layout);
        draw(
            renderer,
            theme,
            layout,
            text_layout,
            cursor_position,
            tree,
            value.unwrap_or(&self.value),
            &self.placeholder,
            self.size,
            self.font,
            self.on_input.is_none(),
            self.is_secure,
            self.leading_icon.as_ref(),
            self.trailing_icon.as_ref(),
            &self.style,
            self.dnd_icon,
            self.line_height,
            self.error.as_deref(),
            self.label.as_deref(),
            self.helper_text.as_deref(),
            self.helper_size,
            self.helper_line_height,
            &layout.bounds(),
            style,
        );
    }

    /// Sets the start dnd handler of the [`TextInput`].
    #[cfg(feature = "wayland")]
    pub fn on_start_dnd(mut self, on_start_dnd: impl Fn(State) -> Message + 'a) -> Self {
        self.on_create_dnd_source = Some(Box::new(on_start_dnd));
        self
    }

    /// Sets the window id of the [`TextInput`] and the window id of the drag icon.
    /// Both ids are required to be unique.
    /// This is required for the dnd to work.
    pub fn surface_ids(mut self, window_id: (window::Id, window::Id)) -> Self {
        self.surface_ids = Some(window_id);
        self
    }

    /// Sets the mode of this [`TextInput`] to be a drag and drop icon.
    pub fn dnd_icon(mut self, dnd_icon: bool) -> Self {
        self.dnd_icon = dnd_icon;
        self
    }

    pub fn on_clear(self, on_clear: Message) -> Self {
        self.trailing_icon(
            crate::widget::icon::from_name("edit-clear-symbolic")
                .size(16)
                .apply(crate::widget::button::custom)
                .class(crate::theme::Button::Icon)
                .on_press(on_clear)
                .padding(8)
                .into(),
        )
    }

    /// Get the layout node of the actual text input
    fn text_layout<'b>(&'a self, layout: Layout<'b>) -> Layout<'b> {
        if self.dnd_icon {
            layout
        } else if self.label.is_some() {
            let mut nodes = layout.children();
            nodes.next();
            nodes.next().unwrap()
        } else {
            layout.children().next().unwrap()
        }
    }
}

impl<'a, Message> Widget<Message, crate::Theme, crate::Renderer> for TextInput<'a, Message>
where
    Message: Clone + 'static,
{
    fn tag(&self) -> tree::Tag {
        tree::Tag::of::<State>()
    }

    fn state(&self) -> tree::State {
        tree::State::new(State::new(
            self.is_secure,
            self.is_read_only,
            self.always_active,
            self.select_on_focus,
        ))
    }

    fn diff(&mut self, tree: &mut Tree) {
        let state = tree.state.downcast_mut::<State>();

        // Unfocus text input if it becomes disabled
        if self.on_input.is_none() {
            state.last_click = None;
            state.is_focused = None;
            state.is_pasting = None;
            state.dragging_state = None;
        }
        let old_value = state
            .value
            .raw()
            .buffer()
            .lines
            .iter()
            .map(|l| l.text())
            .collect::<String>();
        if state.is_secure != self.is_secure
            || old_value != self.value.to_string()
            || state
                .label
                .raw()
                .buffer()
                .lines
                .iter()
                .map(|l| l.text())
                .collect::<String>()
                != self.label.as_deref().unwrap_or_default()
            || state
                .helper_text
                .raw()
                .buffer()
                .lines
                .iter()
                .map(|l| l.text())
                .collect::<String>()
                != self.helper_text.as_deref().unwrap_or_default()
        {
            state.is_secure = self.is_secure;
            state.dirty = true;
        }

        self.is_read_only = state.is_read_only;

        if self.always_active && state.is_focused.is_none() {
            let now = Instant::now();
            LAST_FOCUS_UPDATE.with(|x| x.set(now));
            state.is_focused = Some(Focus {
                updated_at: now,
                now,
            });
        }

        // if the previous state was at the end of the text, keep it there
        let old_value = Value::new(&old_value);
        if state.is_focused.is_some() {
            match state.cursor.state(&old_value) {
                cursor::State::Index(index) => {
                    if index == old_value.len() {
                        state.cursor.move_to(self.value.len());
                    }
                }
                _ => {}
            };
        }

        if !state.is_focused.as_ref().map_or(false, |f| {
            f.updated_at == LAST_FOCUS_UPDATE.with(|f| f.get())
        }) {
            state.is_focused = None;
        }

        let mut children: Vec<_> = self
            .leading_icon
            .iter_mut()
            .chain(self.trailing_icon.iter_mut())
            .map(iced_core::Element::as_widget_mut)
            .collect();
        tree.diff_children(children.as_mut_slice());
    }

    fn children(&self) -> Vec<Tree> {
        self.leading_icon
            .iter()
            .chain(self.trailing_icon.iter())
            .map(|icon| Tree::new(icon))
            .collect()
    }

    fn size(&self) -> Size<Length> {
        Size {
            width: self.width,
            height: Length::Shrink,
        }
    }

    fn layout(
        &self,
        tree: &mut Tree,
        renderer: &crate::Renderer,
        limits: &layout::Limits,
    ) -> layout::Node {
        let font = self.font.unwrap_or_else(|| renderer.default_font());
        if self.dnd_icon {
            let state = tree.state.downcast_mut::<State>();
            let limits = limits.width(Length::Shrink).height(Length::Shrink);

            let size = self.size.unwrap_or_else(|| renderer.default_size().0);

            let bounds = limits.max();

            let value_paragraph = &mut state.value;
            let v = self.value.to_string();
            value_paragraph.update(Text {
                content: if self.value.is_empty() {
                    self.placeholder.as_ref()
                } else {
                    &v
                },
                font,
                bounds,
                size: iced::Pixels(size),
                horizontal_alignment: alignment::Horizontal::Left,
                vertical_alignment: alignment::Vertical::Center,
                line_height: text::LineHeight::default(),
                shaping: text::Shaping::Advanced,
                wrapping: text::Wrapping::default(),
            });

            let Size { width, height } =
                limits.resolve(Length::Shrink, Length::Shrink, value_paragraph.min_bounds());

            let size = limits.resolve(width, height, Size::new(width, height));
            layout::Node::with_children(size, vec![layout::Node::new(size)])
        } else {
            let res = layout(
                renderer,
                limits,
                self.width,
                self.padding,
                self.size,
                self.leading_icon.as_ref(),
                self.trailing_icon.as_ref(),
                self.line_height,
                self.label.as_deref(),
                self.helper_text.as_deref(),
                self.helper_size,
                self.helper_line_height,
                font,
                tree,
            );

            // XXX not ideal, but we need to update the cache when is_secure changes
            let size = self.size.unwrap_or_else(|| renderer.default_size().0);
            let line_height = self.line_height;
            let state = tree.state.downcast_mut::<State>();
            if state.dirty {
                state.dirty = false;
                let value = if self.is_secure {
                    self.value.secure()
                } else {
                    self.value.clone()
                };
                replace_paragraph(
                    state,
                    Layout::new(&res),
                    &value,
                    font,
                    iced::Pixels(size),
                    line_height,
                );
            }
            res
        }
    }

    fn operate(
        &self,
        tree: &mut Tree,
        _layout: Layout<'_>,
        _renderer: &crate::Renderer,
        operation: &mut dyn Operation<()>,
    ) {
        let state = tree.state.downcast_mut::<State>();

        operation.custom(state, Some(&self.id));
        operation.focusable(state, Some(&self.id));
        operation.text_input(state, Some(&self.id));
    }

    fn overlay<'b>(
        &'b mut self,
        tree: &'b mut Tree,
        layout: Layout<'_>,
        renderer: &crate::Renderer,
        translation: Vector,
    ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
        let mut layout_ = Vec::with_capacity(2);
        if self.leading_icon.is_some() {
            let mut children = self.text_layout(layout).children();
            children.next();
            layout_.push(children.next().unwrap());
        }
        if self.trailing_icon.is_some() {
            let mut children = self.text_layout(layout).children();
            children.next();
            if self.leading_icon.is_some() {
                children.next();
            }
            layout_.push(children.next().unwrap());
        };
        let children = self
            .leading_icon
            .iter_mut()
            .chain(self.trailing_icon.iter_mut())
            .zip(&mut tree.children)
            .zip(layout_)
            .filter_map(|((child, state), layout)| {
                child
                    .as_widget_mut()
                    .overlay(state, layout, renderer, translation)
            })
            .collect::<Vec<_>>();

        (!children.is_empty()).then(|| Group::with_children(children).overlay())
    }

    fn on_event(
        &mut self,
        tree: &mut Tree,
        event: Event,
        layout: Layout<'_>,
        cursor_position: mouse::Cursor,
        renderer: &crate::Renderer,
        clipboard: &mut dyn Clipboard,
        shell: &mut Shell<'_, Message>,
        viewport: &Rectangle,
    ) -> event::Status {
        let text_layout = self.text_layout(layout);
        let mut trailing_icon_layout = None;
        let font = self.font.unwrap_or_else(|| renderer.default_font());
        let size = self.size.unwrap_or_else(|| renderer.default_size().0);
        let line_height = self.line_height;

        if self.is_editable {
            if let Some(ref on_edit) = self.on_toggle_edit {
                let state = tree.state.downcast_mut::<State>();
                if !state.is_read_only && state.is_focused.is_none() {
                    state.is_read_only = true;
                    shell.publish((on_edit)(false));
                }
            }
        }

        if tree.children.len() > 0 {
            let index = tree.children.len() - 1;
            if let (Some(trailing_icon), Some(tree)) =
                (self.trailing_icon.as_mut(), tree.children.get_mut(index))
            {
                let children = text_layout.children();
                trailing_icon_layout = Some(children.last().unwrap());

                if let Some(trailing_layout) = trailing_icon_layout {
                    if cursor_position.is_over(trailing_layout.bounds()) {
                        let res = trailing_icon.as_widget_mut().on_event(
                            tree,
                            event.clone(),
                            trailing_layout,
                            cursor_position,
                            renderer,
                            clipboard,
                            shell,
                            viewport,
                        );

                        if res == event::Status::Captured {
                            return res;
                        }
                    }
                }
            }
        }
        let dnd_id = self.dnd_id();
        let id = Widget::id(self);
        update(
            id,
            event,
            text_layout.children().next().unwrap(),
            trailing_icon_layout,
            cursor_position,
            clipboard,
            shell,
            &mut self.value,
            size,
            font,
            self.is_secure,
            self.is_editable,
            self.on_input.as_deref(),
            self.on_paste.as_deref(),
            &self.on_submit,
            self.on_toggle_edit.as_deref(),
            || tree.state.downcast_mut::<State>(),
            self.on_create_dnd_source.as_deref(),
            dnd_id,
            line_height,
            layout,
        )
    }

    fn draw(
        &self,
        tree: &Tree,
        renderer: &mut crate::Renderer,
        theme: &crate::Theme,
        style: &renderer::Style,
        layout: Layout<'_>,
        cursor_position: mouse::Cursor,
        viewport: &Rectangle,
    ) {
        let text_layout = self.text_layout(layout);
        draw(
            renderer,
            theme,
            layout,
            text_layout,
            cursor_position,
            tree,
            &self.value,
            &self.placeholder,
            self.size,
            self.font,
            self.on_input.is_none(),
            self.is_secure,
            self.leading_icon.as_ref(),
            self.trailing_icon.as_ref(),
            &self.style,
            self.dnd_icon,
            self.line_height,
            self.error.as_deref(),
            self.label.as_deref(),
            self.helper_text.as_deref(),
            self.helper_size,
            self.helper_line_height,
            viewport,
            style,
        );
    }

    fn mouse_interaction(
        &self,
        state: &Tree,
        layout: Layout<'_>,
        cursor_position: mouse::Cursor,
        viewport: &Rectangle,
        renderer: &crate::Renderer,
    ) -> mouse::Interaction {
        let layout = self.text_layout(layout);
        let mut index = 0;
        if let (Some(leading_icon), Some(tree)) =
            (self.leading_icon.as_ref(), state.children.get(index))
        {
            let mut children = layout.children();
            children.next();
            let leading_icon_layout = children.next().unwrap();

            if cursor_position.is_over(leading_icon_layout.bounds()) {
                return leading_icon.as_widget().mouse_interaction(
                    tree,
                    layout,
                    cursor_position,
                    viewport,
                    renderer,
                );
            }
            index += 1;
        }

        if let (Some(trailing_icon), Some(tree)) =
            (self.trailing_icon.as_ref(), state.children.get(index))
        {
            let mut children = layout.children();
            children.next();
            // skip if there is no leading icon
            if self.leading_icon.is_some() {
                children.next();
            }
            let trailing_icon_layout = children.next().unwrap();

            if cursor_position.is_over(trailing_icon_layout.bounds()) {
                return trailing_icon.as_widget().mouse_interaction(
                    tree,
                    layout,
                    cursor_position,
                    viewport,
                    renderer,
                );
            }
        }
        let mut children = layout.children();
        let layout = children.next().unwrap();
        mouse_interaction(layout, cursor_position, self.on_input.is_none())
    }

    fn id(&self) -> Option<Id> {
        Some(self.id.clone())
    }

    fn set_id(&mut self, id: Id) {
        self.id = id;
    }

    fn drag_destinations(
        &self,
        state: &Tree,
        layout: Layout<'_>,
        renderer: &crate::Renderer,
        dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
    ) {
        if let Some(input) = layout.children().last() {
            let Rectangle {
                x,
                y,
                width,
                height,
            } = input.bounds();
            dnd_rectangles.push(iced::clipboard::dnd::DndDestinationRectangle {
                id: self.dnd_id(),
                rectangle: iced::clipboard::dnd::Rectangle {
                    x: x as f64,
                    y: y as f64,
                    width: width as f64,
                    height: height as f64,
                },
                mime_types: SUPPORTED_TEXT_MIME_TYPES
                    .iter()
                    .map(|s| Cow::Borrowed(*s))
                    .collect(),
                actions: DndAction::Move,
                preferred: DndAction::Move,
            });
        }
    }
}

impl<'a, Message> From<TextInput<'a, Message>>
    for Element<'a, Message, crate::Theme, crate::Renderer>
where
    Message: 'static + Clone,
{
    fn from(
        text_input: TextInput<'a, Message>,
    ) -> Element<'a, Message, crate::Theme, crate::Renderer> {
        Element::new(text_input)
    }
}

/// Produces a [`Task`] that focuses the [`TextInput`] with the given [`Id`].
pub fn focus<Message: 'static>(id: Id) -> Task<Message> {
    task::effect(Action::widget(operation::focusable::focus(id)))
}

/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
/// end.
pub fn move_cursor_to_end<Message: 'static>(id: Id) -> Task<Message> {
    task::effect(Action::widget(operation::text_input::move_cursor_to_end(
        id,
    )))
}

/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
/// front.
pub fn move_cursor_to_front<Message: 'static>(id: Id) -> Task<Message> {
    task::effect(Action::widget(operation::text_input::move_cursor_to_front(
        id,
    )))
}

/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
/// provided position.
pub fn move_cursor_to<Message: 'static>(id: Id, position: usize) -> Task<Message> {
    task::effect(Action::widget(operation::text_input::move_cursor_to(
        id, position,
    )))
}

/// Produces a [`Task`] that selects all the content of the [`TextInput`] with the given [`Id`].
pub fn select_all<Message: 'static>(id: Id) -> Task<Message> {
    task::effect(Action::widget(operation::text_input::select_all(id)))
}

/// Computes the layout of a [`TextInput`].
#[allow(clippy::cast_precision_loss)]
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_lines)]
pub fn layout<Message>(
    renderer: &crate::Renderer,
    limits: &layout::Limits,
    width: Length,
    padding: Padding,
    size: Option<f32>,
    leading_icon: Option<&Element<'_, Message, crate::Theme, crate::Renderer>>,
    trailing_icon: Option<&Element<'_, Message, crate::Theme, crate::Renderer>>,
    line_height: text::LineHeight,
    label: Option<&str>,
    helper_text: Option<&str>,
    helper_text_size: f32,
    helper_text_line_height: text::LineHeight,
    font: iced_core::Font,
    tree: &mut Tree,
) -> layout::Node {
    let limits = limits.width(width);
    let spacing = THEME.lock().unwrap().cosmic().space_xxs();
    let mut nodes = Vec::with_capacity(3);

    let text_pos = if let Some(label) = label {
        let text_bounds = limits.resolve(width, Length::Shrink, Size::INFINITY);
        let state = tree.state.downcast_mut::<State>();
        let label_paragraph = &mut state.label;
        label_paragraph.update(Text {
            content: label,
            font,
            bounds: text_bounds,
            size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)),
            horizontal_alignment: alignment::Horizontal::Left,
            vertical_alignment: alignment::Vertical::Center,
            line_height,
            shaping: text::Shaping::Advanced,
            wrapping: text::Wrapping::default(),
        });
        let label_size = label_paragraph.min_bounds();

        nodes.push(layout::Node::new(label_size));
        Vector::new(0.0, label_size.height + f32::from(spacing))
    } else {
        Vector::ZERO
    };

    let text_size = size.unwrap_or_else(|| renderer.default_size().0);
    let mut text_input_height = line_height.to_absolute(text_size.into()).0;
    let padding = padding.fit(Size::ZERO, limits.max());

    let helper_pos = if leading_icon.is_some() || trailing_icon.is_some() {
        let children = &mut tree.children;
        // TODO configurable icon spacing, maybe via appearance
        let limits_copy = limits;

        let limits = limits.shrink(padding);
        let icon_spacing = 8.0;
        let mut c_i = 0;
        let (leading_icon_width, mut leading_icon) =
            if let Some((icon, tree)) = leading_icon.zip(children.get_mut(c_i)) {
                let size = icon.as_widget().size();
                let icon_node = icon.as_widget().layout(
                    tree,
                    renderer,
                    &Limits::NONE.width(size.width).height(size.height),
                );
                text_input_height = text_input_height.max(icon_node.bounds().height);
                c_i += 1;
                (icon_node.bounds().width + icon_spacing, Some(icon_node))
            } else {
                (0.0, None)
            };

        let (trailing_icon_width, mut trailing_icon) =
            if let Some((icon, tree)) = trailing_icon.zip(children.get_mut(c_i)) {
                let size = icon.as_widget().size();
                let icon_node = icon.as_widget().layout(
                    tree,
                    renderer,
                    &Limits::NONE.width(size.width).height(size.height),
                );
                text_input_height = text_input_height.max(icon_node.bounds().height);
                (icon_node.bounds().width + icon_spacing, Some(icon_node))
            } else {
                (0.0, None)
            };
        let text_limits = limits
            .width(width)
            .height(line_height.to_absolute(text_size.into()));

        let text_bounds = text_limits.resolve(width, Length::Shrink, Size::INFINITY);

        let text_node = layout::Node::new(
            text_bounds - Size::new(leading_icon_width + trailing_icon_width, 0.0),
        )
        .move_to(Point::new(
            padding.left + leading_icon_width,
            padding.top
                + ((text_input_height - line_height.to_absolute(text_size.into()).0) / 2.0)
                    .max(0.0),
        ));
        let mut node_list: Vec<_> = Vec::with_capacity(3);

        let text_node_bounds = text_node.bounds();
        node_list.push(text_node);

        if let Some(leading_icon) = leading_icon.take() {
            node_list.push(leading_icon.clone().move_to(Point::new(
                padding.left,
                padding.top + ((text_input_height - leading_icon.bounds().height) / 2.0).max(0.0),
            )));
        }
        if let Some(trailing_icon) = trailing_icon.take() {
            let trailing_icon = trailing_icon.clone().move_to(Point::new(
                text_node_bounds.x + text_node_bounds.width + f32::from(spacing),
                padding.top + ((text_input_height - trailing_icon.bounds().height) / 2.0).max(0.0),
            ));
            node_list.push(trailing_icon);
        }

        let text_input_size = Size::new(
            text_node_bounds.x + text_node_bounds.width + trailing_icon_width,
            text_input_height,
        )
        .expand(padding);

        let input_limits = limits_copy
            .width(width)
            .height(text_input_height.max(text_input_size.height))
            .min_width(text_input_size.width);
        let input_bounds = input_limits.resolve(
            width,
            text_input_height.max(text_input_size.height),
            text_input_size,
        );
        let input_node = layout::Node::with_children(input_bounds, node_list).translate(text_pos);
        let y_pos = input_node.bounds().y + input_node.bounds().height + f32::from(spacing);
        nodes.push(input_node);

        Vector::new(0.0, y_pos)
    } else {
        let limits = limits
            .width(width)
            .height(text_input_height + padding.vertical())
            .shrink(padding);
        let text_bounds = limits.resolve(width, Length::Shrink, Size::INFINITY);

        let text = layout::Node::new(text_bounds).move_to(Point::new(padding.left, padding.top));

        let node = layout::Node::with_children(text_bounds.expand(padding), vec![text])
            .translate(text_pos);
        let y_pos = node.bounds().y + node.bounds().height + f32::from(spacing);

        nodes.push(node);

        Vector::new(0.0, y_pos)
    };

    if let Some(helper_text) = helper_text {
        let limits = limits
            .width(width)
            .shrink(padding)
            .height(helper_text_line_height.to_absolute(helper_text_size.into()));
        let text_bounds = limits.resolve(width, Length::Shrink, Size::INFINITY);
        let state = tree.state.downcast_mut::<State>();
        let helper_text_paragraph = &mut state.helper_text;
        helper_text_paragraph.update(Text {
            content: helper_text,
            font,
            bounds: text_bounds,
            size: iced::Pixels(helper_text_size),
            horizontal_alignment: alignment::Horizontal::Left,
            vertical_alignment: alignment::Vertical::Center,
            line_height: helper_text_line_height,
            shaping: text::Shaping::Advanced,
            wrapping: text::Wrapping::default(),
        });
        let helper_text_size = helper_text_paragraph.min_bounds();
        let helper_text_node = layout::Node::new(helper_text_size).translate(helper_pos);
        nodes.push(helper_text_node);
    };

    let mut size = nodes.iter().fold(Size::ZERO, |size, node| {
        Size::new(
            size.width.max(node.bounds().width),
            size.height + node.bounds().height,
        )
    });
    size.height += (nodes.len() - 1) as f32 * f32::from(spacing);

    let limits = limits
        .width(width)
        .height(size.height)
        .min_width(size.width);

    layout::Node::with_children(limits.resolve(width, size.height, size), nodes)
}

/// Processes an [`Event`] and updates the [`State`] of a [`TextInput`]
/// accordingly.
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_lines)]
#[allow(clippy::missing_panics_doc)]
#[allow(clippy::cast_lossless)]
#[allow(clippy::cast_possible_truncation)]
pub fn update<'a, Message: 'static>(
    id: Option<Id>,
    event: Event,
    text_layout: Layout<'_>,
    trailing_icon_layout: Option<Layout<'_>>,
    cursor: mouse::Cursor,
    clipboard: &mut dyn Clipboard,
    shell: &mut Shell<'_, Message>,
    value: &mut Value,
    size: f32,
    font: <crate::Renderer as iced_core::text::Renderer>::Font,
    is_secure: bool,
    is_editable: bool,
    on_input: Option<&dyn Fn(String) -> Message>,
    on_paste: Option<&dyn Fn(String) -> Message>,
    on_submit: &Option<Message>,
    on_toggle_edit: Option<&dyn Fn(bool) -> Message>,
    state: impl FnOnce() -> &'a mut State,
    #[allow(unused_variables)] on_start_dnd_source: Option<&dyn Fn(State) -> Message>,
    #[allow(unused_variables)] dnd_id: u128,
    line_height: text::LineHeight,
    layout: Layout<'_>,
) -> event::Status
where
    Message: Clone,
{
    let update_cache = |state, value| {
        replace_paragraph(state, layout, value, font, iced::Pixels(size), line_height);
    };

    let mut secured_value = if is_secure {
        value.secure()
    } else {
        value.clone()
    };
    let unsecured_value = value;
    let value = &mut secured_value;

    match event {
        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
        | Event::Touch(touch::Event::FingerPressed { .. }) => {
            let state = state();

            let click_position = if on_input.is_some() {
                cursor.position_over(layout.bounds())
            } else {
                None
            };

            if click_position.is_some() {
                state.is_focused = state.is_focused.or_else(|| {
                    let now = Instant::now();
                    LAST_FOCUS_UPDATE.with(|x| x.set(now));
                    Some(Focus {
                        updated_at: now,
                        now,
                    })
                });
            }

            if let Some(cursor_position) = click_position {
                let text_layout = layout.children().next().unwrap();
                let target = cursor_position.x - text_layout.bounds().x;

                let click =
                    mouse::Click::new(cursor_position, mouse::Button::Left, state.last_click);

                match (
                    &state.dragging_state,
                    click.kind(),
                    state.cursor().state(value),
                ) {
                    #[cfg(feature = "wayland")]
                    (None, click::Kind::Single, cursor::State::Selection { start, end }) => {
                        // if something is already selected, we can start a drag and drop for a
                        // single click that is on top of the selected text
                        // is the click on selected text?

                        if let Some(on_input) = on_input {
                            let left = start.min(end);
                            let right = end.max(start);

                            let (left_position, _left_offset) = measure_cursor_and_scroll_offset(
                                state.value.raw(),
                                text_layout.bounds(),
                                left,
                            );

                            let (right_position, _right_offset) = measure_cursor_and_scroll_offset(
                                state.value.raw(),
                                text_layout.bounds(),
                                right,
                            );

                            let width = right_position - left_position;
                            let selection_bounds = Rectangle {
                                x: text_layout.bounds().x + left_position,
                                y: text_layout.bounds().y,
                                width,
                                height: text_layout.bounds().height,
                            };

                            if cursor.is_over(selection_bounds) {
                                // XXX never start a dnd if the input is secure
                                if is_secure {
                                    return event::Status::Ignored;
                                }
                                let input_text =
                                    state.selected_text(&value.to_string()).unwrap_or_default();
                                state.dragging_state = Some(DraggingState::Dnd(
                                    DndAction::empty(),
                                    input_text.clone(),
                                ));
                                let mut editor = Editor::new(unsecured_value, &mut state.cursor);
                                editor.delete();

                                let contents = editor.contents();
                                let unsecured_value = Value::new(&contents);
                                let message = (on_input)(contents);
                                shell.publish(message);
                                if let Some(on_start_dnd) = on_start_dnd_source {
                                    shell.publish(on_start_dnd(state.clone()));
                                }
                                let state_clone = state.clone();

                                iced_core::clipboard::start_dnd(
                                    clipboard,
                                    false,
                                    id.map(|id| iced_core::clipboard::DndSource::Widget(id)),
                                    Some(iced_core::clipboard::IconSurface::new(
                                        Element::from(
                                            TextInput::<'static, ()>::new("", input_text.clone())
                                                .dnd_icon(true),
                                        ),
                                        iced_core::widget::tree::State::new(state_clone),
                                        Vector::ZERO,
                                    )),
                                    Box::new(TextInputString(input_text)),
                                    DndAction::Move,
                                );

                                update_cache(state, &unsecured_value);
                            } else {
                                update_cache(state, value);
                                // existing logic for setting the selection
                                let position = if target > 0.0 {
                                    find_cursor_position(text_layout.bounds(), value, state, target)
                                } else {
                                    None
                                };

                                state.cursor.move_to(position.unwrap_or(0));
                                state.dragging_state = Some(DraggingState::Selection);
                            }
                        } else {
                            // existing logic for setting the selection
                            let position = if target > 0.0 {
                                update_cache(state, value);
                                find_cursor_position(text_layout.bounds(), value, state, target)
                            } else {
                                None
                            };

                            state.cursor.move_to(position.unwrap_or(0));
                            state.dragging_state = Some(DraggingState::Selection);
                        }
                    }
                    (None, click::Kind::Single, _) => {
                        // existing logic for setting the selection
                        let position = if target > 0.0 {
                            update_cache(state, value);
                            find_cursor_position(text_layout.bounds(), value, state, target)
                        } else {
                            None
                        };

                        state.cursor.move_to(position.unwrap_or(0));
                        state.dragging_state = Some(DraggingState::Selection);
                    }
                    (None | Some(DraggingState::Selection), click::Kind::Double, _) => {
                        update_cache(state, value);

                        if is_secure {
                            state.cursor.select_all(value);
                        } else {
                            let position =
                                find_cursor_position(text_layout.bounds(), value, state, target)
                                    .unwrap_or(0);

                            state.cursor.select_range(
                                value.previous_start_of_word(position),
                                value.next_end_of_word(position),
                            );
                        }
                        state.dragging_state = Some(DraggingState::Selection);
                    }
                    (None | Some(DraggingState::Selection), click::Kind::Triple, _) => {
                        update_cache(state, value);
                        state.cursor.select_all(value);
                        state.dragging_state = Some(DraggingState::Selection);
                    }
                    _ => {
                        state.dragging_state = None;
                    }
                }

                // Enable write mode when an editable input label is clicked
                if is_editable
                    && state.is_read_only
                    && matches!(state.dragging_state, None | Some(DraggingState::Selection))
                {
                    state.is_read_only = false;
                    if let Some(on_toggle_edit) = on_toggle_edit {
                        let message = (on_toggle_edit)(!state.is_read_only);
                        shell.publish(message);

                        let now = Instant::now();
                        LAST_FOCUS_UPDATE.with(|x| x.set(now));
                        state.is_focused = Some(Focus {
                            updated_at: now,
                            now,
                        });

                        state.move_cursor_to_end();
                        return event::Status::Captured;
                    }
                }

                state.last_click = Some(click);

                return event::Status::Captured;
            }
            let mut is_trailing_clicked = false;
            if is_editable {
                if let Some(trailing_layout) = trailing_icon_layout {
                    is_trailing_clicked = cursor.is_over(trailing_layout.bounds());

                    if is_trailing_clicked && on_toggle_edit.is_some() {
                        let Some(pos) = cursor.position() else {
                            return event::Status::Ignored;
                        };

                        let click = mouse::Click::new(pos, mouse::Button::Left, state.last_click);

                        match (
                            &state.dragging_state,
                            click.kind(),
                            state.cursor().state(value),
                        ) {
                            (None, click::Kind::Single, _) => {
                                state.is_read_only = !state.is_read_only;
                                if let Some(on_toggle_edit) = on_toggle_edit {
                                    let message = (on_toggle_edit)(!state.is_read_only);
                                    shell.publish(message);

                                    let now = Instant::now();
                                    LAST_FOCUS_UPDATE.with(|x| x.set(now));
                                    state.is_focused = Some(Focus {
                                        updated_at: now,
                                        now,
                                    });

                                    state.move_cursor_to_end();

                                    return event::Status::Captured;
                                }
                            }
                            _ => {
                                state.dragging_state = None;
                            }
                        }
                    }
                }
            }

            if !is_trailing_clicked && click_position.is_none() {
                state.is_focused = None;
                state.dragging_state = None;
                state.is_pasting = None;

                state.keyboard_modifiers = keyboard::Modifiers::default();
            }
        }
        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
        | Event::Touch(touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. }) => {
            let state = state();
            state.dragging_state = None;
        }
        Event::Mouse(mouse::Event::CursorMoved { position })
        | Event::Touch(touch::Event::FingerMoved { position, .. }) => {
            let state = state();

            if matches!(state.dragging_state, Some(DraggingState::Selection)) {
                let target = position.x - text_layout.bounds().x;

                update_cache(state, value);
                let position =
                    find_cursor_position(text_layout.bounds(), value, state, target).unwrap_or(0);

                state
                    .cursor
                    .select_range(state.cursor.start(value), position);

                return event::Status::Captured;
            }
        }
        Event::Keyboard(keyboard::Event::KeyPressed { key, text, .. }) => {
            let state = state();

            if let Some(focus) = &mut state.is_focused {
                let Some(on_input) = on_input else {
                    return event::Status::Ignored;
                };

                if state.is_read_only {
                    return event::Status::Ignored;
                }

                let modifiers = state.keyboard_modifiers;
                focus.updated_at = Instant::now();
                LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));

                match key {
                    keyboard::Key::Named(keyboard::key::Named::Enter) => {
                        if let Some(on_submit) = on_submit.clone() {
                            shell.publish(on_submit);
                        }
                    }
                    keyboard::Key::Named(keyboard::key::Named::Backspace) => {
                        if platform::is_jump_modifier_pressed(modifiers)
                            && state.cursor.selection(value).is_none()
                        {
                            if is_secure {
                                let cursor_pos = state.cursor.end(value);
                                state.cursor.select_range(0, cursor_pos);
                            } else {
                                state.cursor.select_left_by_words(value);
                            }
                        }

                        let mut editor = Editor::new(unsecured_value, &mut state.cursor);
                        editor.backspace();

                        let contents = editor.contents();
                        let unsecured_value = Value::new(&contents);
                        let message = (on_input)(editor.contents());
                        shell.publish(message);

                        let value = if is_secure {
                            unsecured_value.secure()
                        } else {
                            unsecured_value
                        };
                        update_cache(state, &value);
                    }
                    keyboard::Key::Named(keyboard::key::Named::Delete) => {
                        if platform::is_jump_modifier_pressed(modifiers)
                            && state.cursor.selection(value).is_none()
                        {
                            if is_secure {
                                let cursor_pos = state.cursor.end(unsecured_value);
                                state.cursor.select_range(cursor_pos, unsecured_value.len());
                            } else {
                                state.cursor.select_right_by_words(unsecured_value);
                            }
                        }

                        let mut editor = Editor::new(unsecured_value, &mut state.cursor);
                        editor.delete();
                        let contents = editor.contents();
                        let unsecured_value = Value::new(&contents);
                        let message = (on_input)(contents);
                        shell.publish(message);
                        let value = if is_secure {
                            unsecured_value.secure()
                        } else {
                            unsecured_value
                        };

                        update_cache(state, &value);
                    }
                    keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => {
                        if platform::is_jump_modifier_pressed(modifiers) && !is_secure {
                            if modifiers.shift() {
                                state.cursor.select_left_by_words(value);
                            } else {
                                state.cursor.move_left_by_words(value);
                            }
                        } else if modifiers.shift() {
                            state.cursor.select_left(value);
                        } else {
                            state.cursor.move_left(value);
                        }
                    }
                    keyboard::Key::Named(keyboard::key::Named::ArrowRight) => {
                        if platform::is_jump_modifier_pressed(modifiers) && !is_secure {
                            if modifiers.shift() {
                                state.cursor.select_right_by_words(value);
                            } else {
                                state.cursor.move_right_by_words(value);
                            }
                        } else if modifiers.shift() {
                            state.cursor.select_right(value);
                        } else {
                            state.cursor.move_right(value);
                        }
                    }
                    keyboard::Key::Named(keyboard::key::Named::Home) => {
                        if modifiers.shift() {
                            state.cursor.select_range(state.cursor.start(value), 0);
                        } else {
                            state.cursor.move_to(0);
                        }
                    }
                    keyboard::Key::Named(keyboard::key::Named::End) => {
                        if modifiers.shift() {
                            state
                                .cursor
                                .select_range(state.cursor.start(value), value.len());
                        } else {
                            state.cursor.move_to(value.len());
                        }
                    }
                    keyboard::Key::Character(ref c)
                        if "c" == c && state.keyboard_modifiers.command() =>
                    {
                        if !is_secure {
                            if let Some((start, end)) = state.cursor.selection(value) {
                                clipboard.write(
                                    iced_core::clipboard::Kind::Standard,
                                    value.select(start, end).to_string(),
                                );
                            }
                        }
                    }
                    // XXX if we want to allow cutting of secure text, we need to
                    // update the cache and decide which value to cut
                    keyboard::Key::Character(c)
                        if "x" == c && state.keyboard_modifiers.command() =>
                    {
                        if !is_secure {
                            if let Some((start, end)) = state.cursor.selection(value) {
                                clipboard.write(
                                    iced_core::clipboard::Kind::Standard,
                                    value.select(start, end).to_string(),
                                );
                            }

                            let mut editor = Editor::new(value, &mut state.cursor);
                            editor.delete();

                            let message = (on_input)(editor.contents());

                            shell.publish(message);
                        }
                    }
                    keyboard::Key::Character(c)
                        if "v" == c && state.keyboard_modifiers.command() =>
                    {
                        let content = if let Some(content) = state.is_pasting.take() {
                            content
                        } else {
                            let content: String = clipboard
                                .read(iced_core::clipboard::Kind::Standard)
                                .unwrap_or_default()
                                .chars()
                                .filter(|c| !c.is_control())
                                .collect();

                            Value::new(&content)
                        };

                        let mut editor = Editor::new(unsecured_value, &mut state.cursor);

                        editor.paste(content.clone());

                        let contents = editor.contents();
                        let unsecured_value = Value::new(&contents);
                        let message = if let Some(paste) = &on_paste {
                            (paste)(contents)
                        } else {
                            (on_input)(contents)
                        };
                        shell.publish(message);

                        state.is_pasting = Some(content);

                        let value = if is_secure {
                            unsecured_value.secure()
                        } else {
                            unsecured_value
                        };

                        update_cache(state, &value);
                    }
                    keyboard::Key::Character(c)
                        if "a" == c && state.keyboard_modifiers.command() =>
                    {
                        state.cursor.select_all(value);
                    }
                    keyboard::Key::Named(keyboard::key::Named::Escape) => {
                        state.is_focused = None;
                        state.dragging_state = None;
                        state.is_pasting = None;

                        state.keyboard_modifiers = keyboard::Modifiers::default();
                    }
                    keyboard::Key::Named(
                        keyboard::key::Named::Tab
                        | keyboard::key::Named::ArrowUp
                        | keyboard::key::Named::ArrowDown,
                    ) => {
                        return event::Status::Ignored;
                    }
                    keyboard::Key::Character(_)
                    | keyboard::Key::Named(keyboard::key::Named::Space) => {
                        if state.is_pasting.is_none()
                            && !state.keyboard_modifiers.command()
                            && !modifiers.control()
                        {
                            let mut editor = Editor::new(unsecured_value, &mut state.cursor);

                            let character =
                                text.unwrap_or_default().chars().next().unwrap_or_default();
                            if !character.is_control() {
                                editor.insert(character);
                            }
                            let contents = editor.contents();
                            let unsecured_value = Value::new(&contents);
                            let message = (on_input)(contents);
                            shell.publish(message);

                            focus.updated_at = Instant::now();
                            LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));

                            let value = if is_secure {
                                unsecured_value.secure()
                            } else {
                                unsecured_value
                            };
                            update_cache(state, &value);

                            return event::Status::Captured;
                        }
                    }
                    _ => {}
                }

                return event::Status::Captured;
            }
        }
        Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
            let state = state();

            if state.is_focused.is_some() {
                match key {
                    keyboard::Key::Character(c) if "v" == c => {
                        state.is_pasting = None;
                    }
                    keyboard::Key::Named(keyboard::key::Named::Tab)
                    | keyboard::Key::Named(keyboard::key::Named::ArrowUp)
                    | keyboard::Key::Named(keyboard::key::Named::ArrowDown) => {
                        return event::Status::Ignored;
                    }
                    _ => {}
                }

                return event::Status::Captured;
            }
        }
        Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
            let state = state();

            state.keyboard_modifiers = modifiers;
        }
        Event::Window(window::Event::RedrawRequested(now)) => {
            let state = state();

            if let Some(focus) = &mut state.is_focused {
                focus.now = now;

                let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
                    - (now - focus.updated_at).as_millis() % CURSOR_BLINK_INTERVAL_MILLIS;

                shell.request_redraw(window::RedrawRequest::At(
                    now + Duration::from_millis(u64::try_from(millis_until_redraw).unwrap()),
                ));
            }
        }
        #[cfg(feature = "wayland")]
        Event::Dnd(DndEvent::Source(SourceEvent::Finished | SourceEvent::Cancelled)) => {
            let state = state();
            if matches!(state.dragging_state, Some(DraggingState::Dnd(..))) {
                state.dragging_state = None;
                return event::Status::Captured;
            }
        }
        #[cfg(feature = "wayland")]
        Event::Dnd(DndEvent::Offer(
            rectangle,
            OfferEvent::Enter {
                x,
                y,
                mime_types,
                surface,
            },
        )) if rectangle == Some(dnd_id) => {
            let state = state();
            let is_clicked = text_layout.bounds().contains(Point {
                x: x as f32,
                y: y as f32,
            });

            let mut accepted = false;
            for m in &mime_types {
                if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) {
                    let clone = m.clone();
                    accepted = true;
                }
            }
            if accepted {
                let target = x as f32 - text_layout.bounds().x;
                state.dnd_offer =
                    DndOfferState::HandlingOffer(mime_types.clone(), DndAction::empty());
                // existing logic for setting the selection
                let position = if target > 0.0 {
                    update_cache(state, value);
                    find_cursor_position(text_layout.bounds(), value, state, target)
                } else {
                    None
                };

                state.cursor.move_to(position.unwrap_or(0));
                return event::Status::Captured;
            }
        }
        #[cfg(feature = "wayland")]
        Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y }))
            if rectangle == Some(dnd_id) =>
        {
            let state = state();

            let target = x as f32 - text_layout.bounds().x;
            // existing logic for setting the selection
            let position = if target > 0.0 {
                update_cache(state, value);
                find_cursor_position(text_layout.bounds(), value, state, target)
            } else {
                None
            };

            state.cursor.move_to(position.unwrap_or(0));
            return event::Status::Captured;
        }
        #[cfg(feature = "wayland")]
        Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if rectangle == Some(dnd_id) => {
            let state = state();
            if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() {
                let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES
                    .iter()
                    .find(|m| mime_types.contains(&(**m).to_string()))
                else {
                    state.dnd_offer = DndOfferState::None;
                    return event::Status::Captured;
                };
                state.dnd_offer = DndOfferState::Dropped;
            }

            return event::Status::Ignored;
        }
        #[cfg(feature = "wayland")]
        Event::Dnd(DndEvent::Offer(
            rectangle,
            OfferEvent::Leave | OfferEvent::LeaveDestination,
        )) if rectangle == Some(dnd_id) => {
            let state = state();
            // ASHLEY TODO we should be able to reset but for now we don't if we are handling a
            // drop
            match state.dnd_offer {
                DndOfferState::Dropped => {}
                _ => {
                    state.dnd_offer = DndOfferState::None;
                }
            };
            return event::Status::Captured;
        }
        #[cfg(feature = "wayland")]
        Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type }))
            if rectangle == Some(dnd_id) =>
        {
            let state = state();
            if let DndOfferState::Dropped = state.dnd_offer.clone() {
                state.dnd_offer = DndOfferState::None;
                if !SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type.as_str()) || data.is_empty() {
                    return event::Status::Captured;
                }
                let Ok(content) = String::from_utf8(data) else {
                    return event::Status::Captured;
                };

                let mut editor = Editor::new(unsecured_value, &mut state.cursor);

                editor.paste(Value::new(content.as_str()));
                let contents = editor.contents();
                let unsecured_value = Value::new(&contents);

                if let Some(on_paste) = on_paste.as_ref() {
                    let message = (on_paste)(contents);
                    shell.publish(message);
                }

                let value = if is_secure {
                    unsecured_value.secure()
                } else {
                    unsecured_value
                };
                update_cache(state, &value);
                return event::Status::Captured;
            }
            return event::Status::Ignored;
        }
        _ => {}
    }

    event::Status::Ignored
}

/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
/// [`Value`] if provided.
///
/// [`Renderer`]: text::Renderer
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_lines)]
#[allow(clippy::missing_panics_doc)]
pub fn draw<'a, Message>(
    renderer: &mut crate::Renderer,
    theme: &crate::Theme,
    layout: Layout<'_>,
    text_layout: Layout<'_>,
    cursor_position: mouse::Cursor,
    tree: &Tree,
    value: &Value,
    placeholder: &str,
    size: Option<f32>,
    font: Option<<crate::Renderer as iced_core::text::Renderer>::Font>,
    is_disabled: bool,
    is_secure: bool,
    icon: Option<&Element<'a, Message, crate::Theme, crate::Renderer>>,
    trailing_icon: Option<&Element<'a, Message, crate::Theme, crate::Renderer>>,
    style: &<crate::Theme as StyleSheet>::Style,
    dnd_icon: bool,
    line_height: text::LineHeight,
    error: Option<&str>,
    label: Option<&str>,
    helper_text: Option<&str>,
    helper_text_size: f32,
    helper_line_height: text::LineHeight,
    viewport: &Rectangle,
    renderer_style: &renderer::Style,
) {
    // all children should be icon images
    let children = &tree.children;

    let state = tree.state.downcast_ref::<State>();
    let secure_value = is_secure.then(|| value.secure());
    let value = secure_value.as_ref().unwrap_or(value);

    let mut children_layout = layout.children();

    let (label_layout, layout, helper_text_layout) = if label.is_some() && helper_text.is_some() {
        let label_layout = children_layout.next();
        let layout = children_layout.next().unwrap();
        let helper_text_layout = children_layout.next();
        (label_layout, layout, helper_text_layout)
    } else if label.is_some() {
        let label_layout = children_layout.next();
        let layout = children_layout.next().unwrap();
        (label_layout, layout, None)
    } else if helper_text.is_some() {
        let layout = children_layout.next().unwrap();
        let helper_text_layout = children_layout.next();
        (None, layout, helper_text_layout)
    } else {
        let layout = children_layout.next().unwrap();

        (None, layout, None)
    };

    let mut children_layout = layout.children();
    let bounds = layout.bounds();
    // XXX Dnd widget may not have a layout with children, so we just use the text_layout
    let text_bounds = children_layout.next().unwrap_or(text_layout).bounds();

    let is_mouse_over = cursor_position.is_over(bounds);

    let appearance = if is_disabled {
        theme.disabled(style)
    } else if error.is_some() {
        theme.error(style)
    } else if state.is_focused() {
        theme.focused(style)
    } else if is_mouse_over {
        theme.hovered(style)
    } else {
        theme.active(style)
    };

    let mut icon_color = appearance.icon_color.unwrap_or(renderer_style.icon_color);
    let mut text_color = appearance.text_color.unwrap_or(renderer_style.text_color);

    // TODO: iced will not render alpha itself on text or icon colors.
    if is_disabled {
        let background = theme.current_container().component.base.into();
        icon_color = icon_color.blend_alpha(background, 0.5);
        text_color = text_color.blend_alpha(background, 0.5);
    }

    // draw background and its border
    if let Some(border_offset) = appearance.border_offset {
        let offset_bounds = Rectangle {
            x: bounds.x - border_offset,
            y: bounds.y - border_offset,
            width: border_offset.mul_add(2.0, bounds.width),
            height: border_offset.mul_add(2.0, bounds.height),
        };
        renderer.fill_quad(
            renderer::Quad {
                bounds,
                border: Border {
                    radius: appearance.border_radius,
                    width: appearance.border_width,
                    ..Default::default()
                },
                shadow: Shadow {
                    offset: Vector::new(0.0, 1.0),
                    color: Color::TRANSPARENT,
                    blur_radius: 0.0,
                },
            },
            appearance.background,
        );
        renderer.fill_quad(
            renderer::Quad {
                bounds: offset_bounds,
                border: Border {
                    width: appearance.border_width,
                    color: appearance.border_color,
                    radius: appearance.border_radius,
                },
                shadow: Shadow {
                    offset: Vector::new(0.0, 1.0),
                    color: Color::TRANSPARENT,
                    blur_radius: 0.0,
                },
            },
            Background::Color(Color::TRANSPARENT),
        );
    } else {
        renderer.fill_quad(
            renderer::Quad {
                bounds,
                border: Border {
                    width: appearance.border_width,
                    color: appearance.border_color,
                    radius: appearance.border_radius,
                },
                shadow: Shadow {
                    offset: Vector::new(0.0, 1.0),
                    color: Color::TRANSPARENT,
                    blur_radius: 0.0,
                },
            },
            appearance.background,
        );
    }

    // draw the label if it exists
    if let (Some(label_layout), Some(label)) = (label_layout, label) {
        renderer.fill_text(
            Text {
                content: label.to_string(),
                size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)),
                font: font.unwrap_or_else(|| renderer.default_font()),
                bounds: label_layout.bounds().size(),
                horizontal_alignment: alignment::Horizontal::Left,
                vertical_alignment: alignment::Vertical::Top,
                line_height,
                shaping: text::Shaping::Advanced,
                wrapping: text::Wrapping::default(),
            },
            label_layout.bounds().position(),
            appearance.label_color,
            *viewport,
        );
    }
    let mut child_index = 0;
    let leading_icon_tree = children.get(child_index);
    // draw the start icon in the text input
    let has_start_icon = icon.is_some();
    if let (Some(icon), Some(tree)) = (icon, leading_icon_tree) {
        let mut children = text_layout.children();
        let _ = children.next().unwrap();
        let icon_layout = children.next().unwrap();

        icon.as_widget().draw(
            tree,
            renderer,
            theme,
            &renderer::Style {
                icon_color,
                text_color,
                scale_factor: renderer_style.scale_factor,
            },
            icon_layout,
            cursor_position,
            viewport,
        );
        child_index += 1;
    }

    let text = value.to_string();
    let font = font.unwrap_or_else(|| renderer.default_font());
    let size = size.unwrap_or_else(|| renderer.default_size().0);

    let radius_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0.into();
    #[cfg(feature = "wayland")]
    let handling_dnd_offer = !matches!(state.dnd_offer, DndOfferState::None);
    #[cfg(not(feature = "wayland"))]
    let handling_dnd_offer = false;
    let (cursor, offset) = if let Some(focus) = &state.is_focused.or_else(|| {
        handling_dnd_offer.then(|| Focus {
            updated_at: Instant::now(),
            now: Instant::now(),
        })
    }) {
        match state.cursor.state(value) {
            cursor::State::Index(position) => {
                let (text_value_width, offset) =
                    measure_cursor_and_scroll_offset(state.value.raw(), text_bounds, position);

                let is_cursor_visible = handling_dnd_offer
                    || ((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS)
                        % 2
                        == 0;

                if is_cursor_visible {
                    if dnd_icon {
                        (None, 0.0)
                    } else {
                        (
                            Some((
                                renderer::Quad {
                                    bounds: Rectangle {
                                        x: text_bounds.x + text_value_width - offset,
                                        y: text_bounds.y,
                                        width: 1.0,
                                        height: text_bounds.height,
                                    },
                                    border: Border {
                                        width: 0.0,
                                        color: Color::TRANSPARENT,
                                        radius: radius_0,
                                    },
                                    shadow: Shadow {
                                        offset: Vector::ZERO,
                                        color: Color::TRANSPARENT,
                                        blur_radius: 0.0,
                                    },
                                },
                                text_color,
                            )),
                            offset,
                        )
                    }
                } else {
                    (None, offset)
                }
            }
            cursor::State::Selection { start, end } => {
                let left = start.min(end);
                let right = end.max(start);

                let value_paragraph = &state.value;
                let (left_position, left_offset) =
                    measure_cursor_and_scroll_offset(value_paragraph.raw(), text_bounds, left);

                let (right_position, right_offset) =
                    measure_cursor_and_scroll_offset(value_paragraph.raw(), text_bounds, right);

                let width = right_position - left_position;

                if dnd_icon {
                    (None, 0.0)
                } else {
                    (
                        Some((
                            renderer::Quad {
                                bounds: Rectangle {
                                    x: text_bounds.x + left_position,
                                    y: text_bounds.y,
                                    width,
                                    height: text_bounds.height,
                                },
                                border: Border {
                                    width: 0.0,
                                    color: Color::TRANSPARENT,
                                    radius: radius_0,
                                },
                                shadow: Shadow {
                                    offset: Vector::ZERO,
                                    color: Color::TRANSPARENT,
                                    blur_radius: 0.0,
                                },
                            },
                            appearance.selected_fill,
                        )),
                        if end == right {
                            right_offset
                        } else {
                            left_offset
                        },
                    )
                }
            }
        }
    } else {
        (None, 0.0)
    };

    let text_width = state.value.min_width();

    let render = |renderer: &mut crate::Renderer| {
        if let Some((cursor, color)) = cursor {
            renderer.fill_quad(cursor, color);
        } else {
            renderer.with_translation(Vector::ZERO, |_| {});
        }

        let bounds = Rectangle {
            x: text_bounds.x - offset,
            y: text_bounds.center_y(),
            width: f32::INFINITY,
            ..text_bounds
        };
        let color = if text.is_empty() {
            appearance.placeholder_color
        } else {
            text_color
        };

        renderer.fill_text(
            Text {
                content: if text.is_empty() {
                    placeholder.to_string()
                } else {
                    text.clone()
                },
                font,
                bounds: bounds.size(),
                size: iced::Pixels(size),
                horizontal_alignment: alignment::Horizontal::Left,
                vertical_alignment: alignment::Vertical::Center,
                line_height: text::LineHeight::default(),
                shaping: text::Shaping::Advanced,
                wrapping: text::Wrapping::default(),
            },
            bounds.position(),
            color,
            *viewport,
        );
    };

    renderer.with_layer(text_bounds, render);

    let trailing_icon_tree = children.get(child_index);

    // draw the end icon in the text input
    if let (Some(icon), Some(tree)) = (trailing_icon, trailing_icon_tree) {
        let mut children = text_layout.children();
        let mut icon_layout = children.next().unwrap();
        if has_start_icon {
            icon_layout = children.next().unwrap();
        }
        icon_layout = children.next().unwrap();

        icon.as_widget().draw(
            tree,
            renderer,
            theme,
            &renderer::Style {
                icon_color,
                text_color,
                scale_factor: renderer_style.scale_factor,
            },
            icon_layout,
            cursor_position,
            viewport,
        );
    }

    // draw the helper text if it exists
    if let (Some(helper_text_layout), Some(helper_text)) = (helper_text_layout, helper_text) {
        renderer.fill_text(
            Text {
                content: helper_text.to_string(), // TODO remove to_string?
                size: iced::Pixels(helper_text_size),
                font,
                bounds: helper_text_layout.bounds().size(),
                horizontal_alignment: alignment::Horizontal::Left,
                vertical_alignment: alignment::Vertical::Top,
                line_height: helper_line_height,
                shaping: text::Shaping::Advanced,
                wrapping: text::Wrapping::default(),
            },
            helper_text_layout.bounds().position(),
            text_color,
            *viewport,
        );
    }
}

/// Computes the current [`mouse::Interaction`] of the [`TextInput`].
#[must_use]
pub fn mouse_interaction(
    layout: Layout<'_>,
    cursor_position: mouse::Cursor,
    is_disabled: bool,
) -> mouse::Interaction {
    if cursor_position.is_over(layout.bounds()) {
        if is_disabled {
            mouse::Interaction::NotAllowed
        } else {
            mouse::Interaction::Text
        }
    } else {
        mouse::Interaction::default()
    }
}

/// A string which can be sent to the clipboard or drag-and-dropped.
#[derive(Debug, Clone)]
pub struct TextInputString(pub String);

#[cfg(feature = "wayland")]
impl AsMimeTypes for TextInputString {
    fn available(&self) -> Cow<'static, [String]> {
        Cow::Owned(
            SUPPORTED_TEXT_MIME_TYPES
                .iter()
                .cloned()
                .map(String::from)
                .collect::<Vec<_>>(),
        )
    }

    fn as_bytes(&self, mime_type: &str) -> Option<Cow<'static, [u8]>> {
        if SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type) {
            Some(Cow::Owned(self.0.clone().as_bytes().to_vec()))
        } else {
            None
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum DraggingState {
    Selection,
    #[cfg(feature = "wayland")]
    Dnd(DndAction, String),
}

#[cfg(feature = "wayland")]
#[derive(Debug, Default, Clone)]
pub(crate) enum DndOfferState {
    #[default]
    None,
    HandlingOffer(Vec<String>, DndAction),
    Dropped,
}
#[derive(Debug, Default, Clone)]
#[cfg(not(feature = "wayland"))]
pub(crate) struct DndOfferState;

/// The state of a [`TextInput`].
#[derive(Debug, Default, Clone)]
#[must_use]
pub struct State {
    pub value: crate::Plain,
    pub placeholder: crate::Plain,
    pub label: crate::Plain,
    pub helper_text: crate::Plain,
    pub dirty: bool,
    pub is_secure: bool,
    pub is_read_only: bool,
    select_on_focus: bool,
    is_focused: Option<Focus>,
    dragging_state: Option<DraggingState>,
    dnd_offer: DndOfferState,
    is_pasting: Option<Value>,
    last_click: Option<mouse::Click>,
    cursor: Cursor,
    keyboard_modifiers: keyboard::Modifiers,
    // TODO: Add stateful horizontal scrolling offset
}

#[derive(Debug, Clone, Copy)]
struct Focus {
    updated_at: Instant,
    now: Instant,
}

impl State {
    /// Creates a new [`State`], representing an unfocused [`TextInput`].
    pub fn new(
        is_secure: bool,
        is_read_only: bool,
        always_active: bool,
        select_on_focus: bool,
    ) -> Self {
        Self {
            is_secure,
            is_read_only,
            is_focused: always_active.then(|| {
                let now = Instant::now();
                Focus {
                    updated_at: now,
                    now,
                }
            }),
            select_on_focus,
            ..Self::default()
        }
    }

    /// Returns the current value of the selected text in the [`TextInput`].
    #[must_use]
    pub fn selected_text(&self, text: &str) -> Option<String> {
        let value = Value::new(text);
        match self.cursor.state(&value) {
            cursor::State::Index(_) => None,
            cursor::State::Selection { start, end } => {
                let left = start.min(end);
                let right = end.max(start);
                Some(text[left..right].to_string())
            }
        }
    }

    #[cfg(feature = "wayland")]
    /// Returns the current value of the dragged text in the [`TextInput`].
    #[must_use]
    pub fn dragged_text(&self) -> Option<String> {
        match self.dragging_state.as_ref() {
            Some(DraggingState::Dnd(_, text)) => Some(text.clone()),
            _ => None,
        }
    }

    /// Creates a new [`State`], representing a focused [`TextInput`].
    pub fn focused(is_secure: bool, is_read_only: bool) -> Self {
        Self {
            is_secure,
            value: crate::Plain::default(),
            placeholder: crate::Plain::default(),
            label: crate::Plain::default(),
            helper_text: crate::Plain::default(),
            is_read_only,
            is_focused: None,
            select_on_focus: false,
            dragging_state: None,
            dnd_offer: DndOfferState::default(),
            is_pasting: None,
            last_click: None,
            cursor: Cursor::default(),
            keyboard_modifiers: keyboard::Modifiers::default(),
            dirty: false,
        }
    }

    /// Returns whether the [`TextInput`] is currently focused or not.
    #[must_use]
    pub fn is_focused(&self) -> bool {
        self.is_focused.is_some()
    }

    /// Returns the [`Cursor`] of the [`TextInput`].
    #[must_use]
    pub fn cursor(&self) -> Cursor {
        self.cursor
    }

    /// Focuses the [`TextInput`].
    pub fn focus(&mut self) {
        let now = Instant::now();
        LAST_FOCUS_UPDATE.with(|x| x.set(now));
        self.is_read_only = false;
        self.is_focused = Some(Focus {
            updated_at: now,
            now,
        });

        if self.select_on_focus {
            self.select_all()
        } else {
            self.move_cursor_to_end();
        }
    }

    /// Unfocuses the [`TextInput`].
    pub fn unfocus(&mut self) {
        self.is_focused = None;
    }

    /// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text.
    pub fn move_cursor_to_front(&mut self) {
        self.cursor.move_to(0);
    }

    /// Moves the [`Cursor`] of the [`TextInput`] to the end of the input text.
    pub fn move_cursor_to_end(&mut self) {
        self.cursor.move_to(usize::MAX);
    }

    /// Moves the [`Cursor`] of the [`TextInput`] to an arbitrary location.
    pub fn move_cursor_to(&mut self, position: usize) {
        self.cursor.move_to(position);
    }

    /// Selects all the content of the [`TextInput`].
    pub fn select_all(&mut self) {
        self.cursor.select_range(0, usize::MAX);
    }
}

impl operation::Focusable for State {
    fn is_focused(&self) -> bool {
        Self::is_focused(self)
    }

    fn focus(&mut self) {
        Self::focus(self);
    }

    fn unfocus(&mut self) {
        Self::unfocus(self);
    }
}

impl operation::TextInput for State {
    fn move_cursor_to_front(&mut self) {
        Self::move_cursor_to_front(self);
    }

    fn move_cursor_to_end(&mut self) {
        Self::move_cursor_to_end(self);
    }

    fn move_cursor_to(&mut self, position: usize) {
        Self::move_cursor_to(self, position);
    }

    fn select_all(&mut self) {
        Self::select_all(self);
    }
}

fn measure_cursor_and_scroll_offset(
    paragraph: &impl text::Paragraph,
    text_bounds: Rectangle,
    cursor_index: usize,
) -> (f32, f32) {
    let grapheme_position = paragraph
        .grapheme_position(0, cursor_index)
        .unwrap_or(Point::ORIGIN);

    let offset = ((grapheme_position.x + 5.0) - text_bounds.width).max(0.0);

    (grapheme_position.x, offset)
}

/// Computes the position of the text cursor at the given X coordinate of
/// a [`TextInput`].
fn find_cursor_position(
    text_bounds: Rectangle,
    value: &Value,
    state: &State,
    x: f32,
) -> Option<usize> {
    let offset = offset(text_bounds, value, state);
    let value = value.to_string();

    let char_offset = state
        .value
        .raw()
        .hit_test(Point::new(x + offset, text_bounds.height / 2.0))
        .map(text::Hit::cursor)?;

    Some(
        unicode_segmentation::UnicodeSegmentation::graphemes(
            &value[..char_offset.min(value.len())],
            true,
        )
        .count(),
    )
}

fn replace_paragraph(
    state: &mut State,
    layout: Layout<'_>,
    value: &Value,
    font: <crate::Renderer as iced_core::text::Renderer>::Font,
    text_size: Pixels,
    line_height: text::LineHeight,
) {
    let mut children_layout = layout.children();
    let text_bounds = children_layout.next().unwrap().bounds();

    state.value = crate::Plain::new(Text {
        font,
        line_height,
        content: &value.to_string(),
        bounds: Size::new(f32::INFINITY, text_bounds.height),
        size: text_size,
        horizontal_alignment: alignment::Horizontal::Left,
        vertical_alignment: alignment::Vertical::Top,
        shaping: text::Shaping::Advanced,
        wrapping: text::Wrapping::default(),
    });
}

const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;

mod platform {
    use iced_core::keyboard;

    pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool {
        if cfg!(target_os = "macos") {
            modifiers.alt()
        } else {
            modifiers.control()
        }
    }
}

fn offset(text_bounds: Rectangle, value: &Value, state: &State) -> f32 {
    if state.is_focused() {
        let cursor = state.cursor();

        let focus_position = match cursor.state(value) {
            cursor::State::Index(i) => i,
            cursor::State::Selection { end, .. } => end,
        };

        let (_, offset) =
            measure_cursor_and_scroll_offset(state.value.raw(), text_bounds, focus_position);

        offset
    } else {
        0.0
    }
}
