use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main};
use editor::{EditorStyle, MultiBuffer, display_map::*};
use gpui::{AppContext as _, HighlightStyle, Hsla, TestDispatcher, font, px};
use itertools::Itertools;
use multi_buffer::MultiBufferOffset;
use project::project_settings::DiagnosticSeverity;
use rand::{Rng, SeedableRng, rngs::StdRng};
use settings::SettingsStore;
use std::{num::NonZeroU32, time::Duration};
use text::Bias;
use util::RandomCharIter;

fn to_tab_point_benchmark(c: &mut Criterion) {
    let dispatcher = TestDispatcher::new(1);
    let cx = gpui::TestAppContext::build(dispatcher, None);

    let create_tab_map = |length: usize| {
        let mut rng = StdRng::seed_from_u64(1);
        let text = RandomCharIter::new(&mut rng)
            .take(length)
            .collect::<String>();
        let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));

        let buffer_snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
        use editor::display_map::*;
        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot.clone());
        let fold_point = fold_snapshot.to_fold_point(
            inlay_snapshot.to_point(InlayOffset(
                rng.random_range(MultiBufferOffset(0)..MultiBufferOffset(length)),
            )),
            Bias::Left,
        );
        let (_, snapshot) = TabMap::new(fold_snapshot, NonZeroU32::new(4).unwrap());

        (length, snapshot, fold_point)
    };

    let inputs = [1024].into_iter().map(create_tab_map).collect_vec();

    let mut group = c.benchmark_group("To tab point");

    for (batch_size, snapshot, fold_point) in inputs {
        group.bench_with_input(
            BenchmarkId::new("to_tab_point", batch_size),
            &snapshot,
            |bench, snapshot| {
                bench.iter(|| {
                    snapshot.fold_point_to_tab_point(fold_point);
                });
            },
        );
    }

    group.finish();
}

fn to_fold_point_benchmark(c: &mut Criterion) {
    let dispatcher = TestDispatcher::new(1);
    let cx = gpui::TestAppContext::build(dispatcher, None);

    let create_tab_map = |length: usize| {
        let mut rng = StdRng::seed_from_u64(1);
        let text = RandomCharIter::new(&mut rng)
            .take(length)
            .collect::<String>();
        let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));

        let buffer_snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
        use editor::display_map::*;
        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot.clone());

        let fold_point = fold_snapshot.to_fold_point(
            inlay_snapshot.to_point(InlayOffset(
                rng.random_range(MultiBufferOffset(0)..MultiBufferOffset(length)),
            )),
            Bias::Left,
        );

        let (_, snapshot) = TabMap::new(fold_snapshot, NonZeroU32::new(4).unwrap());
        let tab_point = snapshot.fold_point_to_tab_point(fold_point);

        (length, snapshot, tab_point)
    };

    let inputs = [1024].into_iter().map(create_tab_map).collect_vec();

    let mut group = c.benchmark_group("To fold point");

    for (batch_size, snapshot, tab_point) in inputs {
        group.bench_with_input(
            BenchmarkId::new("to_fold_point", batch_size),
            &snapshot,
            |bench, snapshot| {
                bench.iter(|| {
                    snapshot.tab_point_to_fold_point(tab_point, Bias::Left);
                });
            },
        );
    }

    group.finish();
}

