use gpui::prelude::FluentBuilder as _;
use gpui::{
    Anchor, App, AppContext as _, Context, DismissEvent, Entity, IntoElement, MouseDownEvent,
    ParentElement as _, Pixels, Point, Render, Styled, Subscription, Window, anchored, deferred,
    div, px,
};

use crate::input::popovers::ContextMenu;
use crate::input::{self, InputState};
use crate::menu::PopupMenu;

/// Context menu for mouse right clicks.
pub(crate) struct InputContextMenu {
    editor: Entity<InputState>,
    menu: Entity<PopupMenu>,
    mouse_position: Point<Pixels>,
    open: bool,

    _subscriptions: Vec<Subscription>,
}

impl InputState {
    pub(crate) fn handle_right_click_menu(
        &mut self,
        event: &MouseDownEvent,
        offset: usize,
        window: &mut Window,
        cx: &mut Context<Self>,
    ) {
        // Show Mouse context menu
        if !self.selected_range.contains(offset) {
            self.move_to(offset, None, cx);
        }

        self.context_menu_content = Some(ContextMenu::RightClick(self.context_menu.clone()));

        let is_code_editor = self.mode.is_code_editor();
        if is_code_editor {
            self.handle_hover_definition(offset, window, cx);
        }

        let is_enable = !self.disabled;
        let has_goto_definition = is_enable && self.lsp.definition_provider.is_some();
        let has_code_action = is_enable && !self.lsp.code_action_providers.is_empty();
        let is_selected = !self.selected_range.is_empty();
        let has_paste = is_enable && cx.read_from_clipboard().is_some();

        let action_context = self.focus_handle.clone();
        self.context_menu.update(cx, |this, cx| {
            this.mouse_position = event.position;
            this.menu.update(cx, |menu, cx| {
                let new_menu = if let Some(builder) = &self.context_menu_builder {
                    builder(PopupMenu::new(cx), window, cx)
                } else {
                    PopupMenu::new(cx)
                        .when(is_code_editor, |m| {
                            m.menu_with_enable(
                                "Go to Definition",
                                Box::new(input::GoToDefinition),
                                has_goto_definition,
                            )
                            .menu_with_enable(
                                "Show Code Actions",
                                Box::new(input::ToggleCodeActions),
                                has_code_action,
                            )
                            .separator()
                        })
                        .menu_with_enable("Cut", Box::new(input::Cut), is_enable && is_selected)
                        .menu_with_enable("Copy", Box::new(input::Copy), is_selected)
                        .menu_with_enable("Paste", Box::new(input::Paste), has_paste)
                        .separator()
                        .menu("Select All", Box::new(input::SelectAll))
                };

                menu.menu_items = new_menu.menu_items;
                menu.action_context = Some(action_context);
                cx.notify();
            });
            cx.defer_in(window, |this, _, cx| {
                this.open = true;
                cx.notify();
            });
        });
    }
}

impl InputContextMenu {
    pub(crate) fn new(
        editor: Entity<InputState>,
        window: &mut Window,
        cx: &mut App,
    ) -> Entity<Self> {
        cx.new(|cx| {
            let menu = cx.new(|cx| PopupMenu::new(cx).small());

            let _subscriptions = vec![cx.subscribe_in(&menu, window, {
                move |this: &mut Self, _, _: &DismissEvent, window, cx| {
                    this.close(window, cx);
                }
            })];

            Self {
                editor,
                menu,
                mouse_position: Point::default(),
                open: false,
                _subscriptions,
            }
        })
    }

    #[inline]
    pub(crate) fn is_open(&self) -> bool {
        self.open
    }

    #[inline]
    pub(crate) fn close(&mut self, window: &mut Window, cx: &mut Context<Self>) {
        self.open = false;
        self.editor.update(cx, |this, cx| {
            this.focus(window, cx);
        });
    }
}

impl Render for InputContextMenu {
    fn render(&mut self, _: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        if !self.open {
            return div().into_any_element();
        }

        deferred(
            anchored()
                .snap_to_window_with_margin(px(8.))
                .anchor(Anchor::TopLeft)
                .position(self.mouse_position)
                .child(div().cursor_default().child(self.menu.clone())),
        )
        .into_any_element()
    }
}
