use anyhow::Result;
use collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use editor::{
    diagnostic_block_renderer,
    display_map::{
        BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, CustomBlockId,
        RenderBlock,
    },
    scroll::Autoscroll,
    Bias, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToPoint,
};
use futures::{
    channel::mpsc::{self, UnboundedSender},
    StreamExt as _,
};
use gpui::{
    actions, div, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
    FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, SharedString,
    Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
};
use language::{
    Buffer, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, OffsetRangeExt, ToOffset,
    ToPoint as _,
};
use lsp::LanguageServerId;
use multi_buffer::{build_excerpt_ranges, ExpandExcerptDirection, MultiBufferRow};
use project::{DiagnosticSummary, Project, ProjectPath};
use settings::Settings;
use std::{
    any::{Any, TypeId},
    cmp::Ordering,
    ops::Range,
    sync::{
        atomic::{self, AtomicBool},
        Arc,
    },
};
use theme::ActiveTheme;
use ui::{h_flex, prelude::*, Icon, IconName, Label};
use util::{debug_panic, ResultExt};
use workspace::{
    item::{BreadcrumbText, Item, ItemEvent, ItemHandle, TabContentParams},
    ItemNavHistory, ToolbarItemLocation, Workspace,
};

use crate::project_diagnostics_settings::ProjectDiagnosticsSettings;
actions!(grouped_diagnostics, [Deploy, ToggleWarnings]);

pub fn init(cx: &mut AppContext) {
    cx.observe_new_views(GroupedDiagnosticsEditor::register)
        .detach();
}

pub struct GroupedDiagnosticsEditor {
    pub project: Model<Project>,
    workspace: WeakView<Workspace>,
    focus_handle: FocusHandle,
    editor: View<Editor>,
    summary: DiagnosticSummary,
    excerpts: Model<MultiBuffer>,
    path_states: Vec<PathState>,
    pub paths_to_update: BTreeSet<(ProjectPath, LanguageServerId)>,
    pub include_warnings: bool,
    context: u32,
    pub update_paths_tx: UnboundedSender<(ProjectPath, Option<LanguageServerId>)>,
    _update_excerpts_task: Task<Result<()>>,
    _subscription: Subscription,
}

struct PathState {
    path: ProjectPath,
    first_excerpt_id: Option<ExcerptId>,
    last_excerpt_id: Option<ExcerptId>,
    diagnostics: Vec<(DiagnosticData, CustomBlockId)>,
}

#[derive(Debug, Clone)]
struct DiagnosticData {
    language_server_id: LanguageServerId,
    is_primary: bool,
    entry: DiagnosticEntry<language::Anchor>,
}

impl DiagnosticData {
    fn diagnostic_entries_equal(&self, other: &DiagnosticData) -> bool {
        self.language_server_id == other.language_server_id
            && self.is_primary == other.is_primary
            && self.entry.range == other.entry.range
            && equal_without_group_ids(&self.entry.diagnostic, &other.entry.diagnostic)
    }
}

// `group_id` can differ between LSP server diagnostics output,
// hence ignore it when checking diagnostics for updates.
fn equal_without_group_ids(a: &language::Diagnostic, b: &language::Diagnostic) -> bool {
    a.source == b.source
        && a.code == b.code
        && a.severity == b.severity
        && a.message == b.message
        && a.is_primary == b.is_primary
        && a.is_disk_based == b.is_disk_based
        && a.is_unnecessary == b.is_unnecessary
}

impl EventEmitter<EditorEvent> for GroupedDiagnosticsEditor {}

impl Render for GroupedDiagnosticsEditor {
    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
        let child = if self.path_states.is_empty() {
            div()
                .bg(cx.theme().colors().editor_background)
                .flex()
                .items_center()
                .justify_center()
                .size_full()
                .child(Label::new("No problems in workspace"))
        } else {
            div().size_full().child(self.editor.clone())
        };

        div()
            .track_focus(&self.focus_handle)
            .when(self.path_states.is_empty(), |el| {
                el.key_context("EmptyPane")
            })
            .size_full()
            .on_action(cx.listener(Self::toggle_warnings))
            .child(child)
    }
}

