use std::collections::HashSet;
use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;

use completion::get_completions;
use config::{EmbeddedBlockTransclusionLength, Settings};
use diagnostics::diagnostics;
use itertools::Itertools;
use rayon::prelude::*;
use references::references;
use serde_json::Value;
use symbol::{document_symbol, workspace_symbol};
use tokio::sync::RwLock;

use gotodef::goto_definition;
use tower_lsp::jsonrpc::{Error, ErrorCode, Result};

use tower_lsp::lsp_types::*;
use tower_lsp::{Client, LanguageServer, LspService, Server};
use vault::{Preview, Rangeable, Reference, Vault};

mod cli;
mod codeactions;
mod codelens;
mod commands;
mod completion;
mod config;
mod daily;
mod diagnostics;
mod gotodef;
mod hover;
mod macros;
mod references;
mod rename;
mod symbol;
mod tokens;
mod ui;
mod vault;

/// Maximum number of LSP requests processed concurrently by the server.
///
/// The forked `tower-lsp` defaults this to 4. That is far too low: a few slow
/// requests saturate every slot, the server's bounded message queue fills, and
/// the stdin reader blocks — at which point `shutdown` and `$/cancelRequest` are
/// never read and the server appears to hang. A higher limit keeps the server
/// responsive under load (and keeps `$/cancelRequest` enabled).
const MAX_REQUEST_CONCURRENCY: usize = 256;

/// Debounce window for diagnostics.
///
/// Diagnostics are O(open_files × references × referenceables) and were
/// previously recomputed synchronously on every keystroke. In a long session
/// with many open buffers, each pass grows until it takes longer than the gap
/// between edits; the passes then pile up faster than they complete, the
/// server's bounded message queue fills, the stdin reader blocks, and the
/// server goes silent (the reported crash loop). Coalescing edits so only the
/// latest change in a quiet window is computed keeps the load bounded.
const DIAGNOSTICS_DEBOUNCE_MS: u64 = 300;

#[derive(Debug, Clone)]
struct Backend {
    client: Client,
    vault: Arc<RwLock<Option<Vault>>>,
    opened_files: Arc<RwLock<HashSet<PathBuf>>>,
    settings: Arc<RwLock<Option<Settings>>>,
    /// Monotonic counter bumped on every change, used to debounce diagnostics.
    diag_gen: Arc<AtomicU64>,
}

struct TextDocumentItem {
    uri: Url,
    text: String,
}

impl Backend {
    /// Schedule a debounced diagnostics pass.
    ///
    /// Each edit bumps the generation counter and spawns a task that waits for a
    /// short quiet window; only the task whose generation is still current then
    /// computes diagnostics. Bursts of keystrokes therefore collapse into a
    /// single diagnostics run instead of one (expensive, lock-holding) run per
    /// keystroke, which is what previously wedged the server.
    fn schedule_diagnostics(&self) {
        let generation = self.diag_gen.fetch_add(1, Ordering::SeqCst) + 1;
        let backend = self.clone();
        tokio::spawn(async move {
            tokio::time::sleep(Duration::from_millis(DIAGNOSTICS_DEBOUNCE_MS)).await;
            // A newer edit arrived during the debounce window; let its task run.
            if backend.diag_gen.load(Ordering::SeqCst) != generation {
                return;
            }
            if let Err(e) = backend.publish_diagnostics().await {
                backend
                    .client
                    .log_message(
                        MessageType::ERROR,
                        format!("Failed calculating diagnostics {:?}", e),
                    )
                    .await;
            }
        });
    }

    async fn update_vault(&self, params: TextDocumentItem) {
        self.client
            .log_message(MessageType::LOG, "Update Vault Started")
            .await;

        let Ok(path) = params.uri.to_file_path() else {
            self.client
                .log_message(MessageType::ERROR, "Failed to parse URI path")
                .await;
            return;
        };

        let Ok(settings) = self.bind_settings(|settings| Ok(settings.clone())).await else {
            return;
        };

        let guard = self
            .bind_vault_mut(|vault| {
                let text = &params.text;
                Vault::update_vault(&settings, vault, (&path, text));

                Ok(())
            })
            .await;
        drop(guard);

        self.client
            .log_message(MessageType::LOG, "Update Vault Done")
            .await;

        self.schedule_diagnostics();

        if settings.semantic_tokens {
            let _ = self.client.semantic_tokens_refresh().await;
        }
    }