fn create_highlight_endpoints_benchmark(c: &mut Criterion) {
    const LINE_COUNT: usize = 20_000;
    const LINE_VIEW_PORT_COUNT: usize = 100;
    const HIGHLIGHTS_PER_LINE: usize = 4;

    let dispatcher = TestDispatcher::new(1);
    let mut cx = gpui::TestAppContext::build(dispatcher, None);
    cx.update(|cx| {
        let store = SettingsStore::test(cx);
        cx.set_global(store);
        editor::init(cx);
    });

    let mut text = String::new();
    let mut highlight_ranges = Vec::with_capacity(LINE_COUNT * HIGHLIGHTS_PER_LINE);
    for line in 0..LINE_COUNT {
        text.push_str("fn item_");
        text.push_str(&format!("{line:05}"));
        text.push_str("() { ");

        let start = text.len();
        text.push_str("alpha_highlight");
        highlight_ranges.push(MultiBufferOffset(start)..MultiBufferOffset(text.len()));

        text.push_str(" + ");
        let start = text.len();
        text.push_str("beta_highlight");
        highlight_ranges.push(MultiBufferOffset(start)..MultiBufferOffset(text.len()));

        text.push_str(" + ");
        let start = text.len();
        text.push_str("gamma_highlight");
        highlight_ranges.push(MultiBufferOffset(start)..MultiBufferOffset(text.len()));

        text.push_str(" + ");
        let start = text.len();
        text.push_str("delta_highlight");
        highlight_ranges.push(MultiBufferOffset(start)..MultiBufferOffset(text.len()));

        text.push_str("; }\n");
    }

    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
    let buffer_snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
    let highlight_ranges = highlight_ranges
        .into_iter()
        .map(|range| {
            buffer_snapshot.anchor_before(range.start)..buffer_snapshot.anchor_before(range.end)
        })
        .collect();

    let map = cx.new(|cx| {
        DisplayMap::new(
            buffer,
            font("Courier"),
            px(16.0),
            None,
            1,
            1,
            FoldPlaceholder::default(),
            DiagnosticSeverity::Warning,
            cx,
        )
    });
    cx.update(|cx| {
        map.update(cx, |map, cx| {
            map.highlight_text(
                HighlightKey::Editor,
                highlight_ranges,
                HighlightStyle {
                    color: Some(Hsla::blue()),
                    ..Default::default()
                },
                false,
                cx,
            );
        });
    });
    let snapshot = cx.update(|cx| map.update(cx, |map, cx| map.snapshot(cx)));

    let mut group = c.benchmark_group("Create highlight endpoints");
    group.sample_size(10);
    group.measurement_time(Duration::from_secs(10));
    group.bench_with_input(
        BenchmarkId::new("text_highlights", LINE_VIEW_PORT_COUNT),
        &snapshot,
        |bench, snapshot| {
            bench.iter(|| {
                black_box(snapshot.chunks(
                    DisplayRow(400)..DisplayRow(400 + LINE_VIEW_PORT_COUNT as u32),
                    language::LanguageAwareStyling {
                        tree_sitter: false,
                        diagnostics: false,
                    },
                    Default::default(),
                ));
            });
        },
    );
    group.finish();
}

fn highlighted_chunks_benchmark(c: &mut Criterion) {
    const LINE_COUNT: usize = 500;

    let dispatcher = TestDispatcher::new(1);
    let mut cx = gpui::TestAppContext::build(dispatcher, None);
    cx.update(|cx| {
        let store = SettingsStore::test(cx);
        cx.set_global(store);
        editor::init(cx);
    });

    let corpora = [
        (
            "ascii",
            "    let chunks = snapshot.highlighted_chunks(rows.clone(), language_aware, style);",
        ),
        (
            "unicode",
            "の設定を変更する — émojis 🧑\u{200d}✈\u{fe0f} und Ümläute überall, здесь тоже текст",
        ),
        (
            "sparse_invisibles",
            "normal text here\u{200b}and some more text that goes on for a while without issues",
        ),
        (
            "dense_invisibles",
            "a\u{200b}b\u{ad}c\u{2060}d\u{feff}e\u{200b}f\u{ad}g\u{2060}h",
        ),
    ];

    let mut group = c.benchmark_group("Highlighted chunks");
    for (name, line) in corpora {
        let text = std::iter::repeat_n(line, LINE_COUNT)
            .collect::<Vec<_>>()
            .join("\n");
        let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
        let map = cx.new(|cx| {
            DisplayMap::new(
                buffer,
                font("Courier"),
                px(16.0),
                None,
                1,
                1,
                FoldPlaceholder::default(),
                DiagnosticSeverity::Warning,
                cx,
            )
        });
        let snapshot = cx.update(|cx| map.update(cx, |map, cx| map.snapshot(cx)));
        let editor_style = EditorStyle::default();
        group.bench_with_input(
            BenchmarkId::new("highlighted_chunks", name),
            &snapshot,
            |bench, snapshot| {
                bench.iter(|| {
                    let mut total_len = 0usize;
                    let chunks = snapshot.highlighted_chunks(
                        DisplayRow(0)..DisplayRow(LINE_COUNT as u32),
                        language::LanguageAwareStyling {
                            tree_sitter: false,
                            diagnostics: false,
                        },
                        &editor_style,
                    );
                    for chunk in chunks {
                        total_len += black_box(chunk.text).len();
                    }
                    black_box(total_len);
                });
            },
        );
    }
    group.finish();
}

criterion_group!(
    benches,
    to_tab_point_benchmark,
    to_fold_point_benchmark,
    create_highlight_endpoints_benchmark,
    highlighted_chunks_benchmark
);
criterion_main!(benches);