impl GroupedDiagnosticsEditor {
    fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
        workspace.register_action(Self::deploy);
    }

    fn new_with_context(
        context: u32,
        project_handle: Model<Project>,
        workspace: WeakView<Workspace>,
        cx: &mut ViewContext<Self>,
    ) -> Self {
        let project_event_subscription =
            cx.subscribe(&project_handle, |this, project, event, cx| match event {
                project::Event::DiskBasedDiagnosticsStarted { .. } => {
                    cx.notify();
                }
                project::Event::DiskBasedDiagnosticsFinished { language_server_id } => {
                    log::debug!("disk based diagnostics finished for server {language_server_id}");
                    this.enqueue_update_stale_excerpts(Some(*language_server_id));
                }
                project::Event::DiagnosticsUpdated {
                    language_server_id,
                    path,
                } => {
                    this.paths_to_update
                        .insert((path.clone(), *language_server_id));
                    this.summary = project.read(cx).diagnostic_summary(false, cx);
                    cx.emit(EditorEvent::TitleChanged);

                    if this.editor.focus_handle(cx).contains_focused(cx) || this.focus_handle.contains_focused(cx) {
                        log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. recording change");
                    } else {
                        log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. updating excerpts");
                        this.enqueue_update_stale_excerpts(Some(*language_server_id));
                    }
                }
                _ => {}
            });

        let focus_handle = cx.focus_handle();
        cx.on_focus_in(&focus_handle, |this, cx| this.focus_in(cx))
            .detach();
        cx.on_focus_out(&focus_handle, |this, _event, cx| this.focus_out(cx))
            .detach();

        let excerpts = cx.new_model(|cx| {
            MultiBuffer::new(
                project_handle.read(cx).replica_id(),
                project_handle.read(cx).capability(),
            )
        });
        let editor = cx.new_view(|cx| {
            let mut editor =
                Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), false, cx);
            editor.set_vertical_scroll_margin(5, cx);
            editor
        });
        cx.subscribe(&editor, |this, _editor, event: &EditorEvent, cx| {
            cx.emit(event.clone());
            match event {
                EditorEvent::Focused => {
                    if this.path_states.is_empty() {
                        cx.focus(&this.focus_handle);
                    }
                }
                EditorEvent::Blurred => this.enqueue_update_stale_excerpts(None),
                _ => {}
            }
        })
        .detach();

        let (update_excerpts_tx, mut update_excerpts_rx) = mpsc::unbounded();

        let project = project_handle.read(cx);
        let mut this = Self {
            project: project_handle.clone(),
            context,
            summary: project.diagnostic_summary(false, cx),
            workspace,
            excerpts,
            focus_handle,
            editor,
            path_states: Vec::new(),
            paths_to_update: BTreeSet::new(),
            include_warnings: ProjectDiagnosticsSettings::get_global(cx).include_warnings,
            update_paths_tx: update_excerpts_tx,
            _update_excerpts_task: cx.spawn(move |this, mut cx| async move {
                while let Some((path, language_server_id)) = update_excerpts_rx.next().await {
                    if let Some(buffer) = project_handle
                        .update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))?
                        .await
                        .log_err()
                    {
                        this.update(&mut cx, |this, cx| {
                            this.update_excerpts(path, language_server_id, buffer, cx);
                        })?;
                    }
                }
                anyhow::Ok(())
            }),
            _subscription: project_event_subscription,
        };
        this.enqueue_update_all_excerpts(cx);
        this
    }

    fn new(
        project_handle: Model<Project>,
        workspace: WeakView<Workspace>,
        cx: &mut ViewContext<Self>,
    ) -> Self {
        Self::new_with_context(
            editor::DEFAULT_MULTIBUFFER_CONTEXT,
            project_handle,
            workspace,
            cx,
        )
    }

    fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
        if let Some(existing) = workspace.item_of_type::<GroupedDiagnosticsEditor>(cx) {
            workspace.activate_item(&existing, true, true, cx);
        } else {
            let workspace_handle = cx.view().downgrade();
            let diagnostics = cx.new_view(|cx| {
                GroupedDiagnosticsEditor::new(workspace.project().clone(), workspace_handle, cx)
            });
            workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, cx);
        }
    }

    pub fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
        self.include_warnings = !self.include_warnings;
        self.enqueue_update_all_excerpts(cx);
        cx.notify();
    }

    fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
        if self.focus_handle.is_focused(cx) && !self.path_states.is_empty() {
            self.editor.focus_handle(cx).focus(cx)
        }
    }

    fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
        if !self.focus_handle.is_focused(cx) && !self.editor.focus_handle(cx).is_focused(cx) {
            self.enqueue_update_stale_excerpts(None);
        }
    }

    /// Enqueue an update of all excerpts. Updates all paths that either
    /// currently have diagnostics or are currently present in this view.
    fn enqueue_update_all_excerpts(&mut self, cx: &mut ViewContext<Self>) {
        self.project.update(cx, |project, cx| {
            let mut paths = project
                .diagnostic_summaries(false, cx)
                .map(|(path, _, _)| path)
                .collect::<BTreeSet<_>>();
            paths.extend(self.path_states.iter().map(|state| state.path.clone()));
            for path in paths {
                self.update_paths_tx.unbounded_send((path, None)).unwrap();
            }
        });
    }

    /// Enqueue an update of the excerpts for any path whose diagnostics are known
    /// to have changed. If a language server id is passed, then only the excerpts for
    /// that language server's diagnostics will be updated. Otherwise, all stale excerpts
    /// will be refreshed.
    pub fn enqueue_update_stale_excerpts(&mut self, language_server_id: Option<LanguageServerId>) {
        for (path, server_id) in &self.paths_to_update {
            if language_server_id.map_or(true, |id| id == *server_id) {
                self.update_paths_tx
                    .unbounded_send((path.clone(), Some(*server_id)))
                    .unwrap();
            }
        }
    }

    fn update_excerpts(
        &mut self,
        path_to_update: ProjectPath,
        server_to_update: Option<LanguageServerId>,
        buffer: Model<Buffer>,
        cx: &mut ViewContext<Self>,
    ) {
        self.paths_to_update.retain(|(path, server_id)| {
            *path != path_to_update
                || server_to_update.map_or(false, |to_update| *server_id != to_update)
        });

        // TODO change selections as in the old panel, to the next primary diagnostics
        // TODO make [shift-]f8 to work, jump to the next block group
        let _was_empty = self.path_states.is_empty();
        let path_ix = match self.path_states.binary_search_by(|probe| {
            project::compare_paths((&probe.path.path, true), (&path_to_update.path, true))
        }) {
            Ok(ix) => ix,
            Err(ix) => {
                self.path_states.insert(
                    ix,
                    PathState {
                        path: path_to_update.clone(),
                        diagnostics: Vec::new(),
                        last_excerpt_id: None,
                        first_excerpt_id: None,
                    },
                );
                ix
            }
        };

        let max_severity = if self.include_warnings {
            DiagnosticSeverity::WARNING
        } else {
            DiagnosticSeverity::ERROR
        };

        let excerpt_borders = self.excerpt_borders_for_path(path_ix);
        let path_state = &mut self.path_states[path_ix];
        let buffer_snapshot = buffer.read(cx).snapshot();

        let mut path_update = PathUpdate::new(
            excerpt_borders,
            &buffer_snapshot,
            server_to_update,
            max_severity,
            path_state,
        );
        path_update.prepare_excerpt_data(
            self.context,
            self.excerpts.read(cx).snapshot(cx),
            buffer.read(cx).snapshot(),
            path_state.diagnostics.iter(),
        );
        self.excerpts.update(cx, |multi_buffer, cx| {
            path_update.apply_excerpt_changes(
                path_state,
                self.context,
                buffer_snapshot,
                multi_buffer,
                buffer,
                cx,
            );
        });

        let new_multi_buffer_snapshot = self.excerpts.read(cx).snapshot(cx);
        let blocks_to_insert =
            path_update.prepare_blocks_to_insert(self.editor.clone(), new_multi_buffer_snapshot);

        let new_block_ids = self.editor.update(cx, |editor, cx| {
            editor.remove_blocks(std::mem::take(&mut path_update.blocks_to_remove), None, cx);
            editor.insert_blocks(blocks_to_insert, Some(Autoscroll::fit()), cx)
        });
        path_state.diagnostics = path_update.new_blocks(new_block_ids);

        if self.path_states.is_empty() {
            if self.editor.focus_handle(cx).is_focused(cx) {
                cx.focus(&self.focus_handle);
            }
        } else if self.focus_handle.is_focused(cx) {
            let focus_handle = self.editor.focus_handle(cx);
            cx.focus(&focus_handle);
        }

        #[cfg(test)]
        self.check_invariants(cx);

        cx.notify();
    }

    fn excerpt_borders_for_path(&self, path_ix: usize) -> (Option<ExcerptId>, Option<ExcerptId>) {
        let previous_path_state_ix =
            Some(path_ix.saturating_sub(1)).filter(|&previous_path_ix| previous_path_ix != path_ix);
        let next_path_state_ix = path_ix + 1;
        let start = previous_path_state_ix.and_then(|i| {
            self.path_states[..=i]
                .iter()
                .rev()
                .find_map(|state| state.last_excerpt_id)
        });
        let end = self.path_states[next_path_state_ix..]
            .iter()
            .find_map(|state| state.first_excerpt_id);
        (start, end)
    }

    #[cfg(test)]
    fn check_invariants(&self, cx: &mut ViewContext<Self>) {
        let mut excerpts = Vec::new();
        for (id, buffer, _) in self.excerpts.read(cx).snapshot(cx).excerpts() {
            if let Some(file) = buffer.file() {
                excerpts.push((id, file.path().clone()));
            }
        }

        let mut prev_path = None;
        for (_, path) in &excerpts {
            if let Some(prev_path) = prev_path {
                if path < prev_path {
                    panic!("excerpts are not sorted by path {:?}", excerpts);
                }
            }
            prev_path = Some(path);
        }
    }
}