    async fn reconstruct_vault(&self) {
        let progress = self
            .client
            .progress(ProgressToken::Number(1), "Constructing Vault")
            .begin()
            .await;

        let timer = std::time::Instant::now();

        let Ok(settings) = self.bind_settings(|settings| Ok(settings.clone())).await else {
            return;
        };

        {
            let _ = self
                .bind_vault_mut(|vault| {
                    let Ok(new_vault) = Vault::construct_vault(&settings, vault.root_dir()) else {
                        return Err(Error::new(ErrorCode::ServerError(0)));
                    };

                    *vault = new_vault;

                    Ok(())
                })
                .await;
        }

        let elapsed = timer.elapsed();

        progress
            .finish_with_message(format!("Finished in {}ms", elapsed.as_millis()))
            .await;

        if elapsed.as_millis() > 10 {
            self.client
                .log_message(
                    MessageType::LOG,
                    format!("Vault Construction took {}ms", elapsed.as_millis()),
                )
                .await;
        }

        self.schedule_diagnostics();

        if settings.semantic_tokens {
            let _ = self.client.semantic_tokens_refresh().await;
        }
    }

    async fn publish_diagnostics(&self) -> Result<()> {
        let timer = std::time::Instant::now();

        self.client
            .log_message(MessageType::LOG, "Diagnostics Started")
            .await;

        let uris = self
            .bind_opened_files(|files| {
                Ok(files
                    .into_par_iter()
                    .filter_map(|url| Url::from_file_path(url).ok())
                    .collect::<Vec<_>>())
            })
            .await?;

        let settings = self.bind_settings(|settings| Ok(settings.clone())).await?;

        let diagnostics = self
            .bind_vault(|vault| {
                Ok(uris
                    .par_iter()
                    .filter_map(|uri| {
                        let path = uri.to_file_path().ok()?;

                        diagnostics(vault, &settings, (&path, uri))
                            .map(|diags| (uri.clone(), diags))
                    })
                    .collect::<Vec<_>>())
            })
            .await?;

        for (uri, diags) in diagnostics {
            self.client.publish_diagnostics(uri, diags, None).await;
        }

        self.client
            .log_message(MessageType::LOG, "Diagnostics Done")
            .await;

        let elapsed = timer.elapsed();

        self.client
            .log_message(
                MessageType::LOG,
                format!("Diagnostics Done took {}ms", elapsed.as_millis()),
            )
            .await;

        Ok(())
    }

    /// This is an FP reference. Lets say that there is monad around the vault of type Result<Vault>, representing accesing the RwLock arond it in async
    /// This function will extract the vautl result, apply the given function which will return another monad (which I am asuming to be another result)
    /// The function then returns this monad
    ///
    /// I think this is a nice pattern; convienient and pretty simple api; cool stuff, if I say so myself!
    ///
    /// TODO: Hopefully rust async closures will be more convienient to use eventually and this can accept an async closure; this would enable better logging
    /// in the call back functions. (though to get aroudn this, the callback could return a Result of a writer style monad, which could be logged async outside of
    /// the callback)
    async fn bind_vault<T>(&self, callback: impl FnOnce(&Vault) -> Result<T>) -> Result<T> {
        let guard = self.vault.read().await;
        let Some(vault) = guard.deref() else {
            return Err(Error::new(ErrorCode::ServerError(0)));
        };

        // Vault queries (go-to-definition, code actions, diagnostics, ...) are
        // synchronous and CPU-bound. Running them directly on a Tokio worker
        // thread blocks the async runtime, so under sustained load the stdin
        // reader and the `shutdown`/`$/cancelRequest` handlers can no longer be
        // scheduled and the server goes silent. `block_in_place` moves the work
        // off the runtime's core threads so I/O and control messages keep flowing.
        tokio::task::block_in_place(|| callback(vault))
    }

    async fn bind_vault_mut<T>(&self, callback: impl Fn(&mut Vault) -> Result<T>) -> Result<T> {
        if let Err(e) = self.vault.try_write() {
            self.client
                .log_message(
                    MessageType::ERROR,
                    format!("Failed to get VAULT lock for write {:?}", e),
                )
                .await;
        } else {
            self.client
                .log_message(MessageType::LOG, "VAULT Lock is good")
                .await;
        }

        let mut guard = self.vault.write().await;
        let Some(ref mut vault) = *guard else {
            return Err(Error::new(ErrorCode::ServerError(0)));
        };

        // See `bind_vault`: vault construction/update is synchronous, and
        // `reconstruct_vault` performs blocking disk I/O across the whole vault.
        // Offload it from the async worker thread so the runtime stays responsive.
        tokio::task::block_in_place(|| callback(vault))
    }

