use gpui::{
    App, AppContext as _, Context, Entity, EventEmitter, FocusHandle, Focusable, FontWeight,
    InteractiveElement, IntoElement, ParentElement, PromptButton, PromptHandle, PromptLevel,
    PromptResponse, Refineable, Render, RenderablePromptHandle, SharedString, Styled,
    TextStyleRefinement, Window, div,
};
use markdown::{Markdown, MarkdownElement, MarkdownStyle};
use settings::{Settings, SettingsStore};
use theme::ThemeSettings;
use ui::{
    ActiveTheme, ButtonCommon, ButtonStyle, Clickable, ElevationIndex, FluentBuilder, LabelSize,
    StyledExt, TintColor, h_flex, v_flex,
};
use workspace::WorkspaceSettings;

pub fn init(cx: &mut App) {
    process_settings(cx);

    cx.observe_global::<SettingsStore>(process_settings)
        .detach();
}

fn process_settings(cx: &mut App) {
    let settings = WorkspaceSettings::get_global(cx);
    if settings.use_system_prompts && cfg!(not(any(target_os = "linux", target_os = "freebsd"))) {
        cx.reset_prompt_builder();
    } else {
        cx.set_prompt_builder(zed_prompt_renderer);
    }
}

/// Use this function in conjunction with [App::set_prompt_builder] to force
/// GPUI to use the internal prompt system.
fn zed_prompt_renderer(
    level: PromptLevel,
    message: &str,
    detail: Option<&str>,
    actions: &[PromptButton],
    handle: PromptHandle,
    window: &mut Window,
    cx: &mut App,
) -> RenderablePromptHandle {
    let renderer = cx.new({
        |cx| ZedPromptRenderer {
            _level: level,
            message: cx.new(|cx| Markdown::new(SharedString::new(message), None, None, cx)),
            actions: actions.iter().map(|a| a.label().to_string()).collect(),
            focus: cx.focus_handle(),
            active_action_id: 0,
            detail: detail
                .filter(|text| !text.is_empty())
                .map(|text| cx.new(|cx| Markdown::new(SharedString::new(text), None, None, cx))),
        }
    });

    handle.with_view(renderer, window, cx)
}

pub struct ZedPromptRenderer {
    _level: PromptLevel,
    message: Entity<Markdown>,
    actions: Vec<String>,
    focus: FocusHandle,
    active_action_id: usize,
    detail: Option<Entity<Markdown>>,
}

impl ZedPromptRenderer {
    fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
        cx.emit(PromptResponse(self.active_action_id));
    }

    fn cancel(&mut self, _: &menu::Cancel, _window: &mut Window, cx: &mut Context<Self>) {
        if let Some(ix) = self.actions.iter().position(|a| a == "Cancel") {
            cx.emit(PromptResponse(ix));
        }
    }

    fn select_first(
        &mut self,
        _: &menu::SelectFirst,
        _window: &mut Window,
        cx: &mut Context<Self>,
    ) {
        self.active_action_id = self.actions.len().saturating_sub(1);
        cx.notify();
    }

    fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
        self.active_action_id = 0;
        cx.notify();
    }

    fn select_next(&mut self, _: &menu::SelectNext, _window: &mut Window, cx: &mut Context<Self>) {
        if self.active_action_id > 0 {
            self.active_action_id -= 1;
        } else {
            self.active_action_id = self.actions.len().saturating_sub(1);
        }
        cx.notify();
    }

    fn select_previous(
        &mut self,
        _: &menu::SelectPrevious,
        _window: &mut Window,
        cx: &mut Context<Self>,
    ) {
        self.active_action_id = (self.active_action_id + 1) % self.actions.len();
        cx.notify();
    }
}

impl Render for ZedPromptRenderer {
    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        let settings = ThemeSettings::get_global(cx);
        let font_size = settings.ui_font_size(cx).into();
        let prompt = v_flex()
            .key_context("Prompt")
            .cursor_default()
            .track_focus(&self.focus)
            .on_action(cx.listener(Self::confirm))
            .on_action(cx.listener(Self::cancel))
            .on_action(cx.listener(Self::select_next))
            .on_action(cx.listener(Self::select_previous))
            .on_action(cx.listener(Self::select_first))
            .on_action(cx.listener(Self::select_last))
            .elevation_3(cx)
            .w_72()
            .overflow_hidden()
            .p_4()
            .gap_4()
            .font_family(settings.ui_font.family.clone())
            .child(
                div()
                    .w_full()
                    .child(MarkdownElement::new(self.message.clone(), {
                        let mut base_text_style = window.text_style();
                        base_text_style.refine(&TextStyleRefinement {
                            font_family: Some(settings.ui_font.family.clone()),
                            font_size: Some(font_size),
                            font_weight: Some(FontWeight::BOLD),
                            color: Some(ui::Color::Default.color(cx)),
                            ..Default::default()
                        });
                        MarkdownStyle {
                            base_text_style,
                            selection_background_color: cx
                                .theme()
                                .colors()
                                .element_selection_background,
                            ..Default::default()
                        }
                    })),
            )
            .children(self.detail.clone().map(|detail| {
                div()
                    .w_full()
                    .text_xs()
                    .child(MarkdownElement::new(detail, {
                        let mut base_text_style = window.text_style();
                        base_text_style.refine(&TextStyleRefinement {
                            font_family: Some(settings.ui_font.family.clone()),
                            font_size: Some(font_size),
                            color: Some(ui::Color::Muted.color(cx)),
                            ..Default::default()
                        });
                        MarkdownStyle {
                            base_text_style,
                            selection_background_color: cx
                                .theme()
                                .colors()
                                .element_selection_background,
                            ..Default::default()
                        }
                    }))
            }))
            .child(h_flex().justify_end().gap_2().children(
                self.actions.iter().enumerate().rev().map(|(ix, action)| {
                    ui::Button::new(ix, action.clone())
                        .label_size(LabelSize::Large)
                        .style(ButtonStyle::Filled)
                        .when(ix == self.active_action_id, |el| {
                            el.style(ButtonStyle::Tinted(TintColor::Accent))
                        })
                        .layer(ElevationIndex::ModalSurface)
                        .on_click(cx.listener(move |_, _, _window, cx| {
                            cx.emit(PromptResponse(ix));
                        }))
                }),
            ));

        div()
            .size_full()
            .occlude()
            .bg(gpui::black().opacity(0.2))
            .child(
                div()
                    .size_full()
                    .absolute()
                    .top_0()
                    .left_0()
                    .flex()
                    .flex_col()
                    .justify_around()
                    .child(
                        div()
                            .w_full()
                            .flex()
                            .flex_row()
                            .justify_around()
                            .child(prompt),
                    ),
            )
    }
}

impl EventEmitter<PromptResponse> for ZedPromptRenderer {}

impl Focusable for ZedPromptRenderer {
    fn focus_handle(&self, _: &crate::App) -> FocusHandle {
        self.focus.clone()
    }
}
