//! Component TOML detection — inspects the project root for marker files and
//! emits `[[component]]` entries for the detected tech stacks.

use std::path::Path;

/// Detect the tech stack from the project root and return TOML `[[component]]` entries.
///
/// Inspects well-known marker files at the project root to identify which
/// tech stacks are present, then emits one `[[component]]` entry per detected
/// stack with sensible default `setup` commands. If no markers are found, a
/// single fallback `app` component with an empty `setup` list is returned so
/// that the pipeline never breaks on an unknown stack.
pub(crate) fn detect_components_toml(root: &Path) -> String {
    let mut sections = Vec::new();

    if root.join("Cargo.toml").exists() {
        sections.push(
            "[[component]]\nname = \"server\"\npath = \".\"\nsetup = [\"cargo check\"]\n"
                .to_string(),
        );
    }

    if root.join("package.json").exists() {
        let setup_cmd = if root.join("pnpm-lock.yaml").exists() {
            "pnpm install"
        } else {
            "npm install"
        };
        sections.push(format!(
            "[[component]]\nname = \"frontend\"\npath = \".\"\nsetup = [\"{setup_cmd}\"]\n"
        ));
    }

    if root.join("pyproject.toml").exists() || root.join("requirements.txt").exists() {
        sections.push(
            "[[component]]\nname = \"python\"\npath = \".\"\nsetup = [\"pip install -r requirements.txt\"]\n"
                .to_string(),
        );
    }

    if root.join("go.mod").exists() {
        sections.push(
            "[[component]]\nname = \"go\"\npath = \".\"\nsetup = [\"go build ./...\"]\n"
                .to_string(),
        );
    }

    if root.join("Gemfile").exists() {
        sections.push(
            "[[component]]\nname = \"ruby\"\npath = \".\"\nsetup = [\"bundle install\"]\n"
                .to_string(),
        );
    }

    if sections.is_empty() {
        // No tech stack markers detected — emit a single generic component
        // with an empty setup list. The ONBOARDING_PROMPT instructs the chat
        // agent to inspect the project and replace this with real definitions.
        sections.push("[[component]]\nname = \"app\"\npath = \".\"\nsetup = []\n".to_string());
    }

    sections.join("\n")
}

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

    #[test]
    fn detect_no_markers_returns_fallback_components() {
        let dir = tempdir().unwrap();
        let toml = detect_components_toml(dir.path());
        // At least one [[component]] entry should always be present
        assert!(
            toml.contains("[[component]]"),
            "should always emit at least one component"
        );
        // Fallback should use a generic app component with empty setup
        assert!(
            toml.contains("name = \"app\""),
            "fallback should use generic 'app' component name"
        );
        assert!(
            toml.contains("setup = []"),
            "fallback should have empty setup list"
        );
        // Must not contain Rust-specific commands in a non-Rust project
        assert!(
            !toml.contains("cargo"),
            "fallback must not contain Rust-specific commands"
        );
    }

    #[test]
    fn detect_cargo_toml_generates_rust_component() {
        let dir = tempdir().unwrap();
        fs::write(
            dir.path().join("Cargo.toml"),
            "[package]\nname = \"test\"\n",
        )
        .unwrap();

        let toml = detect_components_toml(dir.path());
        assert!(toml.contains("name = \"server\""));
        assert!(toml.contains("setup = [\"cargo check\"]"));
    }

    #[test]
    fn detect_package_json_with_pnpm_lock_generates_pnpm_component() {
        let dir = tempdir().unwrap();
        fs::write(dir.path().join("package.json"), "{}").unwrap();
        fs::write(dir.path().join("pnpm-lock.yaml"), "").unwrap();

        let toml = detect_components_toml(dir.path());
        assert!(toml.contains("name = \"frontend\""));
        assert!(toml.contains("setup = [\"pnpm install\"]"));
    }

    #[test]
    fn detect_package_json_without_pnpm_lock_generates_npm_component() {
        let dir = tempdir().unwrap();
        fs::write(dir.path().join("package.json"), "{}").unwrap();

        let toml = detect_components_toml(dir.path());
        assert!(toml.contains("name = \"frontend\""));
        assert!(toml.contains("setup = [\"npm install\"]"));
    }

    #[test]
    fn detect_pyproject_toml_generates_python_component() {
        let dir = tempdir().unwrap();
        fs::write(
            dir.path().join("pyproject.toml"),
            "[project]\nname = \"test\"\n",
        )
        .unwrap();

        let toml = detect_components_toml(dir.path());
        assert!(toml.contains("name = \"python\""));
        assert!(toml.contains("pip install"));
    }

    #[test]
    fn detect_requirements_txt_generates_python_component() {
        let dir = tempdir().unwrap();
        fs::write(dir.path().join("requirements.txt"), "flask\n").unwrap();

        let toml = detect_components_toml(dir.path());
        assert!(toml.contains("name = \"python\""));
        assert!(toml.contains("pip install"));
    }

    #[test]
    fn detect_go_mod_generates_go_component() {
        let dir = tempdir().unwrap();
        fs::write(dir.path().join("go.mod"), "module example.com/app\n").unwrap();

        let toml = detect_components_toml(dir.path());
        assert!(toml.contains("name = \"go\""));
        assert!(toml.contains("setup = [\"go build ./...\"]"));
    }

    #[test]
    fn detect_gemfile_generates_ruby_component() {
        let dir = tempdir().unwrap();
        fs::write(
            dir.path().join("Gemfile"),
            "source \"https://rubygems.org\"\n",
        )
        .unwrap();

        let toml = detect_components_toml(dir.path());
        assert!(toml.contains("name = \"ruby\""));
        assert!(toml.contains("setup = [\"bundle install\"]"));
    }

    #[test]
    fn no_rust_commands_in_go_project() {
        let dir = tempdir().unwrap();
        fs::write(dir.path().join("go.mod"), "module example.com/app\n").unwrap();

        let toml = detect_components_toml(dir.path());
        assert!(
            !toml.contains("cargo"),
            "go project must not contain cargo commands"
        );
        assert!(toml.contains("go build"), "go project must use Go tooling");
    }

    #[test]
    fn no_rust_commands_in_node_project() {
        let dir = tempdir().unwrap();
        fs::write(dir.path().join("package.json"), "{}").unwrap();

        let toml = detect_components_toml(dir.path());
        assert!(
            !toml.contains("cargo"),
            "node project must not contain cargo commands"
        );
        assert!(
            toml.contains("npm install"),
            "node project must use npm tooling"
        );
    }

    #[test]
    fn no_rust_commands_when_no_stack_detected() {
        let dir = tempdir().unwrap();

        let toml = detect_components_toml(dir.path());
        assert!(
            !toml.contains("cargo"),
            "unknown stack must not contain cargo commands"
        );
        // setup list must be empty
        assert!(
            toml.contains("setup = []"),
            "unknown stack must have empty setup list"
        );
    }

    #[test]
    fn detect_multiple_markers_generates_multiple_components() {
        let dir = tempdir().unwrap();
        fs::write(
            dir.path().join("Cargo.toml"),
            "[package]\nname = \"server\"\n",
        )
        .unwrap();
        fs::write(dir.path().join("package.json"), "{}").unwrap();

        let toml = detect_components_toml(dir.path());
        assert!(toml.contains("name = \"server\""));
        assert!(toml.contains("name = \"frontend\""));
        // Both component entries should be present
        let component_count = toml.matches("[[component]]").count();
        assert_eq!(component_count, 2);
    }

    #[test]
    fn detect_no_fallback_when_markers_found() {
        let dir = tempdir().unwrap();
        fs::write(dir.path().join("Cargo.toml"), "[package]\nname = \"x\"\n").unwrap();

        let toml = detect_components_toml(dir.path());
        // The fallback "app" component should NOT appear when a real stack is detected
        assert!(!toml.contains("name = \"app\""));
    }
}