    async fn bind_settings<T>(&self, callback: impl FnOnce(&Settings) -> Result<T>) -> Result<T> {
        let guard = self.settings.read().await;
        let Some(settings) = guard.deref() else {
            return Err(Error::new(ErrorCode::ServerError(1)));
        };

        callback(settings)
    }

    async fn bind_opened_files<T>(
        &self,
        callback: impl Fn(&HashSet<PathBuf>) -> Result<T>,
    ) -> Result<T> {
        let opened_files = self.opened_files.read().await;
        callback(opened_files.deref())
    }

    async fn bind_opened_files_mut<T>(
        &self,
        callback: impl Fn(&mut HashSet<PathBuf>) -> Result<T>,
    ) -> Result<T> {
        if let Err(e) = self.opened_files.try_write() {
            self.client
                .log_message(
                    MessageType::ERROR,
                    format!("Failed to get FILES lock for write {:?}", e),
                )
                .await;
        } else {
            self.client
                .log_message(MessageType::LOG, "FILES Lock is good")
                .await;
        }

        let mut opened_files = self.opened_files.write().await;
        callback(opened_files.deref_mut())
    }
}

#[tower_lsp::async_trait]
impl LanguageServer for Backend {
    async fn initialize(&self, i: InitializeParams) -> Result<InitializeResult> {
        self.client
            .log_message(
                MessageType::LOG,
                format!(
                    "Initializing with root_uri: {:?}, workspace_folders: {:?}",
                    i.root_uri, i.workspace_folders
                ),
            )
            .await;

        // Try root_uri first, then workspace_folders, then fall back to current_dir
        let root_dir = if let Some(ref uri) = i.root_uri {
            uri.to_file_path()
                .or(Err(Error::new(ErrorCode::InvalidParams)))?
        } else if let Some(ref folders) = i.workspace_folders {
            if let Some(folder) = folders.iter().next() {
                folder
                    .uri
                    .to_file_path()
                    .or(Err(Error::new(ErrorCode::InvalidParams)))?
            } else {
                self.client
                    .log_message(
                        MessageType::WARNING,
                        "No root_uri or workspace_folders provided, falling back to current_dir. \
                         This may indicate misconfigured LSP client root_markers or root_dir.",
                    )
                    .await;
                std::env::current_dir().or(Err(Error::new(ErrorCode::InvalidParams)))?
            }
        } else {
            self.client
                .log_message(
                    MessageType::WARNING,
                    "No root_uri or workspace_folders provided, falling back to current_dir. \
                     This may indicate misconfigured LSP client root_markers or root_dir.",
                )
                .await;
            std::env::current_dir().or(Err(Error::new(ErrorCode::InvalidParams)))?
        };

        self.client
            .log_message(MessageType::LOG, format!("Using root_dir: {:?}", root_dir))
            .await;

        let read_settings = match Settings::new(&root_dir, &i.capabilities) {
            Ok(settings) => settings,
            Err(e) => {
                self.client
                    .log_message(
                        MessageType::ERROR,
                        format!("Failed to read settings {:?}", e),
                    )
                    .await;
                return Err(Error::new(ErrorCode::ServerError(1)));
            }
        };

        let Ok(vault) = Vault::construct_vault(&read_settings, &root_dir) else {
            return Err(Error::new(ErrorCode::ServerError(0)));
        };
        let mut value = self.vault.write().await;
        *value = Some(vault);

        let mut settings = self.settings.write().await;
        *settings = Some(read_settings);

        let file_op_reg = FileOperationRegistrationOptions {
            filters: std::iter::once(FileOperationFilter {
                pattern: FileOperationPattern {
                    options: None,
                    glob: "**/*.md".into(),
                    matches: None,
                },
                ..Default::default()
            })
            .collect(),
        };

        return Ok(InitializeResult {
            server_info: None,
            capabilities: ServerCapabilities {
                text_document_sync: Some(TextDocumentSyncCapability::Kind(
                    TextDocumentSyncKind::FULL,
                )),
                completion_provider: Some(CompletionOptions {
                    resolve_provider: Some(false),
                    trigger_characters: Some(vec![
                        "[".into(),
                        " ".into(),
                        "(".into(),
                        "#".into(),
                        ">".into(),
                    ]),
                    work_done_progress_options: Default::default(),
                    all_commit_characters: None,
                    completion_item: None,
                }),
                // definition: Some(GotoCapability::default()),,
                inlay_hint_provider: Some(OneOf::Left(true)),
                definition_provider: Some(OneOf::Left(true)),
                references_provider: Some(OneOf::Left(true)),
                rename_provider: Some(OneOf::Left(true)),
                hover_provider: Some(HoverProviderCapability::Simple(true)),
                document_symbol_provider: Some(OneOf::Left(true)),
                workspace_symbol_provider: Some(OneOf::Left(true)),
                code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
                workspace: Some(WorkspaceServerCapabilities {
                    file_operations: Some(WorkspaceFileOperationsServerCapabilities {
                        did_create: Some(file_op_reg.clone()),
                        did_rename: Some(file_op_reg.clone()),
                        did_delete: Some(file_op_reg.clone()),
                        ..Default::default()
                    }),
                    ..Default::default()
                }),
                code_lens_provider: Some(CodeLensOptions {
                    resolve_provider: None,
                }),
                execute_command_provider: Some(ExecuteCommandOptions {
                    commands: vec![
                        "apply_edits".into(),
                        "jump".into(),
                        "moxide.findReferences".into(),
                        "tomorrow".into(),
                        "today".into(),
                        "yesterday".into(),
                        "last friday".into(),
                        "last saturday".into(),
                        "last sunday".into(),
                        "last monday".into(),
                        "last tuesday".into(),
                        "last wednesday".into(),
                        "last thursday".into(),
                        "friday".into(),
                        "saturday".into(),
                        "sunday".into(),
                        "monday".into(),
                        "tuesday".into(),
                        "wednesday".into(),
                        "thursday".into(),
                    ],
                    ..Default::default()
                }),
                semantic_tokens_provider: Some(
                    SemanticTokensServerCapabilities::SemanticTokensOptions(
                        SemanticTokensOptions {
                            full: Some(SemanticTokensFullOptions::Bool(true)),
                            range: Some(false),
                            legend: SemanticTokensLegend {
                                token_types: vec![
                                    SemanticTokenType::DECORATOR,
                                    SemanticTokenType::COMMENT,
                                ],
                                token_modifiers: vec![
                                    SemanticTokenModifier::DECLARATION,
                                    SemanticTokenModifier::DEPRECATED,
                                ],
                            },
                            ..Default::default()
                        },
                    ),
                ),
                ..Default::default()
            },
        });
    }