impl FocusableView for GroupedDiagnosticsEditor {
    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
        self.focus_handle.clone()
    }
}

impl Item for GroupedDiagnosticsEditor {
    type Event = EditorEvent;

    fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) {
        Editor::to_item_events(event, f)
    }

    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
        self.editor.update(cx, |editor, cx| editor.deactivated(cx));
    }

    fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
        self.editor
            .update(cx, |editor, cx| editor.navigate(data, cx))
    }

    fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
        Some("Project Diagnostics".into())
    }

    fn tab_content(&self, params: TabContentParams, _: &WindowContext) -> AnyElement {
        if self.summary.error_count == 0 && self.summary.warning_count == 0 {
            Label::new("No problems")
                .color(params.text_color())
                .into_any_element()
        } else {
            h_flex()
                .gap_1()
                .when(self.summary.error_count > 0, |then| {
                    then.child(
                        h_flex()
                            .gap_1()
                            .child(Icon::new(IconName::XCircle).color(Color::Error))
                            .child(
                                Label::new(self.summary.error_count.to_string())
                                    .color(params.text_color()),
                            ),
                    )
                })
                .when(self.summary.warning_count > 0, |then| {
                    then.child(
                        h_flex()
                            .gap_1()
                            .child(Icon::new(IconName::ExclamationTriangle).color(Color::Warning))
                            .child(
                                Label::new(self.summary.warning_count.to_string())
                                    .color(params.text_color()),
                            ),
                    )
                })
                .into_any_element()
        }
    }

    fn telemetry_event_text(&self) -> Option<&'static str> {
        Some("project diagnostics")
    }

    fn for_each_project_item(
        &self,
        cx: &AppContext,
        f: &mut dyn FnMut(gpui::EntityId, &dyn project::Item),
    ) {
        self.editor.for_each_project_item(cx, f)
    }

    fn is_singleton(&self, _: &AppContext) -> bool {
        false
    }

    fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
        self.editor.update(cx, |editor, _| {
            editor.set_nav_history(Some(nav_history));
        });
    }

    fn clone_on_split(
        &self,
        _workspace_id: Option<workspace::WorkspaceId>,
        cx: &mut ViewContext<Self>,
    ) -> Option<View<Self>>
    where
        Self: Sized,
    {
        Some(cx.new_view(|cx| {
            GroupedDiagnosticsEditor::new(self.project.clone(), self.workspace.clone(), cx)
        }))
    }

    fn is_dirty(&self, cx: &AppContext) -> bool {
        self.excerpts.read(cx).is_dirty(cx)
    }

    fn has_conflict(&self, cx: &AppContext) -> bool {
        self.excerpts.read(cx).has_conflict(cx)
    }

    fn can_save(&self, _: &AppContext) -> bool {
        true
    }

    fn save(
        &mut self,
        format: bool,
        project: Model<Project>,
        cx: &mut ViewContext<Self>,
    ) -> Task<Result<()>> {
        self.editor.save(format, project, cx)
    }

    fn save_as(
        &mut self,
        _: Model<Project>,
        _: ProjectPath,
        _: &mut ViewContext<Self>,
    ) -> Task<Result<()>> {
        unreachable!()
    }

    fn reload(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
        self.editor.reload(project, cx)
    }

    fn act_as_type<'a>(
        &'a self,
        type_id: TypeId,
        self_handle: &'a View<Self>,
        _: &'a AppContext,
    ) -> Option<AnyView> {
        if type_id == TypeId::of::<Self>() {
            Some(self_handle.to_any())
        } else if type_id == TypeId::of::<Editor>() {
            Some(self.editor.to_any())
        } else {
            None
        }
    }

    fn breadcrumb_location(&self) -> ToolbarItemLocation {
        ToolbarItemLocation::PrimaryLeft
    }

    fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
        self.editor.breadcrumbs(theme, cx)
    }

    fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
        self.editor
            .update(cx, |editor, cx| editor.added_to_workspace(workspace, cx));
    }
}

