use super::element::TextElement;
use super::state::*;
use super::*;

impl Render for TextInput {
    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        let style = self.style;
        let focus = self.focus_handle.clone();
        let entity_id = cx.entity().entity_id();
        let chromeless = self.chromeless;
        let multiline = self.multiline;
        let leading_icon = self.leading_icon;
        // Content-width layout: the wrappers below size to the widest line so an
        // outer `overflow_scroll` container can scroll the field horizontally
        // (and expose a horizontal `max_offset`), instead of clipping to the
        // viewport. Opt-in, non-wrapping multiline only.
        let content_width_layout =
            multiline && self.interaction.content_width_layout && !self.soft_wrap;
        let pad_x = if chromeless { px(0.0) } else { px(8.0) };
        let pad_y = if chromeless || !multiline {
            px(0.0)
        } else {
            self.vertical_padding_override.unwrap_or(px(8.0))
        };
        let is_focused = focus.is_focused(window);

        if self.interaction.has_focus != is_focused {
            self.interaction.has_focus = is_focused;
            self.interaction.cursor_blink_visible = true;
            if !is_focused {
                self.interaction.cursor_blink_task.take();
                self.interaction.context_menu = None;
            }
        }

        if is_focused
            && self.interaction.cursor_blink_task.is_none()
            && crate::ui_runtime::current().uses_cursor_blink()
        {
            let task = cx.spawn(
                async move |input: gpui::WeakEntity<TextInput>, cx: &mut gpui::AsyncApp| {
                    loop {
                        smol::Timer::after(Duration::from_millis(800)).await;
                        let should_continue = input
                            .update(cx, |input, cx| {
                                if !input.interaction.has_focus {
                                    input.interaction.cursor_blink_visible = true;
                                    input.interaction.cursor_blink_task = None;
                                    cx.notify();
                                    return false;
                                }

                                if input.selection.range.is_empty() {
                                    input.interaction.cursor_blink_visible =
                                        !input.interaction.cursor_blink_visible;
                                } else {
                                    input.interaction.cursor_blink_visible = true;
                                }
                                cx.notify();
                                true
                            })
                            .unwrap_or(false);

                        if !should_continue {
                            break;
                        }
                    }
                },
            );
            self.interaction.cursor_blink_task = Some(task);
        }

        let mut text_surface = div()
            .pl(if leading_icon.is_some() {
                px(6.0)
            } else {
                pad_x
            })
            .pr(pad_x)
            .py(pad_y);
        if content_width_layout {
            // At least the viewport, but grow to the widest line so the outer
            // scroll container sees horizontal overflow. No clipping here — the
            // container owns horizontal scrolling. `flex_shrink_0` stops the
            // flex-row parent from shrinking it back down to the viewport.
            text_surface = text_surface.min_w_full().flex_shrink_0();
        } else {
            text_surface = text_surface.w_full().min_w(px(0.0)).overflow_hidden();
        }
        let text_surface = text_surface.child(TextElement { input: cx.entity() });