    async fn shutdown(&self) -> Result<()> {
        // TODO: remove all code lenses
        std::process::exit(0);
    }

    async fn code_lens(&self, params: CodeLensParams) -> Result<Option<Vec<CodeLens>>> {
        let path = params_path!(params)?;

        self.bind_vault(|vault| Ok(codelens::code_lens(vault, &path, &params)))
            .await
    }

    async fn initialized(&self, _: InitializedParams) {
        let Ok(settings) = self.bind_settings(|settings| Ok(settings.clone())).await else {
            self.client
                .log_message(
                    MessageType::ERROR,
                    "Failed to read settings in initialized callback",
                )
                .await;
            return;
        };
        self.client
            .log_message(MessageType::LOG, format!("Settings: {:?}", settings))
            .await;

        let Ok(root_path) = self.bind_vault(|vault| Ok(vault.root_dir().clone())).await else {
            self.client
                .log_message(
                    MessageType::ERROR,
                    "Failed to read vault root_dir in initialized callback",
                )
                .await;
            return;
        };

        let Ok(_root_uri) = Url::from_directory_path(&root_path) else {
            self.client
                .log_message(
                    MessageType::ERROR,
                    format!("Failed to convert root_dir to URI: {:?}", root_path),
                )
                .await;
            return;
        };

        let Ok(value) = serde_json::to_value(DidChangeWatchedFilesRegistrationOptions {
            watchers: vec![FileSystemWatcher {
                glob_pattern: GlobPattern::String("**/*.md".into()),
                kind: None,
            }],
        }) else {
            self.client
                .log_message(
                    MessageType::ERROR,
                    "Failed to serialize file watcher registration options",
                )
                .await;
            return;
        };

        let registration = Registration {
            id: "myserver-fileWatcher".to_string(),
            method: "workspace/didChangeWatchedFiles".to_string(),
            register_options: Some(value),
        };

        if let Err(e) = self.client.register_capability(vec![registration]).await {
            self.client
                .log_message(
                    MessageType::ERROR,
                    format!(
                        "Failed to register file watcher capability: {:?}. \
                         File watching will not work. Ensure your editor supports \
                         workspace/didChangeWatchedFiles dynamic registration.",
                        e
                    ),
                )
                .await;
        }
    }