fn compare_data_locations(
    old: &DiagnosticData,
    new: &DiagnosticData,
    snapshot: &BufferSnapshot,
) -> Ordering {
    compare_diagnostics(&old.entry, &new.entry, snapshot)
        .then_with(|| old.language_server_id.cmp(&new.language_server_id))
}

fn compare_diagnostics(
    old: &DiagnosticEntry<language::Anchor>,
    new: &DiagnosticEntry<language::Anchor>,
    snapshot: &BufferSnapshot,
) -> Ordering {
    compare_diagnostic_ranges(&old.range, &new.range, snapshot)
        .then_with(|| old.diagnostic.message.cmp(&new.diagnostic.message))
}

fn compare_diagnostic_ranges(
    old: &Range<language::Anchor>,
    new: &Range<language::Anchor>,
    snapshot: &BufferSnapshot,
) -> Ordering {
    // The diagnostics may point to a previously open Buffer for this file.
    if !old.start.is_valid(snapshot) || !new.start.is_valid(snapshot) {
        return Ordering::Greater;
    }

    old.start
        .to_offset(snapshot)
        .cmp(&new.start.to_offset(snapshot))
        .then_with(|| {
            old.end
                .to_offset(snapshot)
                .cmp(&new.end.to_offset(snapshot))
        })
}

// TODO wrong? What to do here instead?
fn compare_diagnostic_range_edges(
    old: &Range<language::Anchor>,
    new: &Range<language::Anchor>,
    snapshot: &BufferSnapshot,
) -> (Ordering, Ordering) {
    // The diagnostics may point to a previously open Buffer for this file.
    let start_cmp = match (old.start.is_valid(snapshot), new.start.is_valid(snapshot)) {
        (false, false) => old.start.offset.cmp(&new.start.offset),
        (false, true) => Ordering::Greater,
        (true, false) => Ordering::Less,
        (true, true) => old.start.cmp(&new.start, snapshot),
    };

    let end_cmp = old
        .end
        .to_offset(snapshot)
        .cmp(&new.end.to_offset(snapshot));
    (start_cmp, end_cmp)
}

#[derive(Debug)]
struct PathUpdate {
    path_excerpts_borders: (Option<ExcerptId>, Option<ExcerptId>),
    latest_excerpt_id: ExcerptId,
    new_diagnostics: Vec<(DiagnosticData, Option<CustomBlockId>)>,
    diagnostics_by_row_label: BTreeMap<MultiBufferRow, (editor::Anchor, Vec<usize>)>,
    blocks_to_remove: HashSet<CustomBlockId>,
    unchanged_blocks: HashMap<usize, CustomBlockId>,
    excerpts_with_new_diagnostics: HashSet<ExcerptId>,
    excerpts_to_remove: Vec<ExcerptId>,
    excerpt_expands: HashMap<(ExpandExcerptDirection, u32), Vec<ExcerptId>>,
    excerpts_to_add: HashMap<ExcerptId, Vec<Range<language::Anchor>>>,
    first_excerpt_id: Option<ExcerptId>,
    last_excerpt_id: Option<ExcerptId>,
}

impl PathUpdate {
    fn new(
        path_excerpts_borders: (Option<ExcerptId>, Option<ExcerptId>),
        buffer_snapshot: &BufferSnapshot,
        server_to_update: Option<LanguageServerId>,
        max_severity: DiagnosticSeverity,
        path_state: &PathState,
    ) -> Self {
        let mut blocks_to_remove = HashSet::default();
        let mut removed_groups = HashSet::default();
        let mut new_diagnostics = path_state
            .diagnostics
            .iter()
            .filter(|(diagnostic_data, _)| {
                server_to_update.map_or(true, |server_id| {
                    diagnostic_data.language_server_id != server_id
                })
            })
            .filter(|(diagnostic_data, block_id)| {
                let diagnostic = &diagnostic_data.entry.diagnostic;
                let retain = !diagnostic.is_primary || diagnostic.severity <= max_severity;
                if !retain {
                    removed_groups.insert(diagnostic.group_id);
                    blocks_to_remove.insert(*block_id);
                }
                retain
            })
            .map(|(diagnostic, block_id)| (diagnostic.clone(), Some(*block_id)))
            .collect::<Vec<_>>();
        new_diagnostics.retain(|(diagnostic_data, block_id)| {
            let retain = !removed_groups.contains(&diagnostic_data.entry.diagnostic.group_id);
            if !retain {
                if let Some(block_id) = block_id {
                    blocks_to_remove.insert(*block_id);
                }
            }
            retain
        });
        for (server_id, group) in buffer_snapshot
            .diagnostic_groups(server_to_update)
            .into_iter()
            .filter(|(_, group)| {
                group.entries[group.primary_ix].diagnostic.severity <= max_severity
            })
        {
            for (diagnostic_index, diagnostic) in group.entries.iter().enumerate() {
                let new_data = DiagnosticData {
                    language_server_id: server_id,
                    is_primary: diagnostic_index == group.primary_ix,
                    entry: diagnostic.clone(),
                };
                let (Ok(i) | Err(i)) = new_diagnostics.binary_search_by(|probe| {
                    compare_data_locations(&probe.0, &new_data, &buffer_snapshot)
                });
                new_diagnostics.insert(i, (new_data, None));
            }
        }

        let latest_excerpt_id = path_excerpts_borders.0.unwrap_or_else(|| ExcerptId::min());
        Self {
            latest_excerpt_id,
            path_excerpts_borders,
            new_diagnostics,
            blocks_to_remove,
            diagnostics_by_row_label: BTreeMap::new(),
            excerpts_to_remove: Vec::new(),
            excerpts_with_new_diagnostics: HashSet::default(),
            unchanged_blocks: HashMap::default(),
            excerpts_to_add: HashMap::default(),
            excerpt_expands: HashMap::default(),
            first_excerpt_id: None,
            last_excerpt_id: None,
        }
    }

