use serde_json::Value;
use std::sync::Arc;
use tower_lsp_server::LanguageServer;
use tower_lsp_server::jsonrpc::Result;
use tower_lsp_server::ls_types::*;

use super::{PanacheLsp, documents, handlers};

fn watched_document_glob() -> Vec<FileSystemWatcher> {
    crate::all_document_extensions()
        .iter()
        .map(|ext| FileSystemWatcher {
            glob_pattern: GlobPattern::String(format!("**/*.{ext}")),
            kind: Some(WatchKind::all()),
        })
        .collect()
}

fn file_operation_filters() -> Vec<FileOperationFilter> {
    crate::all_document_extensions()
        .iter()
        .map(|ext| FileOperationFilter {
            scheme: Some("file".to_string()),
            pattern: FileOperationPattern {
                glob: format!("**/*.{ext}"),
                matches: Some(FileOperationPatternKind::File),
                options: None,
            },
        })
        .collect()
}

fn legacy_root_uri(params: &InitializeParams) -> Option<Uri> {
    let value = serde_json::to_value(params).ok()?;
    value
        .get("rootUri")
        .cloned()
        .and_then(|root_uri| serde_json::from_value(root_uri).ok())
}

fn experimental_incremental_parsing_from_initialize(params: &InitializeParams) -> bool {
    fn get_bool(value: &Value, path: &[&str]) -> Option<bool> {
        let mut current = value;
        for key in path {
            current = current.get(key)?;
        }
        current.as_bool()
    }

    let Some(options) = params.initialization_options.as_ref() else {
        return false;
    };

    get_bool(
        options,
        &["settings", "panache", "experimental", "incrementalParsing"],
    )
    .or_else(|| get_bool(options, &["panache", "experimental", "incrementalParsing"]))
    .or_else(|| get_bool(options, &["experimental", "incrementalParsing"]))
    .unwrap_or(false)
}

impl LanguageServer for PanacheLsp {
    async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
        // Store workspace root for config discovery
        // Try workspace_folders first, then legacy rootUri fallback.
        if let Some(folders) = params.workspace_folders.as_ref()
            && let Some(folder) = folders.first()
            && let Some(path) = folder.uri.to_file_path()
        {
            *self.workspace_root.lock().await = Some(path.into_owned());
        } else if let Some(root_uri) = legacy_root_uri(&params)
            && let Some(path) = root_uri.to_file_path()
        {
            *self.workspace_root.lock().await = Some(path.into_owned());
        }

        let experimental_incremental = experimental_incremental_parsing_from_initialize(&params);
        {
            let mut runtime_settings = self.runtime_settings.lock().await;
            runtime_settings.experimental_incremental_parsing = experimental_incremental;
        }
        log::debug!(
            "lsp runtime setting experimental.incrementalParsing={} (initialize options)",
            experimental_incremental
        );

