//! Stack detection — inspect the project root for marker files and emit
//! TOML `[[component]]` entries plus `script/build|lint|test` content.

use std::fs;
use std::path::Path;

use super::templates::STORY_KIT_SCRIPT_TEST;

/// 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")
}

/// Detect the appropriate Node.js test command for a directory containing `package.json`.
///
/// Reads the `package.json` content to identify known test runners (vitest, jest).
/// Falls back to `npm test` or `pnpm test` based on which lock file is present.
fn detect_node_test_cmd(pkg_dir: &Path) -> String {
    let has_pnpm = pkg_dir.join("pnpm-lock.yaml").exists();
    let content = std::fs::read_to_string(pkg_dir.join("package.json")).unwrap_or_default();

    if content.contains("\"vitest\"") {
        let pm = if has_pnpm { "pnpm" } else { "npx" };
        return format!("{} vitest run", pm);
    }
    if content.contains("\"jest\"") {
        let pm = if has_pnpm { "pnpm" } else { "npx" };
        return format!("{} jest", pm);
    }

    if has_pnpm {
        "pnpm test".to_string()
    } else {
        "npm test".to_string()
    }
}

/// Detect the appropriate Node.js build command for a directory containing `package.json`.
fn detect_node_build_cmd(pkg_dir: &Path) -> String {
    if pkg_dir.join("pnpm-lock.yaml").exists() {
        "pnpm run build".to_string()
    } else {
        "npm run build".to_string()
    }
}

/// Detect the appropriate Node.js lint command for a directory containing `package.json`.
///
/// Reads the `package.json` content to identify eslint. Falls back to
/// `npm run lint` or `pnpm run lint` based on which lock file is present.
fn detect_node_lint_cmd(pkg_dir: &Path) -> String {
    let has_pnpm = pkg_dir.join("pnpm-lock.yaml").exists();
    let content = std::fs::read_to_string(pkg_dir.join("package.json")).unwrap_or_default();
    if content.contains("\"eslint\"") {
        let pm = if has_pnpm { "pnpm" } else { "npx" };
        return format!("{pm} eslint .");
    }
    if has_pnpm {
        "pnpm run lint".to_string()
    } else {
        "npm run lint".to_string()
    }
}

/// Generate `script/build` content for a new project at `root`.
///
/// Inspects well-known marker files to identify which tech stacks are present
/// and emits the appropriate build commands. Multi-stack projects get combined
/// commands run sequentially. Falls back to a generic stub when no markers
/// are found so the scaffold is always valid.
///
/// For projects with a frontend in a known subdirectory (`frontend/`, `client/`),
/// the build command is detected from the presence of `pnpm-lock.yaml`.
pub(crate) fn detect_script_build(root: &Path) -> String {
    let mut commands: Vec<String> = Vec::new();

    if root.join("Cargo.toml").exists() {
        commands.push("cargo build --release".to_string());
    }

    if root.join("package.json").exists() {
        commands.push(detect_node_build_cmd(root));
    }

    // Detect frontend in known subdirectories (e.g. frontend/, client/)
    for subdir in &["frontend", "client"] {
        let sub_path = root.join(subdir);
        if sub_path.join("package.json").exists() {
            let cmd = detect_node_build_cmd(&sub_path);
            commands.push(format!("(cd {} && {})", subdir, cmd));
        }
    }

    if root.join("pyproject.toml").exists() {
        commands.push("python -m build".to_string());
    }

    if root.join("go.mod").exists() {
        commands.push("go build ./...".to_string());
    }

    if commands.is_empty() {
        return "#!/usr/bin/env bash\nset -euo pipefail\n\n# Add your project's build commands here.\necho \"No build configured\"\n".to_string();
    }

    let mut script = "#!/usr/bin/env bash\nset -euo pipefail\n\n".to_string();
    for cmd in commands {
        script.push_str(&cmd);
        script.push('\n');
    }
    script
}