    fn prepare_excerpt_data<'a>(
        &'a mut self,
        context: u32,
        multi_buffer_snapshot: MultiBufferSnapshot,
        buffer_snapshot: BufferSnapshot,
        current_diagnostics: impl Iterator<Item = &'a (DiagnosticData, CustomBlockId)> + 'a,
    ) {
        let mut current_diagnostics = current_diagnostics.fuse().peekable();
        let mut excerpts_to_expand =
            HashMap::<ExcerptId, HashMap<ExpandExcerptDirection, u32>>::default();
        let mut current_excerpts = path_state_excerpts(
            self.path_excerpts_borders.0,
            self.path_excerpts_borders.1,
            &multi_buffer_snapshot,
        )
        .fuse()
        .peekable();

        for (diagnostic_index, (new_diagnostic, existing_block)) in
            self.new_diagnostics.iter().enumerate()
        {
            if let Some(existing_block) = existing_block {
                self.unchanged_blocks
                    .insert(diagnostic_index, *existing_block);
            }

            loop {
                match current_excerpts.peek() {
                    None => {
                        let excerpt_ranges = self
                            .excerpts_to_add
                            .entry(self.latest_excerpt_id)
                            .or_default();
                        let new_range = new_diagnostic.entry.range.clone();
                        let (Ok(i) | Err(i)) = excerpt_ranges.binary_search_by(|probe| {
                            compare_diagnostic_ranges(probe, &new_range, &buffer_snapshot)
                        });
                        excerpt_ranges.insert(i, new_range);
                        break;
                    }
                    Some((current_excerpt_id, _, current_excerpt_range)) => {
                        match compare_diagnostic_range_edges(
                            &current_excerpt_range.context,
                            &new_diagnostic.entry.range,
                            &buffer_snapshot,
                        ) {
                            /*
                                  new_s new_e
                            ----[---->><<----]--
                             cur_s         cur_e
                            */
                            (
                                Ordering::Less | Ordering::Equal,
                                Ordering::Greater | Ordering::Equal,
                            ) => {
                                self.excerpts_with_new_diagnostics
                                    .insert(*current_excerpt_id);
                                if self.first_excerpt_id.is_none() {
                                    self.first_excerpt_id = Some(*current_excerpt_id);
                                }
                                self.last_excerpt_id = Some(*current_excerpt_id);
                                break;
                            }
                            /*
                                  cur_s cur_e
                            ---->>>>>[--]<<<<<--
                             new_s         new_e
                            */
                            (
                                Ordering::Greater | Ordering::Equal,
                                Ordering::Less | Ordering::Equal,
                            ) => {
                                let expand_up = current_excerpt_range
                                    .context
                                    .start
                                    .to_point(&buffer_snapshot)
                                    .row
                                    .saturating_sub(
                                        new_diagnostic
                                            .entry
                                            .range
                                            .start
                                            .to_point(&buffer_snapshot)
                                            .row,
                                    );
                                let expand_down = new_diagnostic
                                    .entry
                                    .range
                                    .end
                                    .to_point(&buffer_snapshot)
                                    .row
                                    .saturating_sub(
                                        current_excerpt_range
                                            .context
                                            .end
                                            .to_point(&buffer_snapshot)
                                            .row,
                                    );
                                let expand_value = excerpts_to_expand
                                    .entry(*current_excerpt_id)
                                    .or_default()
                                    .entry(ExpandExcerptDirection::UpAndDown)
                                    .or_default();
                                *expand_value = (*expand_value).max(expand_up).max(expand_down);
                                self.excerpts_with_new_diagnostics
                                    .insert(*current_excerpt_id);
                                if self.first_excerpt_id.is_none() {
                                    self.first_excerpt_id = Some(*current_excerpt_id);
                                }
                                self.last_excerpt_id = Some(*current_excerpt_id);
                                break;
                            }
                            /*
                                    new_s   new_e
                                     >       <
                            ----[---->>>]<<<<<--
                             cur_s    cur_e

                            or
                                      new_s new_e
                                        >    <
                            ----[----]-->>><<<--
                             cur_s cur_e
                            */
                            (Ordering::Less, Ordering::Less) => {
                                if current_excerpt_range
                                    .context
                                    .end
                                    .cmp(&new_diagnostic.entry.range.start, &buffer_snapshot)
                                    .is_ge()
                                {
                                    let expand_down = new_diagnostic
                                        .entry
                                        .range
                                        .end
                                        .to_point(&buffer_snapshot)
                                        .row
                                        .saturating_sub(
                                            current_excerpt_range
                                                .context
                                                .end
                                                .to_point(&buffer_snapshot)
                                                .row,
                                        );
                                    let expand_value = excerpts_to_expand
                                        .entry(*current_excerpt_id)
                                        .or_default()
                                        .entry(ExpandExcerptDirection::Down)
                                        .or_default();
                                    *expand_value = (*expand_value).max(expand_down);
                                    self.excerpts_with_new_diagnostics
                                        .insert(*current_excerpt_id);
                                    if self.first_excerpt_id.is_none() {
                                        self.first_excerpt_id = Some(*current_excerpt_id);
                                    }
                                    self.last_excerpt_id = Some(*current_excerpt_id);
                                    break;
                                } else if !self
                                    .excerpts_with_new_diagnostics
                                    .contains(current_excerpt_id)
                                {
                                    self.excerpts_to_remove.push(*current_excerpt_id);
                                }
                            }
                            /*
                                  cur_s      cur_e
                            ---->>>>>[<<<<----]--
                                >        <
                               new_s    new_e

                            or
                                      cur_s cur_e
                            ---->>><<<--[----]--
                                >    <
                               new_s new_e
                            */
                            (Ordering::Greater, Ordering::Greater) => {
                                if current_excerpt_range
                                    .context
                                    .start
                                    .cmp(&new_diagnostic.entry.range.end, &buffer_snapshot)
                                    .is_le()
                                {
                                    let expand_up = current_excerpt_range
                                        .context
                                        .start
                                        .to_point(&buffer_snapshot)
                                        .row
                                        .saturating_sub(
                                            new_diagnostic
                                                .entry
                                                .range
                                                .start
                                                .to_point(&buffer_snapshot)
                                                .row,
                                        );
                                    let expand_value = excerpts_to_expand
                                        .entry(*current_excerpt_id)
                                        .or_default()
                                        .entry(ExpandExcerptDirection::Up)
                                        .or_default();
                                    *expand_value = (*expand_value).max(expand_up);
                                    self.excerpts_with_new_diagnostics
                                        .insert(*current_excerpt_id);
                                    if self.first_excerpt_id.is_none() {
                                        self.first_excerpt_id = Some(*current_excerpt_id);
                                    }
                                    self.last_excerpt_id = Some(*current_excerpt_id);
                                    break;
                                } else {
                                    let excerpt_ranges = self
                                        .excerpts_to_add
                                        .entry(self.latest_excerpt_id)
                                        .or_default();
                                    let new_range = new_diagnostic.entry.range.clone();
                                    let (Ok(i) | Err(i)) =
                                        excerpt_ranges.binary_search_by(|probe| {
                                            compare_diagnostic_ranges(
                                                probe,
                                                &new_range,
                                                &buffer_snapshot,
                                            )
                                        });
                                    excerpt_ranges.insert(i, new_range);
                                    break;
                                }
                            }
                        }
                        if let Some((next_id, ..)) = current_excerpts.next() {
                            self.latest_excerpt_id = next_id;
                        }
                    }
                }
            }

            loop {
                match current_diagnostics.peek() {
                    None => break,
                    Some((current_diagnostic, current_block)) => {
                        match compare_data_locations(
                            current_diagnostic,
                            new_diagnostic,
                            &buffer_snapshot,
                        ) {
                            Ordering::Less => {
                                self.blocks_to_remove.insert(*current_block);
                            }
                            Ordering::Equal => {
                                if current_diagnostic.diagnostic_entries_equal(&new_diagnostic) {
                                    self.unchanged_blocks
                                        .insert(diagnostic_index, *current_block);
                                } else {
                                    self.blocks_to_remove.insert(*current_block);
                                }
                                let _ = current_diagnostics.next();
                                break;
                            }
                            Ordering::Greater => break,
                        }
                        let _ = current_diagnostics.next();
                    }
                }
            }
        }

        self.excerpts_to_remove.retain(|excerpt_id| {
            !self.excerpts_with_new_diagnostics.contains(excerpt_id)
                && !excerpts_to_expand.contains_key(excerpt_id)
        });
        self.excerpts_to_remove.extend(
            current_excerpts
                .filter(|(excerpt_id, ..)| {
                    !self.excerpts_with_new_diagnostics.contains(excerpt_id)
                        && !excerpts_to_expand.contains_key(excerpt_id)
                })
                .map(|(excerpt_id, ..)| excerpt_id),
        );
        let mut excerpt_expands = HashMap::default();
        for (excerpt_id, directions) in excerpts_to_expand {
            let excerpt_expand = if directions.len() > 1 {
                Some((
                    ExpandExcerptDirection::UpAndDown,
                    directions
                        .values()
                        .max()
                        .copied()
                        .unwrap_or_default()
                        .max(context),
                ))
            } else {
                directions
                    .into_iter()
                    .next()
                    .map(|(direction, expand)| (direction, expand.max(context)))
            };
            if let Some(expand) = excerpt_expand {
                excerpt_expands
                    .entry(expand)
                    .or_insert_with(|| Vec::new())
                    .push(excerpt_id);
            }
        }
        self.blocks_to_remove
            .extend(current_diagnostics.map(|(_, block_id)| block_id));
    }

    fn apply_excerpt_changes(
        &mut self,
        path_state: &mut PathState,
        context: u32,
        buffer_snapshot: BufferSnapshot,
        multi_buffer: &mut MultiBuffer,
        buffer: Model<Buffer>,
        cx: &mut gpui::ModelContext<MultiBuffer>,
    ) {
        let max_point = buffer_snapshot.max_point();
        for (after_excerpt_id, ranges) in std::mem::take(&mut self.excerpts_to_add) {
            let ranges = ranges
                .into_iter()
                .map(|range| {
                    let mut extended_point_range = range.to_point(&buffer_snapshot);
                    extended_point_range.start.row =
                        extended_point_range.start.row.saturating_sub(context);
                    extended_point_range.start.column = 0;
                    extended_point_range.end.row =
                        (extended_point_range.end.row + context).min(max_point.row);
                    extended_point_range.end.column = u32::MAX;
                    let extended_start =
                        buffer_snapshot.clip_point(extended_point_range.start, Bias::Left);
                    let extended_end =
                        buffer_snapshot.clip_point(extended_point_range.end, Bias::Right);
                    extended_start..extended_end
                })
                .collect::<Vec<_>>();
            let (joined_ranges, _) = build_excerpt_ranges(&buffer_snapshot, &ranges, context);
            let excerpts = multi_buffer.insert_excerpts_after(
                after_excerpt_id,
                buffer.clone(),
                joined_ranges,
                cx,
            );
            if self.first_excerpt_id.is_none() {
                self.first_excerpt_id = excerpts.first().copied();
            }
            self.last_excerpt_id = excerpts.last().copied();
        }
        for ((direction, line_count), excerpts) in std::mem::take(&mut self.excerpt_expands) {
            multi_buffer.expand_excerpts(excerpts, line_count, direction, cx);
        }
        multi_buffer.remove_excerpts(std::mem::take(&mut self.excerpts_to_remove), cx);
        path_state.first_excerpt_id = self.first_excerpt_id;
        path_state.last_excerpt_id = self.last_excerpt_id;
    }

    fn prepare_blocks_to_insert(
        &mut self,
        editor: View<Editor>,
        multi_buffer_snapshot: MultiBufferSnapshot,
    ) -> Vec<BlockProperties<editor::Anchor>> {
        let mut updated_excerpts = path_state_excerpts(
            self.path_excerpts_borders.0,
            self.path_excerpts_borders.1,
            &multi_buffer_snapshot,
        )
        .fuse()
        .peekable();
        let mut used_labels = BTreeMap::new();
        self.diagnostics_by_row_label = self.new_diagnostics.iter().enumerate().fold(
            BTreeMap::new(),
            |mut diagnostics_by_row_label, (diagnostic_index, (diagnostic, existing_block))| {
                let new_diagnostic = &diagnostic.entry;
                let block_position = new_diagnostic.range.start;
                let excerpt_id = loop {
                    match updated_excerpts.peek() {
                        None => break None,
                        Some((excerpt_id, excerpt_buffer_snapshot, excerpt_range)) => {
                            let excerpt_range = &excerpt_range.context;
                            match block_position.cmp(&excerpt_range.start, excerpt_buffer_snapshot)
                            {
                                Ordering::Less => break None,
                                Ordering::Equal | Ordering::Greater => match block_position
                                    .cmp(&excerpt_range.end, excerpt_buffer_snapshot)
                                {
                                    Ordering::Equal | Ordering::Less => break Some(*excerpt_id),
                                    Ordering::Greater => {
                                        let _ = updated_excerpts.next();
                                    }
                                },
                            }
                        }
                    }
                };

                let Some(position_in_multi_buffer) = excerpt_id.and_then(|excerpt_id| {
                    multi_buffer_snapshot.anchor_in_excerpt(excerpt_id, block_position)
                }) else {
                    return diagnostics_by_row_label;
                };

                let multi_buffer_row = MultiBufferRow(
                    position_in_multi_buffer
                        .to_point(&multi_buffer_snapshot)
                        .row,
                );

                let grouped_diagnostics = &mut diagnostics_by_row_label
                    .entry(multi_buffer_row)
                    .or_insert_with(|| (position_in_multi_buffer, Vec::new()))
                    .1;
                let new_label = used_labels
                    .entry(multi_buffer_row)
                    .or_insert_with(|| HashSet::default())
                    .insert((
                        new_diagnostic.diagnostic.source.as_deref(),
                        new_diagnostic.diagnostic.message.as_str(),
                    ));

                if !new_label || !grouped_diagnostics.is_empty() {
                    if let Some(existing_block) = existing_block {
                        self.blocks_to_remove.insert(*existing_block);
                    }
                    if let Some(block_id) = self.unchanged_blocks.remove(&diagnostic_index) {
                        self.blocks_to_remove.insert(block_id);
                    }
                }
                if new_label {
                    let (Ok(i) | Err(i)) = grouped_diagnostics.binary_search_by(|&probe| {
                        let a = &self.new_diagnostics[probe].0.entry.diagnostic;
                        let b = &self.new_diagnostics[diagnostic_index].0.entry.diagnostic;
                        a.group_id
                            .cmp(&b.group_id)
                            .then_with(|| a.is_primary.cmp(&b.is_primary).reverse())
                            .then_with(|| a.severity.cmp(&b.severity))
                    });
                    grouped_diagnostics.insert(i, diagnostic_index);
                }

                diagnostics_by_row_label
            },
        );

        self.diagnostics_by_row_label
            .values()
            .filter_map(|(earliest_in_row_position, diagnostics_at_line)| {
                let earliest_in_row_position = *earliest_in_row_position;
                match diagnostics_at_line.len() {
                    0 => None,
                    len => {
                        if len == 1 {
                            let i = diagnostics_at_line.first().copied()?;
                            if self.unchanged_blocks.contains_key(&i) {
                                return None;
                            }
                        }
                        let lines_in_first_message = diagnostic_text_lines(
                            &self
                                .new_diagnostics
                                .get(diagnostics_at_line.first().copied()?)?
                                .0
                                .entry
                                .diagnostic,
                        );
                        let folded_block_height = lines_in_first_message.clamp(1, 2);
                        let diagnostics_to_render = Arc::new(
                            diagnostics_at_line
                                .iter()
                                .filter_map(|&index| self.new_diagnostics.get(index))
                                .map(|(diagnostic_data, _)| {
                                    diagnostic_data.entry.diagnostic.clone()
                                })
                                .collect::<Vec<_>>(),
                        );
                        Some(BlockProperties {
                            position: earliest_in_row_position,
                            height: folded_block_height,
                            style: BlockStyle::Sticky,
                            render: render_same_line_diagnostics(
                                Arc::new(AtomicBool::new(false)),
                                diagnostics_to_render,
                                editor.clone(),
                                folded_block_height,
                            ),
                            disposition: BlockDisposition::Above,
                        })
                    }
                }
            })
            .collect()
    }

    fn new_blocks(
        mut self,
        new_block_ids: Vec<CustomBlockId>,
    ) -> Vec<(DiagnosticData, CustomBlockId)> {
        let mut new_block_ids = new_block_ids.into_iter().fuse();
        for (_, (_, grouped_diagnostics)) in self.diagnostics_by_row_label {
            let mut created_block_id = None;
            match grouped_diagnostics.len() {
                0 => {
                    debug_panic!("Unexpected empty diagnostics group");
                    continue;
                }
                1 => {
                    let index = grouped_diagnostics[0];
                    if let Some(&block_id) = self.unchanged_blocks.get(&index) {
                        self.new_diagnostics[index].1 = Some(block_id);
                    } else {
                        let Some(block_id) =
                            created_block_id.get_or_insert_with(|| new_block_ids.next())
                        else {
                            debug_panic!("Expected a new block for each new diagnostic");
                            continue;
                        };
                        self.new_diagnostics[index].1 = Some(*block_id);
                    }
                }
                _ => {
                    let Some(block_id) =
                        created_block_id.get_or_insert_with(|| new_block_ids.next())
                    else {
                        debug_panic!("Expected a new block for each new diagnostic group");
                        continue;
                    };
                    for i in grouped_diagnostics {
                        self.new_diagnostics[i].1 = Some(*block_id);
                    }
                }
            }
        }

        self.new_diagnostics
            .into_iter()
            .filter_map(|(diagnostic, block_id)| Some((diagnostic, block_id?)))
            .collect()
    }
}