        Ok(InitializeResult {
            capabilities: ServerCapabilities {
                text_document_sync: Some(TextDocumentSyncCapability::Options(
                    TextDocumentSyncOptions {
                        open_close: Some(true),
                        change: Some(TextDocumentSyncKind::INCREMENTAL),
                        ..Default::default()
                    },
                )),
                document_formatting_provider: Some(OneOf::Left(true)),
                document_range_formatting_provider: Some(OneOf::Left(true)),
                code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
                document_symbol_provider: Some(OneOf::Left(true)),
                document_link_provider: Some(DocumentLinkOptions {
                    resolve_provider: Some(true),
                    work_done_progress_options: WorkDoneProgressOptions {
                        work_done_progress: None,
                    },
                }),
                folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
                definition_provider: Some(OneOf::Left(true)),
                hover_provider: Some(HoverProviderCapability::Simple(true)),
                completion_provider: Some(CompletionOptions {
                    trigger_characters: Some(vec!["(".into(), "/".into(), "<".into()]),
                    ..Default::default()
                }),
                references_provider: Some(OneOf::Left(true)),
                workspace_symbol_provider: Some(OneOf::Left(true)),
                rename_provider: Some(OneOf::Right(RenameOptions {
                    prepare_provider: Some(true),
                    work_done_progress_options: WorkDoneProgressOptions {
                        work_done_progress: None,
                    },
                })),
                workspace: Some(WorkspaceServerCapabilities {
                    workspace_folders: Some(WorkspaceFoldersServerCapabilities {
                        supported: Some(true),
                        change_notifications: Some(OneOf::Left(true)),
                    }),
                    file_operations: Some(WorkspaceFileOperationsServerCapabilities {
                        will_rename: Some(FileOperationRegistrationOptions {
                            filters: file_operation_filters(),
                        }),
                        ..Default::default()
                    }),
                }),
                ..Default::default()
            },
            server_info: Some(ServerInfo {
                name: "panache-lsp".to_string(),
                version: Some(env!("CARGO_PKG_VERSION").to_string()),
            }),
            ..Default::default()
        })
    }

    async fn initialized(&self, _: InitializedParams) {
        self.client
            .log_message(MessageType::INFO, "panache LSP server initialized")
            .await;
        log::debug!("initialized LSP server");

        // Register file watchers for bibliography files
        if let Ok(options) = serde_json::to_value(DidChangeWatchedFilesRegistrationOptions {
            watchers: {
                let mut watchers = vec![
                    FileSystemWatcher {
                        glob_pattern: GlobPattern::String("**/*.bib".to_string()),
                        kind: Some(WatchKind::all()),
                    },
                    FileSystemWatcher {
                        glob_pattern: GlobPattern::String("**/*.json".to_string()),
                        kind: Some(WatchKind::all()),
                    },
                    FileSystemWatcher {
                        glob_pattern: GlobPattern::String("**/*.yaml".to_string()),
                        kind: Some(WatchKind::all()),
                    },
                    FileSystemWatcher {
                        glob_pattern: GlobPattern::String("**/*.yml".to_string()),
                        kind: Some(WatchKind::all()),
                    },
                    FileSystemWatcher {
                        glob_pattern: GlobPattern::String("**/*.ris".to_string()),
                        kind: Some(WatchKind::all()),
                    },
                ];
                watchers.extend(watched_document_glob());
                watchers
            },
        }) {
            let registrations = vec![Registration {
                id: "watch-bibliography-files".to_string(),
                method: "workspace/didChangeWatchedFiles".to_string(),
                register_options: Some(options),
            }];

            if let Err(e) = self.client.register_capability(registrations).await {
                self.client
                    .log_message(
                        MessageType::WARNING,
                        format!("Failed to register file watchers: {:?}", e),
                    )
                    .await;
            }
        }
    }

    async fn shutdown(&self) -> Result<()> {
        Ok(())
    }

    async fn did_open(&self, params: DidOpenTextDocumentParams) {
        documents::did_open(
            &self.client,
            Arc::clone(&self.document_map),
            Arc::clone(&self.workspace_root),
            Arc::clone(&self.salsa_db),
            params,
        )
        .await;
    }

    async fn did_change(&self, params: DidChangeTextDocumentParams) {
        documents::did_change(
            Arc::clone(&self.document_map),
            Arc::clone(&self.workspace_root),
            Arc::clone(&self.salsa_db),
            Arc::clone(&self.runtime_settings),
            &self.client,
            params,
        )
        .await;
    }

    async fn did_close(&self, params: DidCloseTextDocumentParams) {
        documents::did_close(
            &self.client,
            Arc::clone(&self.document_map),
            Arc::clone(&self.salsa_db),
            params,
        )
        .await;
    }

    async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
        handlers::formatting::format_document(
            &self.client,
            Arc::clone(&self.document_map),
            Arc::clone(&self.salsa_db),
            Arc::clone(&self.workspace_root),
            params,
        )
        .await
    }

    async fn range_formatting(
        &self,
        params: DocumentRangeFormattingParams,
    ) -> Result<Option<Vec<TextEdit>>> {
        handlers::formatting::format_range(
            &self.client,
            Arc::clone(&self.document_map),
            Arc::clone(&self.salsa_db),
            Arc::clone(&self.workspace_root),
            params,
        )
        .await
    }

    async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
        handlers::code_actions::code_action(
            &self.client,
            Arc::clone(&self.document_map),
            Arc::clone(&self.salsa_db),
            Arc::clone(&self.workspace_root),
            params,
        )
        .await
    }

    async fn document_symbol(
        &self,
        params: DocumentSymbolParams,
    ) -> Result<Option<DocumentSymbolResponse>> {
        handlers::document_symbols::document_symbol(
            &self.client,
            Arc::clone(&self.document_map),
            Arc::clone(&self.salsa_db),
            Arc::clone(&self.workspace_root),
            params,
        )
        .await
    }

    async fn document_link(&self, params: DocumentLinkParams) -> Result<Option<Vec<DocumentLink>>> {
        handlers::document_links::document_links(
            Arc::clone(&self.document_map),
            Arc::clone(&self.salsa_db),
            params,
        )
        .await
    }

    async fn document_link_resolve(&self, params: DocumentLink) -> Result<DocumentLink> {
        handlers::document_links::document_link_resolve(params).await
    }

    async fn folding_range(&self, params: FoldingRangeParams) -> Result<Option<Vec<FoldingRange>>> {
        handlers::folding_ranges::folding_range(
            &self.client,
            Arc::clone(&self.document_map),
            Arc::clone(&self.salsa_db),
            Arc::clone(&self.workspace_root),
            params,
        )
        .await
    }

    async fn goto_definition(
        &self,
        params: GotoDefinitionParams,
    ) -> Result<Option<GotoDefinitionResponse>> {
        handlers::goto_definition::goto_definition(
            &self.client,
            Arc::clone(&self.document_map),
            Arc::clone(&self.salsa_db),
            Arc::clone(&self.workspace_root),
            params,
        )
        .await
    }

    async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
        handlers::hover::hover(
            &self.client,
            Arc::clone(&self.document_map),
            Arc::clone(&self.salsa_db),
            Arc::clone(&self.workspace_root),
            params,
        )
        .await
    }

    async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
        handlers::completion::completion(
            &self.client,
            Arc::clone(&self.document_map),
            Arc::clone(&self.salsa_db),
            Arc::clone(&self.workspace_root),
            params,
        )
        .await
    }

    async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
        log::debug!(
            "lsp.rename request uri={:?} line={} char={}",
            params.text_document_position.text_document.uri,
            params.text_document_position.position.line,
            params.text_document_position.position.character
        );
        handlers::rename::rename(
            &self.client,
            Arc::clone(&self.document_map),
            Arc::clone(&self.salsa_db),
            Arc::clone(&self.workspace_root),
            params,
        )
        .await
    }

    async fn prepare_rename(
        &self,
        params: TextDocumentPositionParams,
    ) -> Result<Option<PrepareRenameResponse>> {
        log::debug!(
            "lsp.prepareRename request uri={:?} line={} char={}",
            params.text_document.uri,
            params.position.line,
            params.position.character
        );
        handlers::prepare_rename::prepare_rename(
            &self.client,
            Arc::clone(&self.document_map),
            Arc::clone(&self.salsa_db),
            Arc::clone(&self.workspace_root),
            params,
        )
        .await
    }

    async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
        handlers::references::references(
            &self.client,
            Arc::clone(&self.document_map),
            Arc::clone(&self.salsa_db),
            Arc::clone(&self.workspace_root),
            params,
        )
        .await
    }

    async fn symbol(
        &self,
        params: WorkspaceSymbolParams,
    ) -> Result<Option<WorkspaceSymbolResponse>> {
        handlers::workspace_symbols::workspace_symbol(
            &self.client,
            Arc::clone(&self.document_map),
            Arc::clone(&self.salsa_db),
            Arc::clone(&self.workspace_root),
            params,
        )
        .await
    }

    async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
        handlers::file_watcher::did_change_watched_files(
            &self.client,
            Arc::clone(&self.document_map),
            Arc::clone(&self.salsa_db),
            Arc::clone(&self.workspace_root),
            params,
        )
        .await;
    }

    async fn will_rename_files(&self, params: RenameFilesParams) -> Result<Option<WorkspaceEdit>> {
        handlers::file_rename::will_rename_files(
            Arc::clone(&self.document_map),
            Arc::clone(&self.salsa_db),
            Arc::clone(&self.workspace_root),
            params,
        )
        .await
    }
}
