use std::fs;
use std::io::BufRead;
use std::path::Path;

#[derive(serde::Serialize)]
pub struct DirEntry {
    pub name: String,
    pub path: String,
    pub is_dir: bool,
}

#[tauri::command]
fn read_directory(path: String) -> Result<Vec<DirEntry>, String> {
    let dir_path = Path::new(&path);
    if !dir_path.is_dir() {
        return Err(format!("{} is not a directory", path));
    }

    let mut entries: Vec<DirEntry> = Vec::new();
    let read_dir = fs::read_dir(dir_path).map_err(|e| e.to_string())?;

    for entry in read_dir {
        let entry = entry.map_err(|e| e.to_string())?;
        let file_name = entry.file_name().to_string_lossy().to_string();

        if file_name.starts_with('.') {
            continue;
        }

        let file_path = entry.path().to_string_lossy().to_string();
        let is_dir = entry.path().is_dir();

        entries.push(DirEntry {
            name: file_name,
            path: file_path,
            is_dir,
        });
    }

    entries.sort_by(|a, b| {
        if a.is_dir == b.is_dir {
            a.name.to_lowercase().cmp(&b.name.to_lowercase())
        } else if a.is_dir {
            std::cmp::Ordering::Less
        } else {
            std::cmp::Ordering::Greater
        }
    });

    Ok(entries)
}

#[tauri::command]
fn read_file(path: String) -> Result<String, String> {
    fs::read_to_string(&path).map_err(|e| format!("Failed to read {}: {}", path, e))
}

#[tauri::command]
fn write_file(path: String, contents: String) -> Result<(), String> {
    fs::write(&path, contents).map_err(|e| format!("Failed to write {}: {}", path, e))
}

#[tauri::command]
fn create_file(path: String) -> Result<(), String> {
    if Path::new(&path).exists() {
        return Err(format!("{} already exists", path));
    }
    fs::write(&path, "").map_err(|e| e.to_string())
}

#[tauri::command]
fn create_directory(path: String) -> Result<(), String> {
    fs::create_dir_all(&path).map_err(|e| e.to_string())
}

#[tauri::command]
fn rename_path(old_path: String, new_path: String) -> Result<(), String> {
    fs::rename(&old_path, &new_path).map_err(|e| e.to_string())
}

#[tauri::command]
fn delete_path(path: String) -> Result<(), String> {
    let p = Path::new(&path);
    if p.is_dir() {
        fs::remove_dir_all(p).map_err(|e| e.to_string())
    } else {
        fs::remove_file(p).map_err(|e| e.to_string())
    }
}

#[derive(serde::Serialize)]
pub struct SearchMatch {
    pub file_path: String,
    pub file_name: String,
    pub line_number: usize,
    pub line_content: String,
    pub match_start: usize,
    pub match_end: usize,
}

const SKIP_DIRS: &[&str] = &[
    "node_modules", "target", ".git", "dist", "build", "__pycache__",
    ".next", ".nuxt", "vendor", ".venv", "venv",
];

fn search_dir(
    dir: &Path,
    query: &str,
    case_sensitive: bool,
    results: &mut Vec<SearchMatch>,
    max_results: usize,
) {
    if results.len() >= max_results {
        return;
    }

    let read_dir = match fs::read_dir(dir) {
        Ok(rd) => rd,
        Err(_) => return,
    };

    let mut entries: Vec<_> = read_dir.filter_map(|e| e.ok()).collect();
    entries.sort_by(|a, b| a.file_name().cmp(&b.file_name()));

    for entry in entries {
        if results.len() >= max_results {
            return;
        }

        let name = entry.file_name().to_string_lossy().to_string();
        if name.starts_with('.') {
            continue;
        }

        let path = entry.path();
        if path.is_dir() {
            if SKIP_DIRS.contains(&name.as_str()) {
                continue;
            }
            search_dir(&path, query, case_sensitive, results, max_results);
        } else {
            search_file(&path, &name, query, case_sensitive, results, max_results);
        }
    }
}

fn search_file(
    path: &Path,
    name: &str,
    query: &str,
    case_sensitive: bool,
    results: &mut Vec<SearchMatch>,
    max_results: usize,
) {
    let file = match fs::File::open(path) {
        Ok(f) => f,
        Err(_) => return,
    };

    // Skip binary/large files
    let metadata = match file.metadata() {
        Ok(m) => m,
        Err(_) => return,
    };
    if metadata.len() > 2_000_000 {
        return;
    }

    let reader = std::io::BufReader::new(file);
    let query_lower = if case_sensitive { query.to_string() } else { query.to_lowercase() };
    let path_str = path.to_string_lossy().to_string();

    for (i, line) in reader.lines().enumerate() {
        if results.len() >= max_results {
            return;
        }
        let line = match line {
            Ok(l) => l,
            Err(_) => return, // likely binary
        };
        let search_line = if case_sensitive { line.clone() } else { line.to_lowercase() };
        if let Some(pos) = search_line.find(&query_lower) {
            results.push(SearchMatch {
                file_path: path_str.clone(),
                file_name: name.to_string(),
                line_number: i + 1,
                line_content: line.chars().take(500).collect(),
                match_start: pos,
                match_end: pos + query.len(),
            });
        }
    }
}

#[tauri::command]
fn search_in_files(
    dir: String,
    query: String,
    case_sensitive: bool,
) -> Result<Vec<SearchMatch>, String> {
    let dir_path = Path::new(&dir);
    if !dir_path.is_dir() {
        return Err(format!("{} is not a directory", dir));
    }
    if query.is_empty() {
        return Ok(vec![]);
    }

    let mut results = Vec::new();
    search_dir(dir_path, &query, case_sensitive, &mut results, 1000);
    Ok(results)
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_fs::init())
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_shell::init())
        .invoke_handler(tauri::generate_handler![
            read_directory,
            read_file,
            write_file,
            create_file,
            create_directory,
            rename_path,
            delete_path,
            search_in_files,
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