    async fn did_open(&self, params: DidOpenTextDocumentParams) {
        {
            let _new_files = self
                .bind_opened_files_mut(|files| {
                    // diagnostics will only be published for the files that are opened; We must track which files are opened
                    let path = params_path!(params)?;

                    files.insert(path);

                    Ok(files.clone())
                })
                .await;

            self.client
                .log_message(MessageType::LOG, "Added file")
                .await;

            self.update_vault(TextDocumentItem {
                uri: params.text_document.uri,
                text: params.text_document.text,
            })
            .await; // usually, this is not necesary; however some may start the LS without saving a changed file, so it is necessary
        } // drop the lock

        // `update_vault` already scheduled a (debounced) diagnostics pass.
    }

    async fn did_close(&self, params: DidCloseTextDocumentParams) {
        let removed_file = self
            .bind_opened_files_mut(|files| {
                let path = params_path!(params)?;

                Ok(files.take(&path))
            })
            .await;

        if let Ok(Some(file)) = removed_file {
            self.client
                .log_message(MessageType::LOG, format!("Remove file {:?}", file))
                .await;
        }
    }

    async fn did_change(&self, mut params: DidChangeTextDocumentParams) {
        self.update_vault(TextDocumentItem {
            uri: params.text_document.uri,
            text: params.content_changes.remove(0).text,
        })
        .await;
    }

    async fn did_change_watched_files(&self, _params: DidChangeWatchedFilesParams) {
        self.reconstruct_vault().await
    }

    async fn goto_definition(
        &self,
        params: GotoDefinitionParams,
    ) -> Result<Option<GotoDefinitionResponse>> {
        self.bind_vault(|vault| {
            let path = params_path!(params.text_document_position_params)?;
            Ok(
                goto_definition(vault, params.text_document_position_params.position, &path)
                    .map(GotoDefinitionResponse::Array),
            )
        })
        .await
    }