/// Generate `script/lint` content for a new project at `root`.
///
/// Inspects well-known marker files to identify which linters are present
/// and emits the appropriate lint commands. Multi-stack projects get combined
/// commands run sequentially. Falls back to a generic stub when no markers
/// are found so the scaffold is always valid.
///
/// For projects with a frontend in a known subdirectory (`frontend/`, `client/`),
/// the lint command is detected from the `package.json` (eslint, npm, pnpm).
pub(crate) fn detect_script_lint(root: &Path) -> String {
    let mut commands: Vec<String> = Vec::new();

    if root.join("Cargo.toml").exists() {
        commands.push("cargo fmt --all --check".to_string());
        commands.push("cargo clippy -- -D warnings".to_string());
    }

    if root.join("package.json").exists() {
        commands.push(detect_node_lint_cmd(root));
    }

    // Detect frontend in known subdirectories (e.g. frontend/, client/)
    for subdir in &["frontend", "client"] {
        let sub_path = root.join(subdir);
        if sub_path.join("package.json").exists() {
            let cmd = detect_node_lint_cmd(&sub_path);
            commands.push(format!("(cd {} && {})", subdir, cmd));
        }
    }

    if root.join("pyproject.toml").exists() || root.join("requirements.txt").exists() {
        let mut content = std::fs::read_to_string(root.join("pyproject.toml")).unwrap_or_default();
        content
            .push_str(&std::fs::read_to_string(root.join("requirements.txt")).unwrap_or_default());
        if content.contains("ruff") {
            commands.push("ruff check .".to_string());
        } else {
            commands.push("flake8 .".to_string());
        }
    }

    if root.join("go.mod").exists() {
        commands.push("go vet ./...".to_string());
    }

    if commands.is_empty() {
        return "#!/usr/bin/env bash\nset -euo pipefail\n\n# Add your project's lint commands here.\necho \"No linters configured\"\n".to_string();
    }

    let mut script = "#!/usr/bin/env bash\nset -euo pipefail\n\n".to_string();
    for cmd in commands {
        script.push_str(&cmd);
        script.push('\n');
    }
    script
}

/// Generate `script/test` content for a new project at `root`.
///
/// Inspects well-known marker files to identify which tech stacks are present
/// and emits the appropriate test commands. Multi-stack projects get combined
/// commands run sequentially. Falls back to the generic stub when no markers
/// are found so the scaffold is always valid.
///
/// For projects with a frontend in a known subdirectory (`frontend/`, `client/`),
/// the test runner is detected from the `package.json` (vitest, jest, npm, pnpm).
pub(crate) fn detect_script_test(root: &Path) -> String {
    let mut commands: Vec<String> = Vec::new();

    if root.join("Cargo.toml").exists() {
        commands.push("cargo test".to_string());
    }

    if root.join("package.json").exists() {
        if root.join("pnpm-lock.yaml").exists() {
            commands.push("pnpm test".to_string());
        } else {
            commands.push("npm test".to_string());
        }
    }

    // Detect frontend in known subdirectories (e.g. frontend/, client/)
    for subdir in &["frontend", "client"] {
        let sub_path = root.join(subdir);
        if sub_path.join("package.json").exists() {
            let cmd = detect_node_test_cmd(&sub_path);
            commands.push(format!("(cd {} && {})", subdir, cmd));
        }
    }

    if root.join("pyproject.toml").exists() || root.join("requirements.txt").exists() {
        commands.push("pytest".to_string());
    }

    if root.join("go.mod").exists() {
        commands.push("go test ./...".to_string());
    }

    if commands.is_empty() {
        return STORY_KIT_SCRIPT_TEST.to_string();
    }

    let mut script = "#!/usr/bin/env bash\nset -euo pipefail\n\n".to_string();
    for cmd in commands {
        script.push_str(&cmd);
        script.push('\n');
    }
    script
}

