use std::rc::Rc;

use editor::Editor;
use gpui::{AnyElement, ElementId, Focusable, TextStyleRefinement};
use settings::Settings as _;
use theme_settings::ThemeSettings;
use ui::{Tooltip, prelude::*, rems};

#[derive(IntoElement)]
pub struct SettingsInputField {
    id: Option<ElementId>,
    initial_text: Option<String>,
    placeholder: Option<&'static str>,
    confirm: Option<Rc<dyn Fn(Option<String>, &mut Window, &mut App)>>,
    tab_index: Option<isize>,
    use_buffer_font: bool,
    display_confirm_button: bool,
    display_clear_button: bool,
    clear_on_confirm: bool,
    action_slot: Option<AnyElement>,
    color: Option<Color>,
}

impl SettingsInputField {
    pub fn new() -> Self {
        Self {
            id: None,
            initial_text: None,
            placeholder: None,
            confirm: None,
            tab_index: None,
            use_buffer_font: false,
            display_confirm_button: false,
            display_clear_button: false,
            clear_on_confirm: false,
            action_slot: None,
            color: None,
        }
    }

    pub fn with_id(mut self, id: impl Into<ElementId>) -> Self {
        self.id = Some(id.into());
        self
    }

    pub fn with_initial_text(mut self, initial_text: String) -> Self {
        self.initial_text = Some(initial_text);
        self
    }

    pub fn with_placeholder(mut self, placeholder: &'static str) -> Self {
        self.placeholder = Some(placeholder);
        self
    }

    pub fn on_confirm(
        mut self,
        confirm: impl Fn(Option<String>, &mut Window, &mut App) + 'static,
    ) -> Self {
        self.confirm = Some(Rc::new(confirm));
        self
    }

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

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

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

    pub fn action_slot(mut self, action: impl IntoElement) -> Self {
        self.action_slot = Some(action.into_any_element());
        self
    }

    pub(crate) fn tab_index(mut self, arg: isize) -> Self {
        self.tab_index = Some(arg);
        self
    }

    pub fn with_buffer_font(mut self) -> Self {
        self.use_buffer_font = true;
        self
    }

    pub fn color(mut self, color: Color) -> Self {
        self.color = Some(color);
        self
    }
}