        // `track_focus` alone leaves the element stateless, and gpui only
        // repaints on mouse-move for elements carrying an id — without one the
        // unfocused hover border further down never reaches the screen.
        let mut input = div().id(ElementId::from(("text_input_field", entity_id)));
        if content_width_layout {
            input = input.min_w_full();
        } else {
            input = input.w_full().min_w(px(0.0));
        }
        let mut input = input
            .flex()
            .track_focus(&focus)
            .key_context("TextInput")
            .cursor(CursorStyle::IBeam)
            .on_key_down(cx.listener(Self::on_key_down))
            .on_action(cx.listener(Self::backspace))
            .on_action(cx.listener(Self::delete))
            .on_action(cx.listener(Self::delete_word_left))
            .on_action(cx.listener(Self::delete_word_right))
            .on_action(cx.listener(Self::enter))
            .on_action(cx.listener(Self::shift_enter))
            .on_action(cx.listener(Self::left))
            .on_action(cx.listener(Self::right))
            .on_action(cx.listener(Self::up))
            .on_action(cx.listener(Self::down))
            .on_action(cx.listener(Self::word_left))
            .on_action(cx.listener(Self::word_right))
            .on_action(cx.listener(Self::select_left))
            .on_action(cx.listener(Self::select_right))
            .on_action(cx.listener(Self::select_up))
            .on_action(cx.listener(Self::select_down))
            .on_action(cx.listener(Self::select_word_left))
            .on_action(cx.listener(Self::select_word_right))
            .on_action(cx.listener(Self::select_all))
            .on_action(cx.listener(Self::home))
            .on_action(cx.listener(Self::select_home))
            .on_action(cx.listener(Self::end))
            .on_action(cx.listener(Self::select_end))
            .on_action(cx.listener(Self::page_up))
            .on_action(cx.listener(Self::select_page_up))
            .on_action(cx.listener(Self::page_down))
            .on_action(cx.listener(Self::select_page_down))
            .on_action(cx.listener(Self::paste))
            .on_action(cx.listener(Self::cut))
            .on_action(cx.listener(Self::copy))
            .on_action(cx.listener(Self::undo))
            .on_action(cx.listener(Self::redo))
            .on_action(cx.listener(Self::show_character_palette))
            .on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down))
            .on_mouse_up(MouseButton::Left, cx.listener(Self::on_mouse_up))
            .on_mouse_up_out(MouseButton::Left, cx.listener(Self::on_mouse_up))
            .on_mouse_move(cx.listener(Self::on_mouse_move))
            .on_mouse_down(MouseButton::Right, cx.listener(Self::on_mouse_down_right))
            .line_height(self.effective_line_height(window))
            .text_size(crate::ui_scale::design_px_from_window(13.0, window))
            .when(!multiline && !chromeless, |d| {
                d.h(crate::ui_scale::design_px_from_window(
                    SINGLE_LINE_INPUT_HEIGHT_PX,
                    window,
                ))
            })
            .when(multiline && self.min_lines > 0, |d| {
                let line_height = self.effective_line_height(window);
                d.min_h(line_height * self.min_lines as f32 + pad_y * 2.0)
            })
            .when(!multiline, |d| d.items_center())
            .when(multiline, |d| d.items_start())
            .when_some(leading_icon, |d, icon_path| {
                d.child(
                    div().pl(pad_x).flex_none().child(
                        gpui::svg()
                            .path(icon_path)
                            .size(crate::ui_scale::design_px_from_window(14.0, window))
                            .text_color(style.placeholder),
                    ),
                )
            })
            .child(text_surface);

        if !chromeless {
            input = input
                .bg(style.background)
                .border_1()
                .rounded(px(style.radius));

            if is_focused {
                input = input.border_color(style.focus_border);
            } else {
                input = input
                    .border_color(style.border)
                    .hover(move |s| s.border_color(style.hover_border));
            }

            input = input.focus(move |s| s.border_color(style.focus_border));
        }

        let render_id = ElementId::from(("text_input_root", entity_id));
        let render_id =
            ElementId::from((render_id, if is_focused { "focused" } else { "blurred" }));
        let mut outer = div()
            // Focus changes toggle GPUI platform input handler registration during paint.
            // Key the subtree by focus state so GPUI doesn't reuse a stale unfocused paint
            // range that contains no input handlers when the field becomes focused.
            .id(render_id);
        if content_width_layout {
            // `items_start` so the flex-col cross axis doesn't stretch the inner
            // field back to the viewport width, letting it keep its content width.
            outer = outer.min_w_full().items_start();
        } else {
            outer = outer.w_full().min_w(px(0.0));
        }
        let mut outer = outer.flex().flex_col().child(input);

        if let Some(state) = self.interaction.context_menu {
            outer = outer.child(
                deferred(
                    anchored()
                        .position(state.anchor)
                        .offset(point(px(4.0), px(4.0)))
                        .child(self.render_context_menu(state, cx)),
                )
                .priority(10_000),
            );
        }

        outer
    }
}
