use std::{path::PathBuf, sync::Arc};

use editor::{
    Editor, EditorMode, MultiBuffer,
    actions::{DeleteToPreviousWordStart, SelectAll, SplitSelectionIntoLines},
};
use gpui::{App, AppContext as _, BenchAppContext, BorrowAppContext as _, Focusable as _};
use indoc::{formatdoc, indoc};
use language::{Buffer, Capability, DiskState, File, LocalFile};
use rand::{Rng as _, SeedableRng as _, rngs::StdRng};
use settings::{LocalSettingsKind, LocalSettingsPath, SettingsStore, WorktreeId};
use util::{RandomCharIter, paths::PathStyle, rel_path::RelPath};
use zed_actions::editor::{MoveDown, MoveUp};

struct BenchFile {
    path: Arc<RelPath>,
}

impl File for BenchFile {
    fn as_local(&self) -> Option<&dyn LocalFile> {
        None
    }

    fn disk_state(&self) -> DiskState {
        DiskState::New
    }

    fn path(&self) -> &Arc<RelPath> {
        &self.path
    }

    fn full_path(&self, _: &App) -> PathBuf {
        PathBuf::from("root").join(self.path.as_std_path())
    }

    fn path_style(&self, _: &App) -> PathStyle {
        PathStyle::local()
    }

    fn file_name<'a>(&'a self, _: &'a App) -> &'a str {
        self.path.file_name().unwrap_or("root")
    }

    fn worktree_id(&self, _: &App) -> WorktreeId {
        WorktreeId::from_usize(0)
    }

    fn to_proto(&self, _: &App) -> rpc::proto::File {
        unimplemented!()
    }

    fn is_private(&self) -> bool {
        false
    }
}

#[gpui::bench(
    inputs = multi_cursor_line_counts(),
    group = "Multi-cursor input",
    input_name = "cursors",
    sample_size = 10
)]
fn editor_multi_cursor_input(line_count: &usize, cx: &mut BenchAppContext) {
    init_context(cx);

    let text = "line:\n".repeat(*line_count);
    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));

    let mut window = cx.add_empty_window();
    let editor = window.update(|window, cx| {
        let editor = cx.new(|cx| {
            let mut editor = Editor::new(EditorMode::full(), buffer, None, window, cx);
            editor.set_style(editor::EditorStyle::default(), window, cx);
            editor.select_all(&SelectAll, window, cx);
            editor.split_selection_into_lines(
                &SplitSelectionIntoLines {
                    keep_selections: true,
                },
                window,
                cx,
            );
            editor
        });
        window.focus(&editor.focus_handle(cx), cx);
        editor
    });

    cx.bench_iter(|_| {
        window.update(|window, cx| {
            editor.update(cx, |editor, cx| {
                editor.handle_input("hello world", window, cx);
                editor.delete_to_previous_word_start(
                    &DeleteToPreviousWordStart {
                        ignore_newlines: false,
                        ignore_brackets: false,
                    },
                    window,
                    cx,
                );
                editor.delete_to_previous_word_start(
                    &DeleteToPreviousWordStart {
                        ignore_newlines: false,
                        ignore_brackets: false,
                    },
                    window,
                    cx,
                );
            });
        })
    });
}

#[gpui::bench]
fn open_editor_with_one_long_line(cx: &mut BenchAppContext) {
    init_context(cx);

    let text = String::from_iter(["char"; 1000]);
    cx.bench_iter(move |cx| {
        let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));

        let mut window = cx.add_empty_window();
        window.update(|window, cx| {
            let editor = cx.new(|cx| {
                let mut editor = Editor::new(EditorMode::full(), buffer, None, window, cx);
                editor.set_style(editor::EditorStyle::default(), window, cx);
                editor
            });
            window.focus(&editor.focus_handle(cx), cx);
            editor
        });
    });
}

#[gpui::bench]
fn editor_render(cx: &mut BenchAppContext) {
    init_context(cx);

    let buffer = cx.update(|cx| {
        let mut rng = StdRng::seed_from_u64(1);
        let text_len = rng.random_range(10000..90000);
        if rng.random() {
            let text = RandomCharIter::new(&mut rng)
                .take(text_len)
                .collect::<String>();
            MultiBuffer::build_simple(&text, cx)
        } else {
            MultiBuffer::build_random(&mut rng, cx)
        }
    });

    let mut window = cx.add_empty_window();
    let editor = window.update(|window, cx| {
        let editor = window.replace_root(cx, |window, cx| {
            let mut editor = Editor::new(EditorMode::full(), buffer, None, window, cx);
            editor.set_style(editor::EditorStyle::default(), window, cx);
            editor
        });
        window.focus(&editor.focus_handle(cx), cx);
        editor
    });

    let mut move_down = true;
    cx.bench_renderer(editor, move |editor, window, cx| {
        if move_down {
            editor.move_down(&MoveDown, window, cx);
        } else {
            editor.move_up(&MoveUp, window, cx);
        }
        move_down = !move_down;
    });
}