    async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
        self.bind_vault(|vault| {
            let path = params_position_path!(params)?;
            Ok(references(
                vault,
                params.text_document_position.position,
                &path,
            ))
        })
        .await
    }

    async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
        self.client
            .log_message(MessageType::LOG, "Completions Started")
            .await;

        let timer = std::time::Instant::now();

        let path = params_position_path!(params)?;
        let files = self
            .bind_opened_files(|files| Ok(files.clone().into_iter().collect::<Box<[_]>>()))
            .await?;

        let Ok(settings) = self.bind_settings(|settings| Ok(settings.to_owned())).await else {
            return Err(Error::new(ErrorCode::ServerError(2)));
        }; // TODO: this is bad

        let res = self
            .bind_vault(|vault| Ok(get_completions(vault, &files, &params, &path, &settings)))
            .await;

        let elapsed = timer.elapsed();

        self.client
            .log_message(
                MessageType::LOG,
                format!("Completions Done took {}ms", elapsed.as_millis()),
            )
            .await;

        res
    }

    async fn execute_command(&self, params: ExecuteCommandParams) -> Result<Option<Value>> {
        let settings = self.bind_settings(|settings| Ok(settings.clone())).await?;
        let root_dir = self
            .bind_vault(|vault| Ok(vault.root_dir().to_owned()))
            .await?;

        match params {
            ExecuteCommandParams { command, .. } if *command == *"apply_edits" => {
                let edits = params
                    .arguments
                    .into_iter()
                    .filter_map(|arg| serde_json::from_value::<WorkspaceEdit>(arg).ok())
                    .collect_vec();

                for edit in edits {
                    let _ = self.client.apply_edit(edit).await;
                }

                Ok(None)
            }
            ExecuteCommandParams { command, .. } if *command == *"jump" => {
                let jump_to = params.arguments.first().and_then(|val| val.as_str());
                let settings = self
                    .bind_settings(|settings| Ok(settings.to_owned()))
                    .await?;
                let root_dir = self
                    .bind_vault(|vault| Ok(vault.root_dir().to_owned()))
                    .await?;
                commands::jump(&self.client, &root_dir, &settings, jump_to).await
            }
            ExecuteCommandParams { command, .. } if *command == *"moxide.findReferences" => {
                // CodeLens command: handled client-side in VSCode via
                // editor.action.peekLocations. For other editors (e.g. kakoune-lsp)
                // that send the command back via workspace/executeCommand, we parse
                // the pre-computed locations and show the first one.
                let data = params.arguments.into_iter().find_map(|arg| {
                    serde_json::from_value::<codelens::FindReferencesData>(arg).ok()
                });

                if let Some(data) = data {
                    if let Some(first_location) = data.locations.first() {
                        let _ = self
                            .client
                            .show_document(ShowDocumentParams {
                                uri: first_location.uri.clone(),
                                external: Some(false),
                                take_focus: Some(true),
                                selection: Some(first_location.range),
                            })
                            .await;
                    }
                }

                Ok(None)
            }
            ExecuteCommandParams { command, .. } => {
                // Only route to the daily note jump handler if the command looks like
                // a date string. Unknown commands should not be misrouted.
                let known_date_commands = [
                    "tomorrow",
                    "today",
                    "yesterday",
                    "last friday",
                    "last saturday",
                    "last sunday",
                    "last monday",
                    "last tuesday",
                    "last wednesday",
                    "last thursday",
                    "next friday",
                    "next saturday",
                    "next sunday",
                    "next monday",
                    "next tuesday",
                    "next wednesday",
                    "next thursday",
                ];

                if known_date_commands.contains(&command.as_str()) {
                    jump_to_specific(&command, &self.client, &root_dir, &settings).await
                } else {
                    self.client
                        .log_message(MessageType::WARNING, format!("Unknown command: {command}"))
                        .await;
                    Err(Error {
                        code: ErrorCode::InvalidParams,
                        message: format!("Unknown command: {command}").into(),
                        data: None,
                    })
                }
            }
        }
    }

    async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
        let settings = self.bind_settings(|settings| Ok(settings.clone())).await?;
        self.bind_vault(|vault| {
            let path = params_path!(params.text_document_position_params)?;
            Ok(hover::hover(vault, &params, &path, &settings))
        })
        .await
    }

    async fn document_symbol(
        &self,
        params: DocumentSymbolParams,
    ) -> Result<Option<DocumentSymbolResponse>> {
        self.bind_vault(|vault| {
            let path = params_path!(params)?;
            Ok(document_symbol(vault, &params, &path))
        })
        .await
    }

    async fn symbol(
        &self,
        params: WorkspaceSymbolParams,
    ) -> Result<Option<Vec<SymbolInformation>>> {
        self.bind_vault(|vault| Ok(workspace_symbol(vault, &params)))
            .await
    }

    async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
        self.bind_vault(|vault| {
            let path = params_position_path!(params)?;
            Ok(rename::rename(vault, &params, &path))
        })
        .await
    }

    async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
        let settings = self.bind_settings(|settings| Ok(settings.clone())).await?;

        self.bind_vault(|vault| {
            let path = params_path!(params)?;
            Ok(codeactions::code_actions(vault, &params, &path, &settings))
        })
        .await
    }

    async fn semantic_tokens_full(
        &self,
        params: SemanticTokensParams,
    ) -> Result<Option<SemanticTokensResult>> {
        let settings = self.bind_settings(|settings| Ok(settings.clone())).await?;

        let timer = std::time::Instant::now();

        let path = params_path!(params)?;
        let res = self
            .bind_vault(|vault| {
                Ok(tokens::semantic_tokens_full(
                    vault, &path, params, &settings,
                ))
            })
            .await;

        let elapsed = timer.elapsed();

        self.client
            .log_message(
                MessageType::LOG,
                format!("Semantic Tokens Done took {}ms", elapsed.as_millis()),
            )
            .await;

        return res;
    }

    async fn inlay_hint(&self, params: InlayHintParams) -> Result<Option<Vec<InlayHint>>> {
        let settings = self.bind_settings(|settings| Ok(settings.clone())).await?;
        if !settings.inlay_hints {
            return Ok(None);
        }

        let hints = self
            .bind_vault(|vault| {
                if !settings.block_transclusion {
                    return Ok(None);
                }

                let path = params_path!(params)?;
                let Some(references) = vault.select_references(Some(&path)) else {
                    return Ok(None);
                };

                let embed_block_references_in_range = references
                    .into_iter()
                    .filter(|(_, reference)| {
                        let range = reference.range();
                        range.start.line >= params.range.start.line
                            && range.end.line <= params.range.end.line
                    })
                    .flat_map(|(ref_path, reference)| match reference {
                        Reference::MDIndexedBlockLink(..) | Reference::WikiIndexedBlockLink(..)
                            if vault
                                .select_line(ref_path, reference.data().range.start.line as isize)
                                .and_then(|line| {
                                    let character = line.get(
                                        (reference.range().start.character.checked_sub(1)?)
                                            as usize,
                                    )?;
                                    Some(*character == '!')
                                })? =>
                        {
                            Some((ref_path, reference))
                        }
                        _ => None,
                    });

                let preview_texts = embed_block_references_in_range.flat_map(|(path, it)| {
                    let binding = vault.select_referenceables_for_reference(it, path);
                    let referenceable = binding.first()?;
                    let binding =
                        vault
                            .select_referenceable_preview(referenceable)
                            .and_then(|preview| match preview {
                                Preview::Text(text) => Some(text),
                                _ => None,
                            })?;
                    let preview = binding.trim();
                    let index_index = preview.rfind("^")?;
                    let preview = preview.get(0..index_index)?.trim();
                    // only first x chars
                    let preview = (match settings.block_transclusion_length {
                        EmbeddedBlockTransclusionLength::Partial(x) => preview.get(0..=x),
                        EmbeddedBlockTransclusionLength::Full => None,
                    })
                    .map(|it| format!("{it}..."))
                    .unwrap_or(preview.to_string());

                    Some((
                        preview.to_string(),
                        it.range.start.line,
                        it.range.end.character,
                    ))
                });

                let hints: Vec<InlayHint> = preview_texts
                    .flat_map(|(preview, line, end_char)| {
                        Some(InlayHint {
                            position: Position {
                                line,
                                character: end_char,
                            },
                            label: InlayHintLabel::String(preview),
                            kind: None,
                            data: None,
                            tooltip: None,
                            text_edits: None,
                            padding_left: None,
                            padding_right: None,
                        })
                    })
                    .collect();

                Ok(Some(hints))
            })
            .await;

        self.client
            .log_message(
                MessageType::LOG,
                format!("Recalculating inlayHints for {params:?} {hints:?}"),
            )
            .await;

        hints
    }
}