impl RenderOnce for SettingsInputField {
    fn render(self, window: &mut Window, cx: &mut App) -> impl ui::IntoElement {
        let settings = ThemeSettings::get_global(cx);
        let use_buffer_font = self.use_buffer_font;
        let color = self.color.map(|c| c.color(cx));
        let styles = TextStyleRefinement {
            font_family: use_buffer_font.then(|| settings.buffer_font.family.clone()),
            font_size: use_buffer_font.then(|| rems(0.75).into()),
            color,
            ..Default::default()
        };

        let first_render_initial_text = window.use_state(cx, |_, _| self.initial_text.clone());

        let editor = if let Some(id) = self.id {
            window.use_keyed_state(id, cx, {
                let initial_text = self.initial_text.clone();
                let placeholder = self.placeholder;
                let mut confirm = self.confirm.clone();

                move |window, cx| {
                    let mut editor = Editor::single_line(window, cx);
                    let editor_focus_handle = editor.focus_handle(cx);
                    if let Some(text) = initial_text {
                        editor.set_text(text, window, cx);
                    }

                    if let Some(confirm) = confirm.take()
                        && !self.display_confirm_button
                        && !self.display_clear_button
                        && !self.clear_on_confirm
                    {
                        cx.on_focus_out(
                            &editor_focus_handle,
                            window,
                            move |editor, _, window, cx| {
                                let text = Some(editor.text(cx));
                                confirm(text, window, cx);
                            },
                        )
                        .detach();
                    }

                    if let Some(placeholder) = placeholder {
                        editor.set_placeholder_text(placeholder, window, cx);
                    }
                    editor.set_text_style_refinement(styles);
                    editor
                }
            })
        } else {
            window.use_state(cx, {
                let initial_text = self.initial_text.clone();
                let placeholder = self.placeholder;
                let mut confirm = self.confirm.clone();

                move |window, cx| {
                    let mut editor = Editor::single_line(window, cx);
                    let editor_focus_handle = editor.focus_handle(cx);
                    if let Some(text) = initial_text {
                        editor.set_text(text, window, cx);
                    }

                    if let Some(confirm) = confirm.take()
                        && !self.display_confirm_button
                        && !self.display_clear_button
                        && !self.clear_on_confirm
                    {
                        cx.on_focus_out(
                            &editor_focus_handle,
                            window,
                            move |editor, _, window, cx| {
                                let text = Some(editor.text(cx));
                                confirm(text, window, cx);
                            },
                        )
                        .detach();
                    }

                    if let Some(placeholder) = placeholder {
                        editor.set_placeholder_text(placeholder, window, cx);
                    }
                    editor.set_text_style_refinement(styles);
                    editor
                }
            })
        };

        // When settings change externally (e.g. editing settings.json), the page
        // re-renders but use_keyed_state returns the cached editor with stale text.
        // Reconcile with the expected initial_text when the editor is not focused,
        // so we don't clobber what the user is actively typing.
        if let Some(initial_text) = &self.initial_text
            && let Some(first_initial) = first_render_initial_text.read(cx)
        {
            if initial_text != first_initial && !editor.read(cx).is_focused(window) {
                *first_render_initial_text.as_mut(cx) = self.initial_text.clone();
                let weak_editor = editor.downgrade();
                let initial_text = initial_text.clone();

                window.defer(cx, move |window, cx| {
                    weak_editor
                        .update(cx, |editor, cx| {
                            editor.set_text(initial_text, window, cx);
                        })
                        .ok();
                });
            }
        }

        let weak_editor = editor.downgrade();
        let weak_editor_for_button = editor.downgrade();
        let weak_editor_for_clear = editor.downgrade();

        let clear_on_confirm = self.clear_on_confirm;
        let clear_on_confirm_for_button = self.clear_on_confirm;

        let theme_colors = cx.theme().colors();

        let display_confirm_button = self.display_confirm_button;
        let display_clear_button = self.display_clear_button;
        let confirm_for_button = self.confirm.clone();
        let is_editor_empty = editor.read(cx).text(cx).trim().is_empty();
        let is_editor_focused = editor.read(cx).is_focused(window);

        h_flex()
            .group("settings-input-field-editor")
            .relative()
            .py_1()
            .px_2()
            .h_8()
            .min_w_64()
            .rounded_md()
            .border_1()
            .border_color(theme_colors.border)
            .bg(theme_colors.editor_background)
            .when_some(self.tab_index, |this, tab_index| {
                let focus_handle = editor.focus_handle(cx).tab_index(tab_index).tab_stop(true);
                this.track_focus(&focus_handle)
                    .focus(|s| s.border_color(theme_colors.border_focused))
            })
            .child(editor)
            .child(
                h_flex()
                    .absolute()
                    .top_1()
                    .right_1()
                    .invisible()
                    .when(is_editor_focused, |this| this.visible())
                    .group_hover("settings-input-field-editor", |this| this.visible())
                    .when(
                        display_clear_button && !is_editor_empty && is_editor_focused,
                        |this| {
                            this.child(
                                IconButton::new("clear-button", IconName::Close)
                                    .icon_size(IconSize::Small)
                                    .icon_color(Color::Muted)
                                    .tooltip(Tooltip::text("Clear"))
                                    .on_click(move |_, window, cx| {
                                        let Some(editor) = weak_editor_for_clear.upgrade() else {
                                            return;
                                        };
                                        editor.update(cx, |editor, cx| {
                                            editor.set_text("", window, cx);
                                        });
                                    }),
                            )
                        },
                    )
                    .when(
                        display_confirm_button && !is_editor_empty && is_editor_focused,
                        |this| {
                            this.child(
                                IconButton::new("confirm-button", IconName::Check)
                                    .icon_size(IconSize::Small)
                                    .icon_color(Color::Success)
                                    .tooltip(Tooltip::text("Enter to Confirm"))
                                    .on_click(move |_, window, cx| {
                                        let Some(confirm) = confirm_for_button.as_ref() else {
                                            return;
                                        };
                                        let Some(editor) = weak_editor_for_button.upgrade() else {
                                            return;
                                        };
                                        let new_value =
                                            editor.read_with(cx, |editor, cx| editor.text(cx));
                                        let new_value =
                                            (!new_value.is_empty()).then_some(new_value);
                                        confirm(new_value, window, cx);
                                        if clear_on_confirm_for_button {
                                            editor.update(cx, |editor, cx| {
                                                editor.set_text("", window, cx);
                                            });
                                        }
                                    }),
                            )
                        },
                    )
                    .when_some(self.action_slot, |this, action| this.child(action)),
            )
            .when_some(self.confirm, |this, confirm| {
                this.on_action::<menu::Confirm>({
                    move |_, window, cx| {
                        let Some(editor) = weak_editor.upgrade() else {
                            return;
                        };
                        let new_value = editor.read_with(cx, |editor, cx| editor.text(cx));
                        let new_value = (!new_value.is_empty()).then_some(new_value);
                        confirm(new_value, window, cx);
                        if clear_on_confirm {
                            editor.update(cx, |editor, cx| {
                                editor.set_text("", window, cx);
                            });
                        }
                    }
                })
            })
    }
}
