use std::collections::HashSet;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicUsize, Ordering};

use db::kvp::KeyValueStore;
use gpui::{App, EntityId, EventEmitter, Subscription};
use ui::{IconButtonShape, Tooltip, prelude::*};
use workspace::item::{ItemBufferKind, ItemEvent, ItemHandle};
use workspace::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};

pub struct MultibufferHint {
    shown_on: HashSet<EntityId>,
    active_item: Option<Box<dyn ItemHandle>>,
    subscription: Option<Subscription>,
}

const NUMBER_OF_HINTS: usize = 10;

const SHOWN_COUNT_KEY: &str = "MULTIBUFFER_HINT_SHOWN_COUNT";

impl Default for MultibufferHint {
    fn default() -> Self {
        Self::new()
    }
}

impl MultibufferHint {
    pub fn new() -> Self {
        Self {
            shown_on: Default::default(),
            active_item: None,
            subscription: None,
        }
    }
}

impl MultibufferHint {
    fn counter(cx: &App) -> &'static AtomicUsize {
        static SHOWN_COUNT: OnceLock<AtomicUsize> = OnceLock::new();
        SHOWN_COUNT.get_or_init(|| {
            let value: usize = KeyValueStore::global(cx)
                .read_kvp(SHOWN_COUNT_KEY)
                .ok()
                .flatten()
                .and_then(|v| v.parse().ok())
                .unwrap_or(0);

            AtomicUsize::new(value)
        })
    }

    fn shown_count(cx: &App) -> usize {
        Self::counter(cx).load(Ordering::Relaxed)
    }

    fn increment_count(cx: &mut App) {
        Self::set_count(Self::shown_count(cx) + 1, cx)
    }

    pub(crate) fn set_count(count: usize, cx: &mut App) {
        Self::counter(cx).store(count, Ordering::Relaxed);

        let kvp = KeyValueStore::global(cx);
        db::write_and_log(cx, move || async move {
            kvp.write_kvp(SHOWN_COUNT_KEY.to_string(), format!("{}", count))
                .await
        });
    }

    fn dismiss(&mut self, cx: &mut App) {
        Self::set_count(NUMBER_OF_HINTS, cx)
    }

    /// Determines the toolbar location for this [`MultibufferHint`].
    fn determine_toolbar_location(&mut self, cx: &mut Context<Self>) -> ToolbarItemLocation {
        if Self::shown_count(cx) >= NUMBER_OF_HINTS {
            return ToolbarItemLocation::Hidden;
        }

        let Some(active_pane_item) = self.active_item.as_ref() else {
            return ToolbarItemLocation::Hidden;
        };

        if active_pane_item.buffer_kind(cx) == ItemBufferKind::Singleton
            || active_pane_item.breadcrumbs(cx).is_none()
            || !active_pane_item.can_save(cx)
        {
            return ToolbarItemLocation::Hidden;
        }

        if self.shown_on.insert(active_pane_item.item_id()) {
            Self::increment_count(cx);
        }

        ToolbarItemLocation::Secondary
    }
}

impl EventEmitter<ToolbarItemEvent> for MultibufferHint {}

impl ToolbarItemView for MultibufferHint {
    fn set_active_pane_item(
        &mut self,
        active_pane_item: Option<&dyn ItemHandle>,
        window: &mut Window,
        cx: &mut Context<Self>,
    ) -> ToolbarItemLocation {
        cx.notify();
        self.active_item = active_pane_item.map(|item| item.boxed_clone());

        let Some(active_pane_item) = active_pane_item else {
            return ToolbarItemLocation::Hidden;
        };

        let this = cx.entity().downgrade();
        self.subscription = Some(active_pane_item.subscribe_to_item_events(
            window,
            cx,
            Box::new(move |event, _, cx| {
                if let ItemEvent::UpdateBreadcrumbs = event {
                    this.update(cx, |this, cx| {
                        cx.notify();
                        let location = this.determine_toolbar_location(cx);
                        cx.emit(ToolbarItemEvent::ChangeLocation(location))
                    })
                    .ok();
                }
            }),
        ));

        self.determine_toolbar_location(cx)
    }
}

impl Render for MultibufferHint {
    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        h_flex()
            .px_2()
            .py_0p5()
            .justify_between()
            .bg(cx.theme().status().info_background.opacity(0.5))
            .border_1()
            .border_color(cx.theme().colors().border_variant)
            .rounded_sm()
            .overflow_hidden()
            .child(
                h_flex()
                    .gap_0p5()
                    .child(
                        h_flex()
                            .gap_2()
                            .child(
                                Icon::new(IconName::Info)
                                    .size(IconSize::XSmall)
                                    .color(Color::Muted),
                            )
                            .child(Label::new(
                                "Edit and save files directly in the results multibuffer!",
                            )),
                    )
                    .child(
                        Button::new("open_docs", "Learn More")
                            .end_icon(
                                Icon::new(IconName::ArrowUpRight)
                                    .size(IconSize::Small)
                                    .color(Color::Muted),
                            )
                            .on_click(move |_event, _, cx| {
                                cx.open_url("https://zed.dev/docs/multibuffers")
                            }),
                    ),
            )
            .child(
                IconButton::new("dismiss", IconName::Close)
                    .shape(IconButtonShape::Square)
                    .icon_size(IconSize::Small)
                    .on_click(cx.listener(|this, _event, _, cx| {
                        this.dismiss(cx);
                        cx.emit(ToolbarItemEvent::ChangeLocation(
                            ToolbarItemLocation::Hidden,
                        ))
                    }))
                    .tooltip(Tooltip::text("Dismiss Hint")),
            )
            .into_any_element()
    }
}
