// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
use serde::{Serialize, Deserialize};
use std::path::Path;
use std::fs;
use std::io::Read;
use tauri::{command, Emitter};
use zip::ZipArchive;
use yaml_rust::{YamlLoader, Yaml};
use std::fs::File;
use sha2::{Sha256, Digest};
use reqwest;
use std::error::Error;
use tauri::AppHandle;

// Add the crawlers module
mod crawlers;
use crawlers::HangarCrawler;

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum ServerType {
    Paper,
    Spigot,
    Bukkit,
    Vanilla,
    Forge,
    Fabric,
    Velocity,
    BungeeCord,
    Waterfall,
    Unknown,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ServerInfo {
    server_type: ServerType,
    minecraft_version: Option<String>,
    plugins_directory: String,
    plugins_count: usize,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Plugin {
    name: String,
    version: String,
    latest_version: Option<String>,
    description: Option<String>,
    authors: Vec<String>,
    has_update: bool,
    api_version: Option<String>,
    main_class: Option<String>,
    depend: Option<Vec<String>>,
    soft_depend: Option<Vec<String>>,
    load_before: Option<Vec<String>>,
    commands: Option<serde_json::Value>,
    permissions: Option<serde_json::Value>,
    file_path: String,
    file_hash: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PluginMeta {
    pub name: String,
    pub version: String,
    pub description: Option<String>,
    pub authors: Vec<String>,
    pub api_version: Option<String>,
    pub main_class: Option<String>,
    pub depend: Option<Vec<String>>,
    pub soft_depend: Option<Vec<String>>,
    pub load_before: Option<Vec<String>>,
    pub commands: Option<serde_json::Value>,
    pub permissions: Option<serde_json::Value>,
    pub file_path: String,
    pub file_size: u64,
    pub file_hash: String,
}

/// Calculates SHA-256 hash for a given file path
pub fn calculate_file_hash(file_path: &str) -> Result<String, String> {
    let mut file = File::open(file_path).map_err(|e| format!("Failed to open file for hashing: {}", e))?;
    let mut hasher = Sha256::new();
    let mut buffer = [0; 1024];

    loop {
        let bytes_read = file.read(&mut buffer).map_err(|e| format!("Failed to read file for hashing: {}", e))?;
        if bytes_read == 0 {
            break;
        }
        hasher.update(&buffer[..bytes_read]);
    }

    let hash = hasher.finalize();
    Ok(format!("{:x}", hash))
}

/// Extract metadata from a plugin.yml file inside a JAR
fn extract_plugin_metadata(jar_path: &Path) -> Result<PluginMeta, String> {
    let file = fs::File::open(jar_path)
        .map_err(|e| format!("Failed to open JAR file: {}", e))?;

    let file_size = file.metadata()
        .map_err(|e| format!("Failed to read file metadata: {}", e))?
        .len();

    let mut archive = ZipArchive::new(file)
        .map_err(|e| format!("Invalid JAR file: {}", e))?;

    // Try to find and read plugin.yml or bungee.yml
    let yaml_content = match read_yaml_from_archive(&mut archive, "plugin.yml") {
        Ok(content) => content,
        Err(_) => match read_yaml_from_archive(&mut archive, "bungee.yml") {
            Ok(content) => content,
            Err(_) => {
                // If no plugin metadata file is found, try to infer from filename
                let filename = jar_path.file_name()
                    .and_then(|n| n.to_str())
                    .unwrap_or("unknown.jar");

                // Extract name and version from filename (e.g., "WorldEdit-7.2.8.jar" → name: "WorldEdit", version: "7.2.8")
                let mut parts: Vec<&str> = filename.trim_end_matches(".jar").split('-').collect();
                let version = if parts.len() > 1 {
                    parts.pop().unwrap_or("1.0.0").to_string()
                } else {
                    "1.0.0".to_string()
                };

                let name = parts.join("-");

                return Ok(PluginMeta {
                    name,
                    version,
                    description: None,
                    authors: Vec::new(),
                    api_version: None,
                    main_class: None,
                    depend: None,
                    soft_depend: None,
                    load_before: None,
                    commands: None,
                    permissions: None,
                    file_path: jar_path.to_string_lossy().to_string(),
                    file_size,
                    file_hash: calculate_file_hash(jar_path.to_str().unwrap_or("unknown.jar")).unwrap_or_else(|_| "unknown".to_string()),
                });
            }
        }
    };

    // Parse the YAML content
    let docs = match YamlLoader::load_from_str(&yaml_content) {
        Ok(docs) => docs,
        Err(e) => {
            println!("Failed to parse plugin YAML: {}", e);
            return fallback_plugin_meta(jar_path, file_size);
        }
    };

    if docs.is_empty() {
        return fallback_plugin_meta(jar_path, file_size);
    }

    let doc = &docs[0];

    // Extract basic metadata with fallbacks for missing fields
    let name = yaml_str_with_fallback(doc, "name", jar_path);
    let version = yaml_str_with_fallback(doc, "version", jar_path);

    // Extract optional fields
    let description = yaml_str_opt(doc, "description");

    // Handle authors (can be a single string or an array)
    let authors = match &doc["authors"] {
        Yaml::Array(arr) => {
            arr.iter()
                .filter_map(|a| a.as_str().map(|s| s.to_string()))
                .collect()
        },
        Yaml::String(s) => vec![s.clone()],
        _ => {
            // Fallback to 'author' field which is sometimes used
            match &doc["author"] {
                Yaml::String(s) => vec![s.clone()],
                _ => Vec::new(),
            }
        }
    };

    // Extract other optional metadata
    let api_version = yaml_str_opt(doc, "api-version");
    let main_class = yaml_str_opt(doc, "main");

    // Handle dependency lists
    let depend = yaml_str_array(doc, "depend");
    let soft_depend = yaml_str_array(doc, "softdepend");
    let load_before = yaml_str_array(doc, "loadbefore");

    // Handle complex structures as generic JSON values
    let commands = match &doc["commands"] {
        Yaml::Hash(_) => {
            Some(serde_json::Value::String("Commands data present".to_string()))
        },
        _ => None
    };

    let permissions = match &doc["permissions"] {
        Yaml::Hash(_) => {
            Some(serde_json::Value::String("Permissions data present".to_string()))
        },
        _ => None
    };

    // Calculate the file hash
    let file_hash = calculate_file_hash(jar_path.to_str().unwrap_or("unknown.jar")).unwrap_or_else(|_| "unknown".to_string());

    Ok(PluginMeta {
        name,
        version,
        description,
        authors,
        api_version,
        main_class,
        depend,
        soft_depend,
        load_before,
        commands,
        permissions,
        file_path: jar_path.to_string_lossy().to_string(),
        file_size,
        file_hash,
    })
}

// Helper function to read a YAML file from the ZIP archive
fn read_yaml_from_archive(archive: &mut ZipArchive<fs::File>, file_name: &str) -> Result<String, String> {
    match archive.by_name(file_name) {
        Ok(mut file) => {
            let mut contents = String::new();
            file.read_to_string(&mut contents)
                .map_err(|e| format!("Failed to read {}: {}", file_name, e))?;
            Ok(contents)
        },
        Err(e) => Err(format!("Failed to find {}: {}", file_name, e))
    }
}

// Helper function to create plugin metadata with fallback values
fn fallback_plugin_meta(jar_path: &Path, file_size: u64) -> Result<PluginMeta, String> {
    let filename = jar_path.file_name()
        .and_then(|n| n.to_str())
        .unwrap_or("unknown.jar");

    // Extract name and version from filename (e.g., "WorldEdit-7.2.8.jar" → name: "WorldEdit", version: "7.2.8")
    let mut parts: Vec<&str> = filename.trim_end_matches(".jar").split('-').collect();
    let version = if parts.len() > 1 {
        parts.pop().unwrap_or("1.0.0").to_string()
    } else {
        "1.0.0".to_string()
    };

    let name = parts.join("-");

    Ok(PluginMeta {
        name,
        version,
        description: None,
        authors: Vec::new(),
        api_version: None,
        main_class: None,
        depend: None,
        soft_depend: None,
        load_before: None,
        commands: None,
        permissions: None,
        file_path: jar_path.to_string_lossy().to_string(),
        file_size,
        file_hash: calculate_file_hash(jar_path.to_str().unwrap_or("unknown.jar")).unwrap_or_else(|_| "unknown".to_string()),
    })
}

// Extract a string from YAML with fallback to filename
fn yaml_str_with_fallback(yaml: &Yaml, key: &str, jar_path: &Path) -> String {
    match yaml[key].as_str() {
        Some(s) => s.to_string(),
        None => {
            // Extract from filename as fallback
            let filename = jar_path.file_name()
                .and_then(|n| n.to_str())
                .unwrap_or("unknown.jar");

            if key == "name" {
                // Extract name (e.g., "WorldEdit-7.2.8.jar" → "WorldEdit")
                let parts: Vec<&str> = filename.trim_end_matches(".jar").split('-').collect();
                parts[0].to_string()
            } else if key == "version" {
                // Extract version (e.g., "WorldEdit-7.2.8.jar" → "7.2.8")
                let parts: Vec<&str> = filename.trim_end_matches(".jar").split('-').collect();
                if parts.len() > 1 {
                    parts[1].to_string()
                } else {
                    "1.0.0".to_string()
                }
            } else {
                String::new()
            }
        }
    }
}

fn yaml_str_opt(yaml: &Yaml, key: &str) -> Option<String> {
    yaml[key].as_str().map(|s| s.to_string())
}

fn yaml_str_array(yaml: &Yaml, key: &str) -> Option<Vec<String>> {
    match &yaml[key] {
        Yaml::Array(arr) => {
            let strings: Vec<String> = arr.iter()
                .filter_map(|a| a.as_str().map(|s| s.to_string()))
                .collect();
            if strings.is_empty() { None } else { Some(strings) }
        },
        _ => None
    }
}

/// Detect the server type based on files in the server directory
fn detect_server_type(server_path: &Path) -> ServerType {
    // --- Check for Paper --- (Check before Spigot/Bukkit as Paper includes their files)
    // Primary indicator: config/paper-global.yml (or similar variants)
    if server_path.join("config").join("paper-global.yml").exists() ||
       server_path.join("config").join("paper.yml").exists() { // Also check for paper.yml in config, just in case
        return ServerType::Paper;
    }
    // Secondary indicator: paper.yml in root (less common, but check)
    if server_path.join("paper.yml").exists() {
        return ServerType::Paper;
    }
    // Tertiary indicator: Look for a jar file starting with "paper-" in root
    if let Ok(entries) = fs::read_dir(server_path) {
        for entry in entries.filter_map(Result::ok) {
            if let Some(filename) = entry.file_name().to_str() {
                if filename.starts_with("paper-") && filename.ends_with(".jar") {
                    return ServerType::Paper;
                }
            }
        }
    }
    // --- End Paper Check ---

    // Check for Spigot
    if server_path.join("spigot.yml").exists() {
        return ServerType::Spigot;
    }

    // Check for Bukkit
    if server_path.join("bukkit.yml").exists() {
        return ServerType::Bukkit;
    }

    // Check for Forge
    if server_path.join("forge-server.jar").exists() ||
       server_path.join("mods").exists() {
        return ServerType::Forge;
    }

    // Check for Fabric
    if server_path.join("fabric-server-launch.jar").exists() ||
       (server_path.join("mods").exists() && server_path.join("fabric-server-launcher.properties").exists()) {
        return ServerType::Fabric;
    }

    // Check for Velocity
    if server_path.join("velocity.toml").exists() {
        return ServerType::Velocity;
    }

    // Check for BungeeCord
    if server_path.join("BungeeCord.jar").exists() ||
       server_path.join("config.yml").exists() {
        return ServerType::BungeeCord;
    }

    // Check for Waterfall
    if server_path.join("waterfall.jar").exists() ||
       server_path.join("waterfall.yml").exists() {
        return ServerType::Waterfall;
    }

    // Check if it's at least a vanilla server
    if server_path.join("server.properties").exists() ||
       server_path.join("vanilla_server.jar").exists() {
        return ServerType::Vanilla;
    }

    // If no server type detected
    ServerType::Unknown
}

/// Guess the Minecraft version from various files in the server directory
fn detect_minecraft_version(server_path: &Path, server_type: &ServerType) -> Option<String> {
    // Try from version.json if it exists
    if let Ok(content) = fs::read_to_string(server_path.join("version.json")) {
        if let Ok(json) = serde_json::from_str::<serde_json::Value>(&content) {
            if let Some(version) = json["name"].as_str() {
                return Some(version.to_string());
            }
        }
    }

    // --- Try parsing paper-global.yml for Paper servers ---
    if server_type == &ServerType::Paper {
        let paper_global_path = server_path.join("config").join("paper-global.yml");
        if paper_global_path.exists() {
            if let Ok(content) = fs::read_to_string(&paper_global_path) {
                // Use yaml_rust::YamlLoader here
                match yaml_rust::YamlLoader::load_from_str(&content) {
                    Ok(docs) if !docs.is_empty() => {
                        let doc = &docs[0];
                        // Common location for MC version, might differ
                        if let Some(version) = doc["misc"]["paper-version"].as_str() {
                            // Often includes build number, try to extract base MC version
                            let mc_version = version.split('-').next().unwrap_or(version);
                            return Some(mc_version.to_string());
                        }
                         // Fallback check, some older versions might store it differently
                         if let Some(version) = doc["settings"]["minecraft-version"].as_str() {
                            return Some(version.to_string());
                        }
                    }
                    Err(e) => {
                         println!("Failed to parse paper-global.yml: {}", e);
                    }
                    _ => { /* Empty or invalid YAML */ }
                }
            }
        }
    }
    // --- End Paper version check ---

    // Try from the server jar name pattern
    if let Ok(entries) = fs::read_dir(server_path) {
        for entry in entries {
            if let Ok(entry) = entry {
                let path = entry.path();
                if path.is_file() && path.extension().map_or(false, |ext| ext == "jar") {
                    let filename = path.file_name().and_then(|n| n.to_str()).unwrap_or("");

                    // Extract version from various common patterns in jar names
                    if filename.starts_with("paper-") ||
                       filename.starts_with("spigot-") ||
                       filename.starts_with("craftbukkit-") {
                        // Pattern: paper-1.19.2.jar, spigot-1.19.2.jar
                        let parts: Vec<&str> = filename.split('-').collect();
                        if parts.len() > 1 {
                            let version_part = parts[1].trim_end_matches(".jar");
                            if version_part.contains('.') { // Basic version format check
                                return Some(version_part.to_string());
                            }
                        }
                    }

                    // Look for version patterns like minecraft_server.1.19.2.jar
                    if filename.starts_with("minecraft_server.") {
                        let version_part = filename
                            .trim_start_matches("minecraft_server.")
                            .trim_end_matches(".jar");
                        if version_part.contains('.') {
                            return Some(version_part.to_string());
                        }
                    }
                }
            }
        }
    }

    // If server type is proxy, look in config files
    if server_type == &ServerType::BungeeCord ||
       server_type == &ServerType::Waterfall ||
       server_type == &ServerType::Velocity {
        // Velocity uses TOML, others use YAML
        if server_type == &ServerType::Velocity {
            if let Ok(content) = fs::read_to_string(server_path.join("velocity.toml")) {
                // Very basic TOML parsing just for this field
                for line in content.lines() {
                    if line.contains("minecraft-version") {
                        if let Some(version) = line.split('=').nth(1) {
                            return Some(version.trim().trim_matches('"').to_string());
                        }
                    }
                }
            }
        } else {
            // Try to parse config.yml for BungeeCord/Waterfall
            if let Ok(content) = fs::read_to_string(server_path.join("config.yml")) {
                if let Ok(docs) = YamlLoader::load_from_str(&content) {
                    if !docs.is_empty() {
                        let doc = &docs[0];
                        if let Some(version) = doc["minecraft_version"].as_str() {
                            return Some(version.to_string());
                        }
                    }
                }
            }
        }
    }

    // Default fallback
    None
}

/// Get plugins directory path based on server type
fn get_plugins_directory(server_path: &Path, server_type: &ServerType) -> String {
    match server_type {
        ServerType::Velocity => server_path.join("plugins").to_string_lossy().to_string(),
        ServerType::BungeeCord => server_path.join("plugins").to_string_lossy().to_string(),
        ServerType::Waterfall => server_path.join("plugins").to_string_lossy().to_string(),
        _ => server_path.join("plugins").to_string_lossy().to_string(),
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ScanResult {
    server_info: ServerInfo,
    plugins: Vec<Plugin>,
}

// Payload for progress events
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ScanProgress {
    processed: usize,
    total: usize,
    current_file: String,
}

#[command]
async fn scan_server_directory(app_handle: AppHandle, path: String) -> Result<(), String> {
    // Spawn the scanning logic into a background thread
    tauri::async_runtime::spawn(async move {
        // Bring Manager trait into scope for this block
        let result = perform_scan(&app_handle, &path).await;

        match result {
            Ok(scan_result) => {
                // Emit completion event
                if let Err(e) = app_handle.emit("scan_complete", scan_result) {
                    eprintln!("Failed to emit scan_complete event: {}", e);
                }
            }
            Err(e) => {
                // Emit error event
                if let Err(emit_err) = app_handle.emit("scan_error", e.to_string()) {
                    eprintln!("Failed to emit scan_error event: {}", emit_err);
                }
            }
        }
    });

    Ok(()) // Return immediately
}

// The actual scanning logic, moved to a separate async function
async fn perform_scan(app_handle: &AppHandle, path: &str) -> Result<ScanResult, String> {
    let server_path = Path::new(path);

    if !server_path.exists() {
        return Err(format!("Server path does not exist: {}", path));
    }

    // Detect server type and version
    let server_type = detect_server_type(server_path);
    let minecraft_version = detect_minecraft_version(server_path, &server_type);

    println!("Detected server type: {:?}", server_type);
    if let Some(version) = &minecraft_version {
        println!("Detected Minecraft version: {}", version);
    }

    // Determine plugins directory based on server type
    let plugins_dir_str = get_plugins_directory(server_path, &server_type);
    let plugins_dir = Path::new(&plugins_dir_str);

    if !plugins_dir.exists() {
        return Err(format!(
            "Plugins directory not found at: {}",
            plugins_dir.display()
        ));
    }

    // --- Progress Reporting Setup ---
    let mut jar_files_to_process: Vec<std::path::PathBuf> = Vec::new();
    match fs::read_dir(&plugins_dir) {
        Ok(entries) => {
            for entry in entries {
                if let Ok(entry) = entry {
                    let path = entry.path();
                    if path.is_file() && path.extension().map_or(false, |ext| ext.eq_ignore_ascii_case("jar")) {
                        jar_files_to_process.push(path);
                    }
                }
            }
        }
        Err(e) => {
            return Err(format!("Failed to read plugins directory initially: {}", e));
        }
    }
    let total_plugins = jar_files_to_process.len();
    let mut processed_plugins = 0;
    // --- End Progress Reporting Setup ---

    let mut plugins = Vec::new();

    for path in jar_files_to_process {
        processed_plugins += 1;
        let current_file = path.file_name().unwrap_or_default().to_string_lossy().to_string();

        // Emit progress
        let progress = ScanProgress {
            processed: processed_plugins,
            total: total_plugins,
            current_file: current_file.clone(),
        };
        if let Err(e) = app_handle.emit("scan_progress", progress) {
            eprintln!("Failed to emit scan_progress event: {}", e);
            // Continue processing even if event emission fails
        }

        // Use spawn_blocking for CPU/IO-bound tasks within the async fn
        let meta_result = tauri::async_runtime::spawn_blocking(move || {
            extract_plugin_metadata(&path)
        }).await;

        match meta_result {
            Ok(Ok(meta)) => {
                // Create a Plugin from PluginMeta
                let plugin = Plugin {
                    name: meta.name,
                    version: meta.version,
                    latest_version: None, // Will be filled by update checker
                    description: meta.description,
                    authors: meta.authors,
                    has_update: false, // Will be determined by update checker
                    api_version: meta.api_version,
                    main_class: meta.main_class,
                    depend: meta.depend,
                    soft_depend: meta.soft_depend,
                    load_before: meta.load_before,
                    commands: meta.commands,
                    permissions: meta.permissions,
                    file_path: meta.file_path,
                    file_hash: meta.file_hash,
                };
                plugins.push(plugin);
            }
            Ok(Err(e)) => {
                // Log error but continue with other plugins
                println!("Error reading plugin from {}: {}", current_file, e);
                // Optionally emit a specific plugin error event here
            }
            Err(e) => {
                 // This happens if the blocking task itself panics
                 println!("Task panicked for plugin {}: {}", current_file, e);
            }
        }
    }

    // Create server info
    let server_info = ServerInfo {
        server_type,
        minecraft_version,
        plugins_directory: plugins_dir_str,
        plugins_count: plugins.len(), // Use the count of successfully processed plugins
    };

    Ok(ScanResult {
        server_info,
        plugins,
    })
}

#[command]
fn greet(name: &str) -> String {
    format!("Hello, {}! You've been greeted from Rust!", name)
}

// Web Crawler Module
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum RepositorySource {
    HangarMC,
    SpigotMC,
    Modrinth,
    GitHub,
    BukkitDev,
    Custom(String),
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RepositoryPlugin {
    id: String,               // Unique identifier in the repository
    name: String,             // Plugin name
    version: String,          // Latest version
    description: Option<String>,
    authors: Vec<String>,
    download_url: String,     // URL to download the plugin
    repository: RepositorySource,
    page_url: String,         // URL to the plugin page
    download_count: Option<u64>,
    last_updated: Option<String>,
    icon_url: Option<String>,
    minecraft_versions: Vec<String>,
    categories: Vec<String>,
    rating: Option<f32>,
    file_size: Option<u64>,
    file_hash: Option<String>,
}

// Trait for implementing different repository crawlers
pub trait RepositoryCrawler {
    fn search(&self, query: &str) -> Result<Vec<RepositoryPlugin>, Box<dyn Error>>;
    fn get_plugin_details(&self, plugin_id: &str) -> Result<RepositoryPlugin, Box<dyn Error>>;
    fn get_plugin_versions(&self, plugin_id: &str) -> Result<Vec<String>, Box<dyn Error>>;
    fn download_plugin(&self, plugin_id: &str, version: &str, destination: &Path) -> Result<String, Box<dyn Error>>;
    fn get_repository_name(&self) -> RepositorySource;
}

// Basic HTTP client for crawler implementations
pub struct HttpClient {
    client: reqwest::blocking::Client,
}

impl HttpClient {
    pub fn new() -> Self {
        let client = reqwest::blocking::Client::builder()
            .user_agent("PlugSnatcher/0.1.0")
            .build()
            .unwrap_or_else(|_| reqwest::blocking::Client::new());

        HttpClient { client }
    }

    pub fn get(&self, url: &str) -> Result<String, Box<dyn Error>> {
        let response = self.client.get(url).send()?;

        if response.status().is_success() {
            Ok(response.text()?)
        } else {
            Err(format!("HTTP error: {}", response.status()).into())
        }
    }

    pub fn download(&self, url: &str, destination: &Path) -> Result<(), Box<dyn Error>> {
        let response = self.client.get(url).send()?;

        if response.status().is_success() {
            let bytes = response.bytes()?;
            fs::write(destination, bytes)?;
            Ok(())
        } else {
            Err(format!("Download failed: {}", response.status()).into())
        }
    }
}

// Helper function to get crawler for a specific repository
fn get_crawler(repository: &RepositorySource) -> Option<Box<dyn RepositoryCrawler>> {
    match repository {
        RepositorySource::HangarMC => Some(Box::new(HangarCrawler::new())),
        // Other repositories will be implemented later
        _ => None,
    }
}

// Regular repository functions (not commands)
pub fn lib_search_plugins_in_repositories(query: &str, repositories: Vec<RepositorySource>) -> Result<Vec<RepositoryPlugin>, String> {
    let mut results: Vec<RepositoryPlugin> = Vec::new();

    // Try each requested repository
    for repo in repositories {
        if let Some(crawler) = get_crawler(&repo) {
            match crawler.search(query) {
                Ok(repo_results) => {
                    results.extend(repo_results);
                },
                Err(e) => {
                    println!("Error searching in repository {:?}: {}", repo, e);
                    // Continue with other repositories even if one fails
                }
            }
        } else {
            println!("Repository crawler for {:?} not implemented yet", repo);
        }
    }

    if results.is_empty() {
        Err("No plugins found or repositories not implemented yet".to_string())
    } else {
        Ok(results)
    }
}

pub fn lib_get_plugin_details_from_repository(plugin_id: &str, repository: RepositorySource) -> Result<RepositoryPlugin, String> {
    if let Some(crawler) = get_crawler(&repository) {
        crawler.get_plugin_details(plugin_id).map_err(|e| e.to_string())
    } else {
        Err(format!("Repository crawler for {:?} not implemented yet", repository))
    }
}

pub fn lib_download_plugin_from_repository(plugin_id: &str, version: &str, repository: RepositorySource, destination: &str) -> Result<String, String> {
    if let Some(crawler) = get_crawler(&repository) {
        crawler
            .download_plugin(plugin_id, version, Path::new(destination))
            .map_err(|e| e.to_string())
    } else {
        Err(format!("Repository crawler for {:?} not implemented yet", repository))
    }
}

// A very simple proxy command
#[command]
fn plugin_proxy(action: &str, json_args: &str) -> Result<String, String> {
    match action {
        "search" => {
            // Parse the JSON arguments
            let args: serde_json::Value = serde_json::from_str(json_args)
                .map_err(|e| format!("Invalid JSON: {}", e))?;

            let query = args["query"].as_str()
                .ok_or_else(|| "Missing query parameter".to_string())?;

            // Convert repositories to RepositorySource
            let repo_array = args["repositories"].as_array()
                .ok_or_else(|| "Missing repositories parameter".to_string())?;

            let mut repositories = Vec::new();
            for repo in repo_array {
                let repo_str = repo.as_str()
                    .ok_or_else(|| "Repository must be a string".to_string())?;

                let repo_source = match repo_str {
                    "HangarMC" => RepositorySource::HangarMC,
                    "SpigotMC" => RepositorySource::SpigotMC,
                    "Modrinth" => RepositorySource::Modrinth,
                    "GitHub" => RepositorySource::GitHub,
                    "BukkitDev" => RepositorySource::BukkitDev,
                    _ => {
                        // Handle custom repositories
                        if repo_str.starts_with("Custom:") {
                            let url = repo_str.trim_start_matches("Custom:");
                            RepositorySource::Custom(url.to_string())
                        } else {
                            return Err(format!("Unknown repository: {}", repo_str));
                        }
                    }
                };

                repositories.push(repo_source);
            }

            // Call the implementation
            match lib_search_plugins_in_repositories(query, repositories) {
                Ok(results) => {
                    // Convert results to JSON
                    serde_json::to_string(&results)
                        .map_err(|e| format!("Failed to serialize results: {}", e))
                }
                Err(e) => Err(e),
            }
        },
        "details" => {
            // Parse the JSON arguments
            let args: serde_json::Value = serde_json::from_str(json_args)
                .map_err(|e| format!("Invalid JSON: {}", e))?;

            let plugin_id = args["plugin_id"].as_str()
                .ok_or_else(|| "Missing plugin_id parameter".to_string())?;

            let repo_str = args["repository"].as_str()
                .ok_or_else(|| "Missing repository parameter".to_string())?;

            // Convert repository to RepositorySource
            let repository = match repo_str {
                "HangarMC" => RepositorySource::HangarMC,
                "SpigotMC" => RepositorySource::SpigotMC,
                "Modrinth" => RepositorySource::Modrinth,
                "GitHub" => RepositorySource::GitHub,
                "BukkitDev" => RepositorySource::BukkitDev,
                _ => {
                    // Handle custom repositories
                    if repo_str.starts_with("Custom:") {
                        let url = repo_str.trim_start_matches("Custom:");
                        RepositorySource::Custom(url.to_string())
                    } else {
                        return Err(format!("Unknown repository: {}", repo_str));
                    }
                }
            };

            // Call the implementation
            match lib_get_plugin_details_from_repository(plugin_id, repository) {
                Ok(details) => {
                    // Convert details to JSON
                    serde_json::to_string(&details)
                        .map_err(|e| format!("Failed to serialize details: {}", e))
                }
                Err(e) => Err(e),
            }
        },
        "download" => {
            // Parse the JSON arguments
            let args: serde_json::Value = serde_json::from_str(json_args)
                .map_err(|e| format!("Invalid JSON: {}", e))?;

            let plugin_id = args["plugin_id"].as_str()
                .ok_or_else(|| "Missing plugin_id parameter".to_string())?;

            let version = args["version"].as_str()
                .ok_or_else(|| "Missing version parameter".to_string())?;

            let destination = args["destination"].as_str()
                .ok_or_else(|| "Missing destination parameter".to_string())?;

            let repo_str = args["repository"].as_str()
                .ok_or_else(|| "Missing repository parameter".to_string())?;

            // Convert repository to RepositorySource
            let repository = match repo_str {
                "HangarMC" => RepositorySource::HangarMC,
                "SpigotMC" => RepositorySource::SpigotMC,
                "Modrinth" => RepositorySource::Modrinth,
                "GitHub" => RepositorySource::GitHub,
                "BukkitDev" => RepositorySource::BukkitDev,
                _ => {
                    // Handle custom repositories
                    if repo_str.starts_with("Custom:") {
                        let url = repo_str.trim_start_matches("Custom:");
                        RepositorySource::Custom(url.to_string())
                    } else {
                        return Err(format!("Unknown repository: {}", repo_str));
                    }
                }
            };

            // Call the implementation
            lib_download_plugin_from_repository(plugin_id, version, repository, destination)
        },
        _ => Err(format!("Unknown action: {}", action)),
    }
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_dialog::init())
        .invoke_handler(tauri::generate_handler![
            greet,
            scan_server_directory,
            plugin_proxy
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