async fn jump_to_specific(
    day: &str,
    client: &Client,
    root_dir: &Path,
    settings: &Settings,
) -> Result<Option<Value>> {
    commands::jump(client, root_dir, settings, Some(day)).await
}

use clap::Parser;

#[tokio::main]
async fn main() {
    let cli = cli::Cli::parse();

    match cli.command {
        Some(cli::Commands::Daily { date }) => {
            let root_dir = std::env::current_dir().expect("Failed to get current directory");
            if let Err(e) = cli::run_daily(date, &root_dir) {
                eprintln!("Error: {}", e);
                std::process::exit(1);
            }
        }
        Some(cli::Commands::Config) => {
            let root_dir = std::env::current_dir().expect("Failed to get current directory");
            if let Err(e) = cli::run_config(&root_dir) {
                eprintln!("Error: {}", e);
                std::process::exit(1);
            }
        }
        None => {
            let stdin = tokio::io::stdin();
            let stdout = tokio::io::stdout();

            let (service, socket) = LspService::new(|client| Backend {
                client,
                vault: Arc::new(None.into()),
                opened_files: Arc::new(HashSet::new().into()),
                settings: Arc::new(None.into()),
                diag_gen: Arc::new(AtomicU64::new(0)),
            });
            // The default request concurrency limit is 4. With only 4 slots, a
            // few slow requests fill the server's bounded message queue, which
            // blocks the stdin reader and prevents `shutdown`/`$/cancelRequest`
            // from being processed (the server appears to hang). A higher limit
            // keeps the server responsive and keeps `$/cancelRequest` enabled.
            Server::new(stdin, stdout, socket)
                .concurrency_level(MAX_REQUEST_CONCURRENCY)
                .serve(service)
                .await;
        }
    }
}
