//! Lint script detection — generates `script/lint` content for new projects.

use std::path::Path;

use super::detect_node_lint_cmd;

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

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

    #[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"));
    }
}
