//! Path mapping from file paths to ConceptPath segments.

use std::path::Path;

use crate::config::AphoriaConfig;
use crate::types::Language;

/// Maps file paths to ConceptPath segments.
pub struct PathMapper {
    /// Project name.
    project_name: String,
}

impl PathMapper {
    /// Create a new path mapper for a project.
    pub fn new(root: &Path, config: &AphoriaConfig) -> Self {
        let project_name =
            config.project.name.clone().or_else(|| detect_project_name(root)).unwrap_or_else(
                || root.file_name().and_then(|s| s.to_str()).unwrap_or("unknown").to_string(),
            );

        Self { project_name }
    }

    /// Convert a relative file path to ConceptPath segments.
    ///
    /// Language-specific stripping rules remove boilerplate directories.
    pub fn to_segments(&self, relative_path: &str, language: Language) -> Vec<String> {
        let mut segments = Vec::new();

        // Add language prefix
        let lang_prefix = match language {
            Language::Rust | Language::CargoManifest => "rust",
            Language::Go | Language::GoMod => "go",
            Language::Python | Language::PythonManifest => "python",
            Language::TypeScript => "typescript",
            Language::JavaScript | Language::NpmManifest => "javascript",
            Language::Cpp => "cpp",
            Language::Java => "java",
            Language::Php => "php",
            Language::Ruby => "ruby",
            Language::CSharp => "csharp",
            Language::Ini | Language::Properties => "config",
            Language::Yaml
            | Language::Toml
            | Language::Json
            | Language::Dotenv
            | Language::Terraform => "config",
            Language::Docker => "docker",
            Language::Unknown => "unknown",
        };
        segments.push(lang_prefix.to_string());

        // Add project name
        segments.push(self.project_name.clone());

        // Process path components
        let path = Path::new(relative_path);
        let components: Vec<&str> =
            path.components().filter_map(|c| c.as_os_str().to_str()).collect();

        // Apply language-specific stripping
        let stripped = strip_boilerplate(&components, language);

        // Remove file extension from last component
        if let Some((last, rest)) = stripped.split_last() {
            for component in rest {
                segments.push((*component).to_string());
            }
            // Strip extension
            let stem = Path::new(last).file_stem().and_then(|s| s.to_str()).unwrap_or(last);
            segments.push(stem.to_string());
        }

        segments
    }
}

/// Strip boilerplate directories based on language conventions.
///
/// Removes common structural directories that don't add semantic meaning:
/// - Rust: `src/`, `crates/`
/// - Go: `cmd/`, `internal/`, `pkg/`
/// - Python: `src/`, `lib/`
/// - JS/TS: `src/`, `lib/`
fn strip_boilerplate<'a>(components: &'a [&'a str], language: Language) -> Vec<&'a str> {
    let skip_dirs: &[&str] = match language {
        Language::Rust | Language::CargoManifest => &["src", "crates"],
        Language::Go | Language::GoMod => &["cmd", "internal", "pkg"],
        Language::Python | Language::PythonManifest => &["src", "lib"],
        Language::TypeScript | Language::JavaScript | Language::NpmManifest => &["src", "lib"],
        _ => &[],
    };

    components.iter().filter(|c| !skip_dirs.contains(c)).copied().collect()
}

/// Detect project name from manifest files.
fn detect_project_name(root: &Path) -> Option<String> {
    // Try Cargo.toml
    if let Ok(content) = std::fs::read_to_string(root.join("Cargo.toml")) {
        if let Ok(parsed) = content.parse::<toml::Table>() {
            if let Some(package) = parsed.get("package").and_then(|p| p.as_table()) {
                if let Some(name) = package.get("name").and_then(|n| n.as_str()) {
                    return Some(name.to_string());
                }
            }
        }
    }

    // Try go.mod
    if let Ok(content) = std::fs::read_to_string(root.join("go.mod")) {
        for line in content.lines() {
            if line.starts_with("module ") {
                let module = line.trim_start_matches("module ").trim();
                // Extract last segment of module path
                return Some(module.rsplit('/').next().unwrap_or(module).to_string());
            }
        }
    }

    // Try package.json
    if let Ok(content) = std::fs::read_to_string(root.join("package.json")) {
        if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&content) {
            if let Some(name) = parsed.get("name").and_then(|n| n.as_str()) {
                return Some(name.to_string());
            }
        }
    }

    None
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;

    #[test]
    fn test_rust_path_mapping() {
        let dir = TempDir::new().expect("create temp dir");
        let config = AphoriaConfig {
            project: crate::config::ProjectConfig {
                name: Some("citadeldb".to_string()),
                language: None,
            },
            ..Default::default()
        };

        let mapper = PathMapper::new(dir.path(), &config);
        let segments = mapper.to_segments("crates/citadeldb/src/auth/jwt.rs", Language::Rust);

        assert_eq!(segments, vec!["rust", "citadeldb", "citadeldb", "auth", "jwt"]);
    }

    #[test]
    fn test_go_path_mapping() {
        let dir = TempDir::new().expect("create temp dir");
        let config = AphoriaConfig {
            project: crate::config::ProjectConfig {
                name: Some("myapp".to_string()),
                language: None,
            },
            ..Default::default()
        };

        let mapper = PathMapper::new(dir.path(), &config);
        let segments = mapper.to_segments("internal/auth/jwt/validator.go", Language::Go);

        assert_eq!(segments, vec!["go", "myapp", "auth", "jwt", "validator"]);
    }

    #[test]
    fn test_config_path_mapping() {
        let dir = TempDir::new().expect("create temp dir");
        let config = AphoriaConfig {
            project: crate::config::ProjectConfig {
                name: Some("myapp".to_string()),
                language: None,
            },
            ..Default::default()
        };

        let mapper = PathMapper::new(dir.path(), &config);
        let segments = mapper.to_segments("config/production.yaml", Language::Yaml);

        assert_eq!(segments, vec!["config", "myapp", "config", "production"]);
    }

    #[test]
    fn test_strip_boilerplate() {
        let components = vec!["src", "auth", "jwt.rs"];
        let result = strip_boilerplate(&components, Language::Rust);
        assert_eq!(result, vec!["auth", "jwt.rs"]);

        // Multiple boilerplate dirs (crates/xxx/src/)
        let components = vec!["crates", "mylib", "src", "auth", "jwt.rs"];
        let result = strip_boilerplate(&components, Language::Rust);
        assert_eq!(result, vec!["mylib", "auth", "jwt.rs"]);

        let components = vec!["internal", "auth", "jwt", "validator.go"];
        let result = strip_boilerplate(&components, Language::Go);
        assert_eq!(result, vec!["auth", "jwt", "validator.go"]);
    }
}