fn render_same_line_diagnostics(
    expanded: Arc<AtomicBool>,
    diagnostics: Arc<Vec<language::Diagnostic>>,
    editor_handle: View<Editor>,
    folded_block_height: u8,
) -> RenderBlock {
    Box::new(move |cx: &mut BlockContext| {
        let block_id = match cx.block_id {
            BlockId::Custom(block_id) => block_id,
            _ => {
                debug_panic!("Expected a block id for the diagnostics block");
                return div().into_any_element();
            }
        };
        let Some(first_diagnostic) = diagnostics.first() else {
            debug_panic!("Expected at least one diagnostic");
            return div().into_any_element();
        };
        let button_expanded = expanded.clone();
        let expanded = expanded.load(atomic::Ordering::Acquire);
        let expand_label = if expanded { '-' } else { '+' };
        let first_diagnostics_height = diagnostic_text_lines(first_diagnostic);
        let extra_diagnostics = diagnostics.len() - 1;
        let toggle_expand_label =
            if folded_block_height == first_diagnostics_height && extra_diagnostics == 0 {
                None
            } else if extra_diagnostics > 0 {
                Some(format!("{expand_label}{extra_diagnostics}"))
            } else {
                Some(expand_label.to_string())
            };

        let expanded_block_height = diagnostics
            .iter()
            .map(|diagnostic| diagnostic_text_lines(diagnostic))
            .sum::<u8>();
        let editor_handle = editor_handle.clone();
        let parent = h_flex()
            .items_start()
            .child(v_flex().size_full().when_some_else(
                toggle_expand_label,
                |parent, label| {
                    parent.child(Button::new(cx.block_id, label).on_click({
                        let diagnostics = Arc::clone(&diagnostics);
                        move |_, cx| {
                            let new_expanded = !expanded;
                            button_expanded.store(new_expanded, atomic::Ordering::Release);
                            let new_size = if new_expanded {
                                expanded_block_height
                            } else {
                                folded_block_height
                            };
                            editor_handle.update(cx, |editor, cx| {
                                editor.replace_blocks(
                                    HashMap::from_iter(Some((
                                        block_id,
                                        (
                                            Some(new_size),
                                            render_same_line_diagnostics(
                                                Arc::clone(&button_expanded),
                                                Arc::clone(&diagnostics),
                                                editor_handle.clone(),
                                                folded_block_height,
                                            ),
                                        ),
                                    ))),
                                    None,
                                    cx,
                                )
                            });
                        }
                    }))
                },
                |parent| {
                    parent.child(
                        h_flex()
                            .size(IconSize::default().rems())
                            .invisible()
                            .flex_none(),
                    )
                },
            ));
        let max_message_rows = if expanded {
            None
        } else {
            Some(folded_block_height)
        };
        let mut renderer =
            diagnostic_block_renderer(first_diagnostic.clone(), max_message_rows, false, true);
        let mut diagnostics_element = v_flex();
        diagnostics_element = diagnostics_element.child(renderer(cx));
        if expanded {
            for diagnostic in diagnostics.iter().skip(1) {
                let mut renderer = diagnostic_block_renderer(diagnostic.clone(), None, false, true);
                diagnostics_element = diagnostics_element.child(renderer(cx));
            }
        }
        parent.child(diagnostics_element).into_any_element()
    })
}

fn diagnostic_text_lines(diagnostic: &language::Diagnostic) -> u8 {
    diagnostic.message.matches('\n').count() as u8 + 1
}

fn path_state_excerpts(
    after_excerpt_id: Option<ExcerptId>,
    before_excerpt_id: Option<ExcerptId>,
    multi_buffer_snapshot: &editor::MultiBufferSnapshot,
) -> impl Iterator<Item = (ExcerptId, &BufferSnapshot, ExcerptRange<language::Anchor>)> {
    multi_buffer_snapshot
        .excerpts()
        .skip_while(move |&(excerpt_id, ..)| match after_excerpt_id {
            Some(after_excerpt_id) => after_excerpt_id != excerpt_id,
            None => false,
        })
        .filter(move |&(excerpt_id, ..)| after_excerpt_id != Some(excerpt_id))
        .take_while(move |&(excerpt_id, ..)| match before_excerpt_id {
            Some(before_excerpt_id) => before_excerpt_id != excerpt_id,
            None => true,
        })
}
