use serde::Serialize;
use std::collections::HashSet;
use std::fs;
use std::path::{Path, PathBuf};
use std::time::UNIX_EPOCH;
use tauri::{AppHandle, Manager};

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FileExplorerRoot {
    pub label: String,
    pub path: String,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FileExplorerEntry {
    pub name: String,
    pub path: String,
    pub is_dir: bool,
    pub size_bytes: Option<u64>,
    pub modified_ms: Option<u128>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FileExplorerListResponse {
    pub current_path: String,
    pub parent_path: Option<String>,
    pub roots: Vec<FileExplorerRoot>,
    pub entries: Vec<FileExplorerEntry>,
}

#[tauri::command]
pub fn file_explorer_list(
    app: AppHandle,
    path: Option<String>,
    extensions: Option<Vec<String>>,
) -> Result<FileExplorerListResponse, String> {
    let current_path = resolve_start_path(&app, path)?;
    let extension_filter = normalize_extensions(extensions);

    let mut entries = fs::read_dir(&current_path)
        .map_err(|err| format!("Failed to read '{}': {err}", current_path.display()))?
        .filter_map(Result::ok)
        .filter_map(|entry| {
            let file_type = entry.file_type().ok()?;
            let metadata = entry.metadata().ok();
            let is_dir = file_type.is_dir();
            let path = entry.path();

            if !is_dir && !extension_filter.is_empty() {
                let extension = path
                    .extension()
                    .and_then(|ext| ext.to_str())
                    .map(|ext| ext.to_ascii_lowercase())
                    .unwrap_or_default();
                if !extension_filter.contains(&extension) {
                    return None;
                }
            }

            let name = entry.file_name().to_string_lossy().to_string();
            let size_bytes = if is_dir {
                None
            } else {
                metadata.as_ref().map(|value| value.len())
            };
            let modified_ms = metadata
                .as_ref()
                .and_then(|value| value.modified().ok())
                .and_then(|value| value.duration_since(UNIX_EPOCH).ok())
                .map(|value| value.as_millis());

            Some(FileExplorerEntry {
                name,
                path: path.display().to_string(),
                is_dir,
                size_bytes,
                modified_ms,
            })
        })
        .collect::<Vec<_>>();

    entries.sort_by(|left, right| {
        if left.is_dir != right.is_dir {
            return right.is_dir.cmp(&left.is_dir);
        }

        left.name
            .to_ascii_lowercase()
            .cmp(&right.name.to_ascii_lowercase())
    });

    Ok(FileExplorerListResponse {
        current_path: current_path.display().to_string(),
        parent_path: current_path.parent().map(|parent| parent.display().to_string()),
        roots: collect_roots(&app),
        entries,
    })
}

fn normalize_extensions(extensions: Option<Vec<String>>) -> HashSet<String> {
    extensions
        .unwrap_or_default()
        .into_iter()
        .map(|value| value.trim().trim_start_matches('.').to_ascii_lowercase())
        .filter(|value| !value.is_empty())
        .collect()
}

fn resolve_start_path(app: &AppHandle, raw_path: Option<String>) -> Result<PathBuf, String> {
    if let Some(value) = raw_path {
        let trimmed = value.trim();
        if trimmed.is_empty() {
            return resolve_default_path(app);
        }

        let mut candidate = PathBuf::from(trimmed);
        if candidate.is_relative() {
            candidate = std::env::current_dir()
                .map_err(|err| format!("Failed to read current dir: {err}"))?
                .join(candidate);
        }

        if !candidate.exists() {
            return Err(format!("Path does not exist: {}", candidate.display()));
        }

        if candidate.is_file() {
            return candidate
                .parent()
                .map(|parent| parent.to_path_buf())
                .ok_or_else(|| format!("No parent directory for {}", candidate.display()));
        }

        return Ok(candidate);
    }

    resolve_default_path(app)
}

fn resolve_default_path(app: &AppHandle) -> Result<PathBuf, String> {
    if let Ok(path) = app.path().desktop_dir() {
        return Ok(path);
    }
    if let Ok(path) = app.path().document_dir() {
        return Ok(path);
    }
    if let Ok(path) = app.path().download_dir() {
        return Ok(path);
    }
    if let Ok(path) = app.path().home_dir() {
        return Ok(path);
    }

    std::env::current_dir().map_err(|err| format!("Failed to resolve default path: {err}"))
}

fn collect_roots(app: &AppHandle) -> Vec<FileExplorerRoot> {
    let mut roots = Vec::new();
    let mut seen = HashSet::new();

    let mut push_root = |label: &str, path: PathBuf| {
        let normalized = path.display().to_string();
        if normalized.is_empty() || !Path::new(&normalized).exists() {
            return;
        }

        if seen.insert(normalized.clone()) {
            roots.push(FileExplorerRoot {
                label: label.to_string(),
                path: normalized,
            });
        }
    };

    if let Ok(path) = app.path().desktop_dir() {
        push_root("Desktop", path);
    }
    if let Ok(path) = app.path().document_dir() {
        push_root("Documents", path);
    }
    if let Ok(path) = app.path().download_dir() {
        push_root("Downloads", path);
    }
    if let Ok(path) = app.path().home_dir() {
        push_root("Home", path);
    }

    #[cfg(target_os = "windows")]
    {
        for letter in b'A'..=b'Z' {
            let drive = format!("{}:\\", letter as char);
            let drive_path = PathBuf::from(&drive);
            if drive_path.exists() {
                push_root(&format!("{}:", letter as char), drive_path);
            }
        }
    }

    #[cfg(not(target_os = "windows"))]
    {
        push_root("Root", PathBuf::from("/"));
    }

    roots
}
