mod search;

use std::fs;
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use serde::Serialize;
use tauri::Emitter;

/// Recursively collect all files and directories under a path.
fn collect_entries(dir: &Path, entries: &mut Vec<String>) -> std::io::Result<()> {
    if dir.is_dir() {
        for entry in fs::read_dir(dir)? {
            let entry = entry?;
            let path = entry.path();
            entries.push(path.to_string_lossy().to_string());
            if path.is_dir() {
                collect_entries(&path, entries)?;
            }
        }
    }
    Ok(())
}

/// Read the full contents of a file as a string.
#[tauri::command]
fn read_file(path: String) -> Result<String, String> {
    fs::read_to_string(&path).map_err(|e| e.to_string())
}

/// Write a string to a file, creating or overwriting it.
#[tauri::command]
fn write_file(path: String, content: String) -> Result<(), String> {
    fs::write(&path, &content).map_err(|e| e.to_string())
}

/// Recursively list all entries in a directory tree, sorted alphabetically.
#[tauri::command]
fn list_directory(path: String) -> Result<Vec<String>, String> {
    let mut entries = Vec::new();
    collect_entries(Path::new(&path), &mut entries).map_err(|e| e.to_string())?;
    entries.sort();
    Ok(entries)
}

/// Create a directory and all parent directories if they don't exist.
#[tauri::command]
fn create_directory(path: String) -> Result<(), String> {
    fs::create_dir_all(&path).map_err(|e| e.to_string())
}

/// Event payload sent to the frontend when a file changes on disk.
#[derive(Clone, Serialize)]
struct FsEvent {
    path: String,
    kind: String,
}

/// Exit the application process.
#[tauri::command]
fn exit_app() {
    std::process::exit(0)
}

/// Start a recursive file watcher on the workspace directory.
#[tauri::command]
fn start_watcher(app: tauri::AppHandle, workspace_path: String) -> Result<(), String> {
    let running = Arc::new(AtomicBool::new(true));
    let running_clone = running.clone();

    let mut watcher = RecommendedWatcher::new(
        move |res: Result<Event, notify::Error>| {
            if let Ok(event) = res {
                let kind = match event.kind {
                    EventKind::Create(_) => "created",
                    EventKind::Modify(_) => "modified",
                    EventKind::Remove(_) => "deleted",
                    _ => return,
                };

                for path in &event.paths {
                    let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
                    if ext != "md" {
                        continue;
                    }
                    let payload = FsEvent {
                        path: path.to_string_lossy().to_string(),
                        kind: kind.to_string(),
                    };
                    let _ = app.emit("fs-watch:change", payload);
                }
            }
        },
        Config::default(),
    )
    .map_err(|e| e.to_string())?;

    watcher
        .watch(Path::new(&workspace_path), RecursiveMode::Recursive)
        .map_err(|e| e.to_string())?;

    thread::spawn(move || {
        while running_clone.load(Ordering::Relaxed) {
            thread::sleep(std::time::Duration::from_secs(1));
        }
    });

    Ok(())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_fs::init())
        .plugin(tauri_plugin_opener::init())
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_store::Builder::default().build())
        .plugin(tauri_plugin_sql::Builder::default().build())
        .invoke_handler(tauri::generate_handler![
            read_file,
            write_file,
            list_directory,
            create_directory,
            start_watcher,
            exit_app,
            search::compute_embedding,
            search::rank_by_similarity,
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
