//! Language Server Protocol implementation for Ruchy
//!
//! Based on SPECIFICATION.md section 8: LSP Specification
mod analyzer;
mod basic;
mod capabilities;
mod formatter;
mod server;
pub use analyzer::SemanticAnalyzer;
pub use basic::{LspServer, Notification, Request, Response};
pub use capabilities::{ruchy_token_to_lsp, RuchyTokenType, SEMANTIC_TOKEN_LEGEND};
pub use formatter::Formatter;
pub use server::RuchyLanguageServer;
use anyhow;
use std::collections::HashMap;
use tokio::net::TcpListener;
use tower_lsp::lsp_types::Url;
use tower_lsp::{LspService, Server};
#[cfg(test)]
use proptest::prelude::*;
/// Workspace management for LSP
pub struct Workspace {
    documents: HashMap<Url, String>,
}
impl Workspace {
/// # Examples
/// 
/// ```
/// use ruchy::lsp::mod::new;
/// 
/// let result = new(());
/// assert_eq!(result, Ok(()));
/// ```
pub fn new() -> Self {
        Self {
            documents: HashMap::new(),
        }
    }
/// # Examples
/// 
/// ```
/// use ruchy::lsp::mod::add_document;
/// 
/// let result = add_document(());
/// assert_eq!(result, Ok(()));
/// ```
pub fn add_document(&mut self, uri: Url, content: String) {
        self.documents.insert(uri, content);
    }
/// # Examples
/// 
/// ```
/// use ruchy::lsp::mod::update_document;
/// 
/// let result = update_document(());
/// assert_eq!(result, Ok(()));
/// ```
pub fn update_document(&mut self, uri: &Url, content: String) {
        self.documents.insert(uri.clone(), content);
    }
    /// Get document content by URI
    ///
    /// # Errors
    ///
    /// Returns an error if the document is not found in the workspace
/// # Examples
/// 
/// ```
/// use ruchy::lsp::mod::get_document;
/// 
/// let result = get_document(());
/// assert_eq!(result, Ok(()));
/// ```
pub fn get_document(&self, uri: &Url) -> anyhow::Result<&String> {
        self.documents
            .get(uri)
            .ok_or_else(|| anyhow::anyhow!("Document not found: {uri}"))
    }
/// # Examples
/// 
/// ```
/// use ruchy::lsp::mod::remove_document;
/// 
/// let result = remove_document(());
/// assert_eq!(result, Ok(()));
/// ```
pub fn remove_document(&mut self, uri: &Url) {
        self.documents.remove(uri);
    }
}
impl Default for Workspace {
    fn default() -> Self {
        Self::new()
    }
}
/// Start the LSP server
///
/// # Errors
///
/// Returns an error if the LSP server fails to start or encounters I/O issues
/// # Examples
/// 
/// ```
/// use ruchy::lsp::mod::start_server;
/// 
/// let result = start_server(());
/// assert_eq!(result, Ok(()));
/// ```
pub async fn start_server() -> anyhow::Result<()> {
    let stdin = tokio::io::stdin();
    let stdout = tokio::io::stdout();
    let (service, socket) = LspService::new(RuchyLanguageServer::new);
    Server::new(stdin, stdout, socket).serve(service).await;
    Ok(())
}
/// Start the LSP server on a TCP port for debugging
///
/// # Errors
///
/// Returns an error if the TCP listener fails to bind or connection handling fails
/// # Examples
/// 
/// ```
/// use ruchy::lsp::mod::start_tcp_server;
/// 
/// let result = start_tcp_server(());
/// assert_eq!(result, Ok(()));
/// ```
pub async fn start_tcp_server(port: u16) -> anyhow::Result<()> {
    let listener = TcpListener::bind(format!("127.0.0.1:{port}")).await?;
    while let Ok((stream, _)) = listener.accept().await {
        let (read, write) = tokio::io::split(stream);
        let (service, socket) = LspService::new(RuchyLanguageServer::new);
        tokio::spawn(async move {
            Server::new(read, write, socket).serve(service).await;
        });
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use tower_lsp::lsp_types::Url;

    // Sprint 9: Comprehensive LSP module tests

    #[test]
    fn test_workspace_creation() {
        let workspace = Workspace::new();
        assert!(workspace.documents.is_empty());
    }

    #[test]
    fn test_workspace_default() {
        let workspace = Workspace::default();
        assert!(workspace.documents.is_empty());
    }

    #[test]
    fn test_workspace_add_document() {
        let mut workspace = Workspace::new();
        let uri = Url::parse("file:///test.ruchy").unwrap();
        let content = "let x = 42".to_string();

        workspace.add_document(uri.clone(), content.clone());
        assert_eq!(workspace.documents.len(), 1);
        assert_eq!(workspace.documents.get(&uri), Some(&content));
    }

    #[test]
    fn test_workspace_update_document() {
        let mut workspace = Workspace::new();
        let uri = Url::parse("file:///test.ruchy").unwrap();

        workspace.add_document(uri.clone(), "initial".to_string());
        workspace.update_document(&uri, "updated".to_string());

        assert_eq!(workspace.documents.get(&uri), Some(&"updated".to_string()));
    }

    #[test]
    fn test_workspace_get_document() {
        let mut workspace = Workspace::new();
        let uri = Url::parse("file:///test.ruchy").unwrap();
        let content = "let x = 42".to_string();

        workspace.add_document(uri.clone(), content.clone());

        let result = workspace.get_document(&uri);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), &content);
    }

    #[test]
    fn test_workspace_get_document_not_found() {
        let workspace = Workspace::new();
        let uri = Url::parse("file:///nonexistent.ruchy").unwrap();

        let result = workspace.get_document(&uri);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("Document not found"));
    }

    #[test]
    fn test_workspace_remove_document() {
        let mut workspace = Workspace::new();
        let uri = Url::parse("file:///test.ruchy").unwrap();

        workspace.add_document(uri.clone(), "content".to_string());
        assert_eq!(workspace.documents.len(), 1);

        workspace.remove_document(&uri);
        assert_eq!(workspace.documents.len(), 0);
    }

    #[test]
    fn test_workspace_remove_nonexistent_document() {
        let mut workspace = Workspace::new();
        let uri = Url::parse("file:///test.ruchy").unwrap();

        // Should not panic when removing non-existent document
        workspace.remove_document(&uri);
        assert_eq!(workspace.documents.len(), 0);
    }

    #[test]
    fn test_workspace_multiple_documents() {
        let mut workspace = Workspace::new();
        let uri1 = Url::parse("file:///test1.ruchy").unwrap();
        let uri2 = Url::parse("file:///test2.ruchy").unwrap();
        let uri3 = Url::parse("file:///test3.ruchy").unwrap();

        workspace.add_document(uri1.clone(), "content1".to_string());
        workspace.add_document(uri2.clone(), "content2".to_string());
        workspace.add_document(uri3.clone(), "content3".to_string());

        assert_eq!(workspace.documents.len(), 3);
        assert_eq!(workspace.get_document(&uri1).unwrap(), "content1");
        assert_eq!(workspace.get_document(&uri2).unwrap(), "content2");
        assert_eq!(workspace.get_document(&uri3).unwrap(), "content3");
    }

    #[test]
    fn test_workspace_update_overwrites() {
        let mut workspace = Workspace::new();
        let uri = Url::parse("file:///test.ruchy").unwrap();

        workspace.add_document(uri.clone(), "v1".to_string());
        workspace.add_document(uri.clone(), "v2".to_string());
        workspace.update_document(&uri, "v3".to_string());

        assert_eq!(workspace.documents.len(), 1);
        assert_eq!(workspace.get_document(&uri).unwrap(), "v3");
    }

    #[test]
    fn test_workspace_with_different_schemes() {
        let mut workspace = Workspace::new();
        let file_uri = Url::parse("file:///test.ruchy").unwrap();
        let untitled_uri = Url::parse("untitled:untitled-1").unwrap();

        workspace.add_document(file_uri.clone(), "file content".to_string());
        workspace.add_document(untitled_uri.clone(), "untitled content".to_string());

        assert_eq!(workspace.documents.len(), 2);
        assert_eq!(workspace.get_document(&file_uri).unwrap(), "file content");
        assert_eq!(workspace.get_document(&untitled_uri).unwrap(), "untitled content");
    }

    #[test]
    fn test_workspace_empty_content() {
        let mut workspace = Workspace::new();
        let uri = Url::parse("file:///empty.ruchy").unwrap();

        workspace.add_document(uri.clone(), "".to_string());
        assert_eq!(workspace.get_document(&uri).unwrap(), "");
    }

    #[test]
    fn test_workspace_large_content() {
        let mut workspace = Workspace::new();
        let uri = Url::parse("file:///large.ruchy").unwrap();
        let large_content = "x".repeat(10000);

        workspace.add_document(uri.clone(), large_content.clone());
        assert_eq!(workspace.get_document(&uri).unwrap(), &large_content);
    }

    #[test]
    fn test_semantic_analyzer_creation() {
        let analyzer = SemanticAnalyzer::new();
        // Just verify it can be created
        let _ = analyzer;
    }

    #[test]
    fn test_formatter_creation() {
        let formatter = Formatter::new();
        // Just verify it can be created
        let _ = formatter;
    }

    #[test]
    fn test_ruchy_language_server_creation() {
        let server = RuchyLanguageServer::new(tower_lsp::Client::stdio());
        // Just verify it can be created
        let _ = server;
    }

    #[test]
    fn test_ruchy_token_type_variants() {
        // Test that token types exist
        assert!(RuchyTokenType::Keyword as u32 >= 0);
        assert!(RuchyTokenType::Function as u32 >= 0);
        assert!(RuchyTokenType::Variable as u32 >= 0);
        assert!(RuchyTokenType::Type as u32 >= 0);
        assert!(RuchyTokenType::Number as u32 >= 0);
        assert!(RuchyTokenType::String as u32 >= 0);
        assert!(RuchyTokenType::Comment as u32 >= 0);
        assert!(RuchyTokenType::Operator as u32 >= 0);
    }

    #[test]
    fn test_semantic_token_legend() {
        // Verify the legend is properly initialized
        assert!(!SEMANTIC_TOKEN_LEGEND.token_types.is_empty());
        assert!(SEMANTIC_TOKEN_LEGEND.token_types.len() >= 8); // At least our custom types
    }

    #[test]
    fn test_ruchy_token_to_lsp_conversion() {
        // Test token conversion
        let keyword_idx = ruchy_token_to_lsp(RuchyTokenType::Keyword);
        let function_idx = ruchy_token_to_lsp(RuchyTokenType::Function);
        let variable_idx = ruchy_token_to_lsp(RuchyTokenType::Variable);

        // Indices should be different
        assert_ne!(keyword_idx, function_idx);
        assert_ne!(keyword_idx, variable_idx);
        assert_ne!(function_idx, variable_idx);

        // Indices should be valid
        assert!(keyword_idx < SEMANTIC_TOKEN_LEGEND.token_types.len() as u32);
        assert!(function_idx < SEMANTIC_TOKEN_LEGEND.token_types.len() as u32);
        assert!(variable_idx < SEMANTIC_TOKEN_LEGEND.token_types.len() as u32);
    }

    #[test]
    fn test_workspace_concurrent_updates() {
        let mut workspace = Workspace::new();
        let uri = Url::parse("file:///concurrent.ruchy").unwrap();

        // Simulate multiple updates
        for i in 0..10 {
            workspace.update_document(&uri, format!("version {}", i));
        }

        // Should have the last update
        assert_eq!(workspace.get_document(&uri).unwrap(), "version 9");
    }

    #[test]
    fn test_workspace_uri_normalization() {
        let mut workspace = Workspace::new();

        // Different representations of the same file
        let uri1 = Url::parse("file:///home/user/test.ruchy").unwrap();
        let uri2 = Url::parse("file:///home/user/test.ruchy").unwrap();

        workspace.add_document(uri1.clone(), "content".to_string());

        // Should retrieve with equivalent URI
        assert_eq!(workspace.get_document(&uri2).unwrap(), "content");
    }
}

#[cfg(test)]
mod property_tests_mod {
    use proptest::proptest;
    use super::*;
    use proptest::prelude::*;
    proptest! {
        /// Property: Function never panics on any input
        #[test]
        fn test_new_never_panics(input: String) {
            // Limit input size to avoid timeout
            let _input = if input.len() > 100 { &input[..100] } else { &input[..] };
            // Function should not panic on any input
            let _ = std::panic::catch_unwind(|| {
                // Call function with various inputs
                // This is a template - adjust based on actual function signature
            });
        }
    }
}