/// Generate a `project.toml` for a new project at `root`.
///
/// Detects the tech stack via [`detect_components_toml`] and combines the
/// resulting `[[component]]` entries with the default project settings.
#[cfg(test)]
mod tests {
    use super::*;
    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\""));
    }

    #[test]
    fn detect_script_test_no_markers_returns_stub() {
        let dir = tempdir().unwrap();
        let script = detect_script_test(dir.path());
        assert!(
            script.contains("No tests configured"),
            "fallback should contain the generic stub message"
        );
        assert!(script.starts_with("#!/usr/bin/env bash"));
    }

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

        let script = detect_script_test(dir.path());
        assert!(
            script.contains("cargo test"),
            "Rust project should run cargo test"
        );
        assert!(!script.contains("No tests configured"));
    }

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

        let script = detect_script_test(dir.path());
        assert!(
            script.contains("npm test"),
            "Node project without pnpm-lock should run npm test"
        );
        assert!(!script.contains("No tests configured"));
    }

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

        let script = detect_script_test(dir.path());
        assert!(
            script.contains("pnpm test"),
            "Node project with pnpm-lock should run pnpm test"
        );
        // "pnpm test" is a substring of itself; verify there's no bare "npm test" line
        assert!(
            !script.lines().any(|l| l.trim() == "npm test"),
            "should not use npm when pnpm-lock.yaml is present"
        );
    }

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

        let script = detect_script_test(dir.path());
        assert!(
            script.contains("pytest"),
            "Python project should run pytest"
        );
        assert!(!script.contains("No tests configured"));
    }

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

        let script = detect_script_test(dir.path());
        assert!(
            script.contains("pytest"),
            "Python project (requirements.txt) should run pytest"
        );
    }

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

        let script = detect_script_test(dir.path());
        assert!(
            script.contains("go test ./..."),
            "Go project should run go test ./..."
        );
        assert!(!script.contains("No tests configured"));
    }

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

        let script = detect_script_test(dir.path());
        assert!(
            script.contains("go test ./..."),
            "multi-stack should include Go test command"
        );
        assert!(
            script.contains("npm test"),
            "multi-stack should include Node test command"
        );
    }

    #[test]
    fn detect_script_test_frontend_subdir_with_vitest_uses_npx_vitest() {
        let dir = tempdir().unwrap();
        let frontend = dir.path().join("frontend");
        fs::create_dir_all(&frontend).unwrap();
        fs::write(
            frontend.join("package.json"),
            r#"{"devDependencies":{"vitest":"^1.0.0"},"scripts":{"test":"vitest run"}}"#,
        )
        .unwrap();

        let script = detect_script_test(dir.path());
        assert!(
            script.contains("vitest run"),
            "frontend with vitest should emit vitest run"
        );
        assert!(
            script.contains("cd frontend"),
            "should cd into the frontend directory"
        );
        assert!(
            !script.contains("No tests configured"),
            "should not use stub when frontend is detected"
        );
    }

    #[test]
    fn detect_script_test_frontend_subdir_with_jest_uses_npx_jest() {
        let dir = tempdir().unwrap();
        let frontend = dir.path().join("frontend");
        fs::create_dir_all(&frontend).unwrap();
        fs::write(
            frontend.join("package.json"),
            r#"{"devDependencies":{"jest":"^29.0.0"},"scripts":{"test":"jest"}}"#,
        )
        .unwrap();

        let script = detect_script_test(dir.path());
        assert!(
            script.contains("jest"),
            "frontend with jest should emit jest"
        );
        assert!(
            script.contains("cd frontend"),
            "should cd into the frontend directory"
        );
    }

    #[test]
    fn detect_script_test_frontend_subdir_no_known_runner_uses_npm_test() {
        let dir = tempdir().unwrap();
        let frontend = dir.path().join("frontend");
        fs::create_dir_all(&frontend).unwrap();
        fs::write(
            frontend.join("package.json"),
            r#"{"scripts":{"test":"mocha"}}"#,
        )
        .unwrap();

        let script = detect_script_test(dir.path());
        assert!(
            script.contains("npm test"),
            "frontend without known runner should fall back to npm test"
        );
        assert!(script.contains("cd frontend"));
    }

    #[test]
    fn detect_script_test_frontend_subdir_pnpm_uses_pnpm_vitest() {
        let dir = tempdir().unwrap();
        let frontend = dir.path().join("frontend");
        fs::create_dir_all(&frontend).unwrap();
        fs::write(
            frontend.join("package.json"),
            r#"{"devDependencies":{"vitest":"^1.0.0"}}"#,
        )
        .unwrap();
        fs::write(frontend.join("pnpm-lock.yaml"), "").unwrap();

        let script = detect_script_test(dir.path());
        assert!(
            script.contains("pnpm vitest run"),
            "pnpm frontend with vitest should use pnpm vitest run"
        );
    }

    #[test]
    fn detect_script_test_rust_plus_frontend_subdir_both_included() {
        let dir = tempdir().unwrap();
        fs::write(
            dir.path().join("Cargo.toml"),
            "[package]\nname = \"server\"\n",
        )
        .unwrap();
        let frontend = dir.path().join("frontend");
        fs::create_dir_all(&frontend).unwrap();
        fs::write(
            frontend.join("package.json"),
            r#"{"devDependencies":{"vitest":"^1.0.0"}}"#,
        )
        .unwrap();

        let script = detect_script_test(dir.path());
        assert!(
            script.contains("cargo test"),
            "Rust + frontend should include cargo test"
        );
        assert!(
            script.contains("vitest run"),
            "Rust + frontend should include vitest run"
        );
        assert!(
            script.contains("cd frontend"),
            "Rust + frontend should cd into frontend"
        );
    }

    #[test]
    fn detect_script_test_client_subdir_detected() {
        let dir = tempdir().unwrap();
        let client = dir.path().join("client");
        fs::create_dir_all(&client).unwrap();
        fs::write(
            client.join("package.json"),
            r#"{"scripts":{"test":"jest"}}"#,
        )
        .unwrap();

        let script = detect_script_test(dir.path());
        assert!(
            script.contains("cd client"),
            "client/ subdir should also be detected"
        );
    }

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

        let script = detect_script_test(dir.path());
        assert!(
            script.starts_with("#!/usr/bin/env bash\nset -euo pipefail\n"),
            "generated script should start with bash shebang and set -euo pipefail"
        );
    }

    #[test]
    fn detect_script_build_no_markers_returns_stub() {
        let dir = tempdir().unwrap();
        let script = detect_script_build(dir.path());
        assert!(
            script.contains("No build configured"),
            "fallback should contain the generic stub message"
        );
        assert!(script.starts_with("#!/usr/bin/env bash"));
    }

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

        let script = detect_script_build(dir.path());
        assert!(
            script.contains("cargo build --release"),
            "Rust project should run cargo build --release"
        );
        assert!(!script.contains("No build configured"));
    }

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

        let script = detect_script_build(dir.path());
        assert!(
            script.contains("npm run build"),
            "Node project without pnpm-lock should run npm run build"
        );
    }

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

        let script = detect_script_build(dir.path());
        assert!(
            script.contains("pnpm run build"),
            "Node project with pnpm-lock should run pnpm run build"
        );
        assert!(
            !script.lines().any(|l| l.trim() == "npm run build"),
            "should not use npm when pnpm-lock.yaml is present"
        );
    }

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

        let script = detect_script_build(dir.path());
        assert!(
            script.contains("go build ./..."),
            "Go project should run go build ./..."
        );
    }

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

        let script = detect_script_build(dir.path());
        assert!(
            script.contains("python -m build"),
            "Python project should run python -m build"
        );
    }

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

        let script = detect_script_build(dir.path());
        assert!(
            script.contains("cd frontend"),
            "frontend subdir should be detected for build"
        );
        assert!(script.contains("npm run build"));
    }

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

        let script = detect_script_build(dir.path());
        assert!(script.contains("cargo build --release"));
        assert!(script.contains("cd frontend"));
        assert!(script.contains("npm run build"));
    }

    #[test]
    fn detect_script_lint_no_markers_returns_stub() {
        let dir = tempdir().unwrap();
        let script = detect_script_lint(dir.path());
        assert!(
            script.contains("No linters configured"),
            "fallback should contain the generic stub message"
        );
        assert!(script.starts_with("#!/usr/bin/env bash"));
    }

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

        let script = detect_script_lint(dir.path());
        assert!(
            script.contains("cargo fmt --all --check"),
            "Rust project should check formatting"
        );
        assert!(
            script.contains("cargo clippy -- -D warnings"),
            "Rust project should run clippy"
        );
        assert!(!script.contains("No linters configured"));
    }

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

        let script = detect_script_lint(dir.path());
        assert!(
            script.contains("npm run lint"),
            "Node project without eslint dep should fall back to npm run lint"
        );
    }

    #[test]
    fn detect_script_lint_package_json_with_eslint_uses_npx_eslint() {
        let dir = tempdir().unwrap();
        fs::write(
            dir.path().join("package.json"),
            r#"{"devDependencies":{"eslint":"^8.0.0"}}"#,
        )
        .unwrap();

        let script = detect_script_lint(dir.path());
        assert!(
            script.contains("npx eslint ."),
            "Node project with eslint should use npx eslint ."
        );
    }

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

        let script = detect_script_lint(dir.path());
        assert!(
            script.contains("pnpm eslint ."),
            "pnpm project with eslint should use pnpm eslint ."
        );
    }

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

        let script = detect_script_lint(dir.path());
        assert!(
            script.contains("flake8 ."),
            "Python project without ruff should use flake8"
        );
    }

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

        let script = detect_script_lint(dir.path());
        assert!(
            script.contains("ruff check ."),
            "Python project with ruff configured should use ruff"
        );
        assert!(
            !script.contains("flake8"),
            "should not use flake8 when ruff is configured"
        );
    }

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

        let script = detect_script_lint(dir.path());
        assert!(
            script.contains("go vet ./..."),
            "Go project should run go vet ./..."
        );
    }

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

        let script = detect_script_lint(dir.path());
        assert!(
            script.contains("cd frontend"),
            "frontend subdir should be detected for lint"
        );
    }

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

        let script = detect_script_lint(dir.path());
        assert!(script.contains("cargo fmt --all --check"));
        assert!(script.contains("cargo clippy -- -D warnings"));
        assert!(script.contains("cd frontend"));
    }
}