#[gpui::bench]
fn editor_render_with_editorconfig(cx: &mut BenchAppContext) {
    init_context(cx);

    let worktree_id = WorktreeId::from_usize(0);
    cx.update(|cx| {
        cx.update_global::<SettingsStore, _>(|store, cx| {
            let nested_configs = [
                ("", jetbrains_editorconfig()),
                (
                    "src",
                    indoc! {"
                        [*.{ts,tsx}]
                        indent_size = 2
                    "}
                    .to_string(),
                ),
                (
                    "src/app",
                    indoc! {"
                        [*]
                        trim_trailing_whitespace = false

                        [*.ts]
                        max_line_length = 100
                    "}
                    .to_string(),
                ),
                (
                    "src/app/components",
                    indoc! {"
                        [*.{ts,tsx}]
                        indent_style = space
                    "}
                    .to_string(),
                ),
            ];
            for (directory, content) in nested_configs {
                store
                    .set_local_settings(
                        worktree_id,
                        LocalSettingsPath::InWorktree(Arc::from(
                            RelPath::from_unix_str(directory).unwrap(),
                        )),
                        LocalSettingsKind::Editorconfig,
                        Some(&content),
                        cx,
                    )
                    .unwrap();
            }
        });
    });

    let buffer = cx.update(|cx| {
        let text = indented_code_text(3000);
        let file: Arc<dyn File> = Arc::new(BenchFile {
            path: RelPath::from_unix_str("src/app/components/editor_pane.ts")
                .unwrap()
                .into(),
        });
        let buffer = cx.new(|cx| {
            Buffer::build(
                text::Buffer::new(
                    text::ReplicaId::LOCAL,
                    cx.entity_id().as_non_zero_u64().into(),
                    text,
                ),
                Some(file),
                Capability::ReadWrite,
                cx,
            )
        });
        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
    });

    let mut window = cx.add_empty_window();
    let editor = window.update(|window, cx| {
        let editor = window.replace_root(cx, |window, cx| {
            let mut editor = Editor::new(EditorMode::full(), buffer, None, window, cx);
            editor.set_style(editor::EditorStyle::default(), window, cx);
            editor
        });
        window.focus(&editor.focus_handle(cx), cx);
        editor
    });

    let mut insert = true;
    cx.bench_renderer(editor, move |editor, window, cx| {
        if insert {
            editor.handle_input("x", window, cx);
        } else {
            editor.backspace(&editor::actions::Backspace, window, cx);
        }
        insert = !insert;
        editor.move_down(&MoveDown, window, cx);
        editor.move_up(&MoveUp, window, cx);
    });
}

fn indented_code_text(line_count: usize) -> String {
    let mut text = String::new();
    for block in 0..line_count / 10 {
        text.push_str(&format!("export function component{block:04}() {{\n"));
        text.push_str("    const state = {\n");
        text.push_str("        items: [],\n");
        text.push_str("        selection: null,\n");
        text.push_str("    };\n");
        text.push_str("    if (state.items.length > 0) {\n");
        text.push_str("        for (const item of state.items) {\n");
        text.push_str("            console.log(item, state.selection);\n");
        text.push_str("        }\n");
        text.push_str("    }\n");
        text.push_str("}\n");
    }
    text
}

fn jetbrains_editorconfig() -> String {
    let mut content = indoc! {"
        [*]
        charset = utf-8
        end_of_line = lf
        indent_size = 4
        indent_style = space
        insert_final_newline = true
        max_line_length = 150
        tab_width = 4
        trim_trailing_whitespace = false
    "}
    .to_string();
    for key_index in 0..750 {
        content.push_str(&format!("ij_continuation_option_{key_index:04} = false\n"));
    }
    for key_index in 0..1500 {
        content.push_str(&format!(
            "dotnet_diagnostic.ca{key_index:04}.severity = warning\n"
        ));
    }
    for key_index in 0..750 {
        content.push_str(&format!("resharper_style_option_{key_index:04} = true\n"));
    }
    let sections = [
        "*.css",
        "*.feature",
        "*.less",
        "*.properties",
        "*.proto",
        "*.sass",
        "*.scss",
        "*.vue",
        ".editorconfig",
        "{*.ant,*.appxmanifest,*.axml,*.cscfg,*.csdef,*.disco,*.filelayout,*.fxml,*.jhm,*.jnlp,*.jrxml,*.manifest,*.myapp,*.nuspec,*.rng,*.stylecop,*.svcmap,*.tld,*.tps,*.wadcfgx,*.webref,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul,StyleCop.Cache}",
        "{*.ats,*.ts}",
        "{*.bash,*.sh,*.zsh}",
        "{*.cjs,*.js}",
        "{*.cjsx,*.coffee}",
        "{*.har,*.inputactions,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.stylelintrc,bowerrc,jest.config}",
        "{*.hcl,*.nomad}",
        "{*.htm,*.html,*.ng,*.sht,*.shtm,*.shtml}",
        "{*.markdown,*.md}",
        "{*.pb,*.textproto}",
        "{*.ps1,*.psd1,*.psm1}",
        "{*.tf,*.tfvars}",
        "{*.yaml,*.yml}",
        "*.js.map",
        "*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,fs,fsi,fsscript,fsx,hlsl,hlsli,hlslinc,master,ml,mli,nuspec,paml,razor,resw,resx,shader,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}",
    ];
    for (section_index, section) in sections.iter().enumerate() {
        content.push_str(&formatdoc! {"

            [{section}]
            indent_size = 2
        "});
        for key_index in 0..40 {
            content.push_str(&format!(
                "ij_section_{section_index:02}_option_{key_index:02} = false\n"
            ));
        }
    }
    content
}

fn init_context(cx: &mut BenchAppContext) {
    cx.update(|cx| {
        let store = SettingsStore::test(cx);
        cx.set_global(store);
        assets::Assets.load_test_fonts(cx);
        theme_settings::init(theme::LoadThemes::JustBase, cx);
        editor::init(cx);
    });
}

fn multi_cursor_line_counts() -> Vec<usize> {
    let mut line_counts = vec![1000, 10000];
    if std::env::var("ZED_BENCH_HUGE").is_ok() {
        line_counts.push(100000);
    }
    line_counts
}

gpui::bench_group!(
    benches,
    editor_multi_cursor_input,
    open_editor_with_one_long_line,
    editor_render,
    editor_render_with_editorconfig
);
gpui::bench_main!(benches);
