"""Language profile definitions for multi-language trust5 support (23 languages)."""

from __future__ import annotations

from dataclasses import asdict, dataclass
from typing import Any

_COMMON_SKIP = (".trust5", ".git")


@dataclass(frozen=True)
class LanguageProfile:
    language: str
    extensions: tuple[str, ...]
    test_command: tuple[str, ...]
    test_verify_command: str
    lint_commands: tuple[str, ...]
    syntax_check_command: tuple[str, ...] | None
    package_install_prefix: str
    lsp_language_id: str
    skip_dirs: tuple[str, ...]
    manifest_files: tuple[str, ...]
    prompt_hints: str
    lint_check_commands: tuple[str, ...] = ()
    coverage_command: tuple[str, ...] | None = None
    security_command: tuple[str, ...] | None = None
    frameworks: tuple[str, ...] = ()
    source_roots: tuple[str, ...] = ()  # directories to check as source roots
    path_env_var: str = ""  # env var for test runner (e.g. PYTHONPATH=src)
    syntax_check_tool_names: tuple[str, ...] = ()  # tool names that are syntax-only (for lint dedup)
    dev_dependencies: tuple[str, ...] = ()  # packages auto-installed before lint/test if missing
    required_project_files: tuple[str, ...] = ()  # critical files that must exist in generated projects
    tool_check_commands: tuple[str, ...] = ()  # commands to verify required tools are installed
    manifest_validators: tuple[str, ...] = ()  # commands to validate manifest file syntax
    test_discovery_command: str | None = None  # command to discover tests without running them

    def to_dict(self) -> dict[str, Any]:
        return asdict(self)


def _p(
    lang: str,
    exts: tuple[str, ...],
    test: tuple[str, ...],
    verify: str,
    lint: tuple[str, ...],
    syntax: tuple[str, ...] | None,
    pkg: str,
    lsp_id: str,
    skip: tuple[str, ...],
    manifests: tuple[str, ...],
    hints: str,
    cov: tuple[str, ...] | None = None,
    sec: tuple[str, ...] | None = None,
    fw: tuple[str, ...] = (),
    lint_check: tuple[str, ...] = (),
    src_roots: tuple[str, ...] = (),
    path_var: str = "",
    syntax_tool_names: tuple[str, ...] = (),
    dev_deps: tuple[str, ...] = (),
    required_files: tuple[str, ...] = (),
    tool_checks: tuple[str, ...] = (),
    manifest_vals: tuple[str, ...] = (),
    test_disc: str | None = None,
) -> LanguageProfile:
    return LanguageProfile(
        language=lang,
        extensions=exts,
        test_command=test,
        test_verify_command=verify,
        lint_commands=lint,
        lint_check_commands=lint_check,
        syntax_check_command=syntax,
        package_install_prefix=pkg,
        lsp_language_id=lsp_id,
        skip_dirs=skip + _COMMON_SKIP,
        manifest_files=manifests,
        prompt_hints=hints,
        coverage_command=cov,
        security_command=sec,
        frameworks=fw,
        source_roots=src_roots,
        path_env_var=path_var,
        syntax_check_tool_names=syntax_tool_names,
        dev_dependencies=dev_deps,
        required_project_files=required_files,
        tool_check_commands=tool_checks,
        manifest_validators=manifest_vals,
        test_discovery_command=test_disc,
    )


PROFILES: dict[str, LanguageProfile] = {
    "python": _p(
        "python",
        (".py",),
        ("python3", "-m", "pytest", "-v", "--tb=long", "-x"),
        'Bash("pytest -v --tb=short")',
        ("python3 -m ruff check --fix . 2>/dev/null", "python3 -m black . 2>/dev/null"),
        ("python3", "-m", "compileall", "-q", "."),
        "pip install",
        "python",
        ("__pycache__", ".venv", "venv", ".tox", ".nox", ".eggs"),
        ("pyproject.toml", "requirements.txt", "setup.py"),
        "Language: Python. Use pytest for testing. Follow PEP 8. "
        "Use argparse or click for CLI. Use the project venv for all commands.",
        ("python3", "-m", "pytest", "--cov=.", "--cov-report=term-missing", "-q", "--ignore=.venv", "--ignore=venv"),
        (
            "python3",
            "-m",
            "bandit",
            "-r",
            ".",
            "-q",
            "-f",
            "json",
            "--exclude",
            ".venv,venv,.tox,.nox,.eggs,tests,test",
        ),
        ("FastAPI", "Django", "Flask"),
        lint_check=("python3 -m ruff check --output-format=concise .",),
        src_roots=("src", "lib"),
        path_var="PYTHONPATH",
        syntax_tool_names=("py_compile", "compileall"),
        dev_deps=("ruff", "pytest-timeout"),
        required_files=("pyproject.toml",),
        tool_checks=("python3 -c 'import pytest'", "python3 -c 'import ruff'"),
        manifest_vals=(
            "python3 -c 'import tomllib,pathlib; tomllib.loads(pathlib.Path(\"pyproject.toml\").read_text())'",
        ),
        test_disc="pytest --collect-only -q 2>/dev/null | tail -1",
    ),
    "go": _p(
        "go",
        (".go",),
        ("go", "test", "-v", "-race", "./..."),
        'Bash("go test -v ./...")',
        ("gofmt -w . 2>/dev/null", "go vet ./... 2>/dev/null"),
        ("go", "vet", "./..."),
        "go get",
        "go",
        ("vendor",),
        ("go.mod", "go.sum"),
        "Language: Go. Use testing package. Table-driven tests. cobra for CLI.",
        ("go", "test", "-coverprofile=coverage.out", "-covermode=atomic", "./..."),
        ("gosec", "-fmt=json", "-quiet", "-exclude-dir=vendor", "./..."),
        ("Gin", "Echo", "Fiber", "Chi"),
        lint_check=("gofmt -l .", "go vet ./... 2>&1"),
        required_files=("go.mod",),
        tool_checks=("go version",),
        manifest_vals=("go mod verify",),
        test_disc="go test -list '.*' ./... 2>/dev/null | head -1",
    ),
    "typescript": _p(
        "typescript",
        (".ts", ".tsx"),
        ("npx", "jest", "--verbose"),
        'Bash("npx jest --verbose")',
        ("npx eslint --fix . 2>/dev/null", "npx prettier --write . 2>/dev/null"),
        ("npx", "tsc", "--noEmit"),
        "npm install",
        "typescript",
        ("node_modules", "dist", ".next"),
        ("package.json", "tsconfig.json"),
        "Language: TypeScript. Use Jest or Vitest. ESLint + strict mode.",
        ("npx", "jest", "--coverage", "--coverageReporters=text"),
        ("npx", "audit-ci", "--moderate"),
        ("Next.js", "React", "NestJS", "Express"),
        lint_check=("npx eslint --format=unix .",),
        required_files=("package.json", "tsconfig.json"),
        tool_checks=("npx --version",),
        manifest_vals=("node -e \"JSON.parse(require('fs').readFileSync('package.json','utf8'))\"",),
        test_disc="npx jest --listTests 2>/dev/null | wc -l",
    ),
    "javascript": _p(
        "javascript",
        (".js", ".jsx", ".mjs", ".cjs"),
        ("npx", "jest", "--verbose"),
        'Bash("npx jest --verbose")',
        ("npx eslint --fix . 2>/dev/null", "npx prettier --write . 2>/dev/null"),
        None,
        "npm install",
        "javascript",
        ("node_modules", "dist"),
        ("package.json",),
        "Language: JavaScript ES2024+. Use Jest or Vitest. ESLint rules.",
        ("npx", "jest", "--coverage", "--coverageReporters=text"),
        ("npx", "audit-ci", "--moderate"),
        ("React", "Vue", "Express", "Fastify"),
        lint_check=("npx eslint --format=unix .",),
        required_files=("package.json",),
        tool_checks=("npx --version",),
        manifest_vals=("node -e \"JSON.parse(require('fs').readFileSync('package.json','utf8'))\"",),
        test_disc="npx jest --listTests 2>/dev/null | wc -l",
    ),
    "rust": _p(
        "rust",
        (".rs",),
        ("cargo", "test", "--", "--nocapture"),
        'Bash("cargo test")',
        ("cargo fmt 2>/dev/null", "cargo clippy --fix --allow-dirty 2>/dev/null"),
        ("cargo", "check"),
        "cargo add",
        "rust",
        ("target",),
        ("Cargo.toml", "Cargo.lock"),
        "Language: Rust. Use #[test] + cargo test. clippy lints. clap for CLI.",
        ("cargo", "tarpaulin", "--out", "stdout", "--skip-clean"),
        ("cargo", "audit"),
        ("Actix", "Axum", "Rocket"),
        lint_check=("cargo fmt --check 2>&1", "cargo clippy 2>&1"),
        required_files=("Cargo.toml",),
        tool_checks=("cargo --version",),
        manifest_vals=("cargo verify-project",),
        test_disc="cargo test -- --list 2>/dev/null | tail -1",
    ),
    "java": _p(
        "java",
        (".java",),
        ("mvn", "test", "-q"),
        'Bash("mvn test -q")',
        ("mvn spotless:apply 2>/dev/null",),
        ("mvn", "compile", "-q"),
        "mvn dependency:resolve",
        "java",
        ("target", "build", ".gradle"),
        ("pom.xml", "build.gradle", "build.gradle.kts"),
        "Language: Java. JUnit 5 for testing. Google Java Style. Maven/Gradle.",
        ("mvn", "jacoco:report", "-q"),
        ("mvn", "spotbugs:check", "-q"),
        ("Spring Boot", "Quarkus"),
        lint_check=("mvn spotless:check -q 2>&1",),
        required_files=("pom.xml",),
        tool_checks=("mvn --version",),
    ),
    "ruby": _p(
        "ruby",
        (".rb",),
        ("bundle", "exec", "rspec", "--format", "documentation"),
        'Bash("bundle exec rspec")',
        ("bundle exec rubocop -A 2>/dev/null",),
        None,
        "gem install",
        "ruby",
        (".bundle", "vendor"),
        ("Gemfile", "Gemfile.lock"),
        "Language: Ruby. RSpec for testing. RuboCop style.",
        ("bundle", "exec", "rspec", "--format", "progress"),
        ("bundle", "exec", "brakeman", "-q", "--no-pager"),
        ("Rails", "Sinatra"),
        lint_check=("bundle exec rubocop --format=emacs 2>&1",),
        required_files=("Gemfile",),
        tool_checks=("bundle --version",),
        test_disc="bundle exec rspec --dry-run 2>/dev/null | tail -1",
    ),
    "elixir": _p(
        "elixir",
        (".ex", ".exs"),
        ("mix", "test", "--trace"),
        'Bash("mix test")',
        ("mix format 2>/dev/null", "mix credo --strict 2>/dev/null"),
        ("mix", "compile", "--warnings-as-errors"),
        "mix deps.get",
        "elixir",
        ("_build", "deps"),
        ("mix.exs", "mix.lock"),
        "Language: Elixir. ExUnit for testing. mix format.",
        ("mix", "test", "--cover"),
        ("mix", "sobelow", "--format", "json"),
        ("Phoenix",),
        lint_check=("mix format --check-formatted 2>&1", "mix credo --strict --format=oneline 2>&1"),
        required_files=("mix.exs",),
        tool_checks=("mix --version",),
    ),
    "cpp": _p(
        "cpp",
        (".cpp", ".cc", ".cxx", ".h", ".hpp"),
        ("cmake", "--build", "build", "--target", "test"),
        'Bash("cmake --build build --target test")',
        ("clang-format -i **/*.cpp **/*.h 2>/dev/null",),
        ("cmake", "--build", "build"),
        "conan install",
        "cpp",
        ("build", "cmake-build-debug"),
        ("CMakeLists.txt", "conanfile.txt"),
        "Language: C++. Google Test/Catch2. clang-format. CMake.",
        ("cmake", "--build", "build", "--target", "coverage"),
        ("cppcheck", "--enable=all", "--error-exitcode=1", "."),
        lint_check=("clang-format --dry-run --Werror **/*.cpp **/*.h 2>&1",),
        required_files=("CMakeLists.txt",),
        tool_checks=("cmake --version",),
    ),
    "c": _p(
        "c",
        (".c", ".h"),
        ("ctest",),
        'Bash("ctest")',
        ("clang-format -i **/*.c **/*.h 2>/dev/null",),
        ("make",),
        "apt install",
        "c",
        ("build",),
        ("Makefile", "CMakeLists.txt"),
        "Language: C. Use CUnit or Check. CMake or Make build system.",
        None,
        ("cppcheck", "--enable=all", "--error-exitcode=1", "."),
        lint_check=("clang-format --dry-run --Werror **/*.c **/*.h 2>&1",),
        required_files=("Makefile",),
        tool_checks=("make --version",),
    ),
    "php": _p(
        "php",
        (".php",),
        ("vendor/bin/phpunit",),
        'Bash("vendor/bin/phpunit")',
        ("vendor/bin/php-cs-fixer fix . 2>/dev/null",),
        ("php", "-l"),
        "composer require",
        "php",
        ("vendor",),
        ("composer.json", "composer.lock"),
        "Language: PHP 8.3+. PHPUnit for testing. PHP-CS-Fixer.",
        ("vendor/bin/phpunit", "--coverage-text"),
        None,  # phpstan is a type checker, not a security scanner
        ("Laravel", "Symfony"),
        lint_check=("vendor/bin/php-cs-fixer fix --dry-run --diff 2>&1",),
        required_files=("composer.json",),
        tool_checks=("php --version",),
        manifest_vals=("php -r \"json_decode(file_get_contents('composer.json'));\"",),
        test_disc="vendor/bin/phpunit --list-tests 2>/dev/null | wc -l",
    ),
    "kotlin": _p(
        "kotlin",
        (".kt", ".kts"),
        ("gradle", "test"),
        'Bash("gradle test")',
        ("gradle", "ktlintFormat 2>/dev/null"),
        ("gradle", "compileKotlin"),
        "gradle dependencies",
        "kotlin",
        ("build", ".gradle"),
        ("build.gradle.kts", "build.gradle"),
        "Language: Kotlin 2.0+. JUnit 5. Gradle. ktlint.",
        ("gradle", "test", "jacocoTestReport"),
        ("gradle", "detekt"),
        ("Ktor", "Spring Boot"),
        lint_check=("gradle ktlintCheck 2>&1",),
        required_files=("build.gradle.kts",),
        tool_checks=("gradle --version",),
    ),
    "swift": _p(
        "swift",
        (".swift",),
        ("swift", "test"),
        'Bash("swift test")',
        ("swift-format", "format", "-i", "-r", ". 2>/dev/null"),
        ("swift", "build"),
        "swift package resolve",
        "swift",
        (".build",),
        ("Package.swift",),
        "Language: Swift 6+. XCTest. SwiftPM.",
        ("swift", "test", "--enable-code-coverage"),
        None,
        ("SwiftUI", "Vapor"),
        lint_check=("swift-format lint -r . 2>&1",),
        required_files=("Package.swift",),
        tool_checks=("swift --version",),
    ),
    "dart": _p(
        "dart",
        (".dart",),
        ("dart", "test"),
        'Bash("dart test")',
        ("dart", "fix", "--apply 2>/dev/null"),
        ("dart", "analyze"),
        "dart pub add",
        "dart",
        (".dart_tool", "build"),
        ("pubspec.yaml", "pubspec.lock"),
        "Language: Dart 3.5+. Use dart test. dart fix for formatting.",
        ("dart", "test", "--coverage"),
        None,
        ("Flutter",),
        lint_check=("dart analyze 2>&1",),
        required_files=("pubspec.yaml",),
        tool_checks=("dart --version",),
    ),
    "scala": _p(
        "scala",
        (".scala", ".sc"),
        ("sbt", "test"),
        'Bash("sbt test")',
        ("sbt", "scalafmtAll 2>/dev/null"),
        ("sbt", "compile"),
        "sbt update",
        "scala",
        ("target", "project/target"),
        ("build.sbt",),
        "Language: Scala 3. ScalaTest. sbt. scalafmt.",
        ("sbt", "coverage", "test", "coverageReport"),
        None,
        ("Akka", "Cats Effect", "ZIO"),
        lint_check=("sbt scalafmtCheckAll 2>&1",),
        required_files=("build.sbt",),
        tool_checks=("sbt --version",),
    ),
    "haskell": _p(
        "haskell",
        (".hs",),
        ("cabal", "test"),
        'Bash("cabal test")',
        ("ormolu", "-i **/*.hs 2>/dev/null"),
        ("cabal", "build"),
        "cabal install",
        "haskell",
        ("dist-newstyle",),
        ("cabal.project", "package.yaml", "stack.yaml"),
        "Language: Haskell. HSpec or tasty. cabal.",
        ("cabal", "test", "--enable-coverage"),
        None,
        lint_check=("ormolu --check **/*.hs 2>&1",),
        required_files=("package.yaml",),
        tool_checks=("cabal --version",),
    ),
    "zig": _p(
        "zig",
        (".zig",),
        ("zig", "test"),
        'Bash("zig test")',
        (),
        ("zig", "build"),
        "",
        "zig",
        ("zig-cache", "zig-out"),
        ("build.zig",),
        "Language: Zig. Use std.testing. zig build.",
        None,
        None,
        required_files=("build.zig",),
        tool_checks=("zig version",),
    ),
    "r": _p(
        "r",
        (".R", ".r", ".Rmd"),
        ("Rscript", "-e", "testthat::test_dir('tests')"),
        "Bash(\"Rscript -e testthat::test_dir('tests')\")",
        ("Rscript", "-e", "styler::style_dir() 2>/dev/null"),
        None,
        "Rscript -e install.packages",
        "r",
        ("renv",),
        ("DESCRIPTION", "NAMESPACE", "renv.lock"),
        "Language: R. testthat for testing. styler for formatting.",
        ("Rscript", "-e", "covr::package_coverage()"),
        None,
        ("Shiny",),
        required_files=("DESCRIPTION",),
        tool_checks=("Rscript --version",),
    ),
    "csharp": _p(
        "csharp",
        (".cs",),
        ("dotnet", "test"),
        'Bash("dotnet test")',
        ("dotnet", "format 2>/dev/null"),
        ("dotnet", "build"),
        "dotnet add package",
        "csharp",
        ("bin", "obj"),
        ("*.csproj", "*.sln"),
        "Language: C# 12. xUnit/NUnit. dotnet CLI.",
        ("dotnet", "test", "--collect:XPlat Code Coverage"),
        ("dotnet", "tool", "run", "security-scan"),
        ("ASP.NET Core", "Blazor"),
        lint_check=("dotnet format --verify-no-changes 2>&1",),
        tool_checks=("dotnet --version",),
    ),
    "lua": _p(
        "lua",
        (".lua",),
        ("busted",),
        'Bash("busted")',
        (),
        ("luac", "-p"),
        "luarocks install",
        "lua",
        (),
        ("*.rockspec",),
        "Language: Lua. busted for testing.",
        ("busted", "--coverage"),
        None,
        tool_checks=("lua -v",),
    ),
    "html": _p(
        "html",
        (".html", ".htm"),
        (),
        'Bash("echo no tests")',
        ("npx prettier --write **/*.html 2>/dev/null",),
        None,
        "",
        "html",
        (),
        ("index.html",),
        "Language: HTML. Use semantic HTML5. Accessible markup.",
        None,
        None,
    ),
    "vue": _p(
        "vue",
        (".vue",),
        ("npx", "vitest", "run"),
        'Bash("npx vitest run")',
        ("npx eslint --fix . 2>/dev/null", "npx prettier --write . 2>/dev/null"),
        ("npx", "vue-tsc", "--noEmit"),
        "npm install",
        "vue",
        ("node_modules", "dist"),
        ("package.json", "vite.config.ts"),
        "Language: Vue 3.5. Vitest. ESLint + Prettier.",
        ("npx", "vitest", "--coverage"),
        None,
        ("Nuxt",),
        lint_check=("npx eslint --format=unix .",),
        required_files=("package.json",),
        tool_checks=("npx --version",),
        manifest_vals=("node -e \"JSON.parse(require('fs').readFileSync('package.json','utf8'))\"",),
        test_disc="npx vitest --list 2>/dev/null | wc -l",
    ),
    "svelte": _p(
        "svelte",
        (".svelte",),
        ("npx", "vitest", "run"),
        'Bash("npx vitest run")',
        ("npx eslint --fix . 2>/dev/null", "npx prettier --write . 2>/dev/null"),
        ("npx", "svelte-check"),
        "npm install",
        "svelte",
        ("node_modules", ".svelte-kit"),
        ("package.json", "svelte.config.js"),
        "Language: Svelte. Vitest. SvelteKit.",
        ("npx", "vitest", "--coverage"),
        None,
        ("SvelteKit",),
        lint_check=("npx eslint --format=unix .",),
        required_files=("package.json",),
        tool_checks=("npx --version",),
        manifest_vals=("node -e \"JSON.parse(require('fs').readFileSync('package.json','utf8'))\"",),
        test_disc="npx vitest --list 2>/dev/null | wc -l",
    ),
}

# Framework detection: manifest file patterns -> framework name
_FRAMEWORK_MARKERS: dict[str, str] = {
    "next.config.js": "Next.js",
    "next.config.mjs": "Next.js",
    "next.config.ts": "Next.js",
    "nuxt.config.ts": "Nuxt",
    "angular.json": "Angular",
    "svelte.config.js": "SvelteKit",
    "astro.config.mjs": "Astro",
    "remix.config.js": "Remix",
    "vite.config.ts": "Vite",
    "manage.py": "Django",
}

_MANIFEST_TO_LANG: dict[str, str] = {
    "nimble": "nim",
    "rebar.config": "erlang",
    "*.lpi": "pascal",
    "Makefile.PL": "perl",
    "cpanfile": "perl",
    "dune-project": "ocaml",
    "project.clj": "clojure",
    "deps.edn": "clojure",
    "JuliaProject.toml": "julia",
    "shard.yml": "crystal",
    "dub.json": "d",
    "dub.sdl": "d",
    "v.mod": "v",
    "gleam.toml": "gleam",
}

_EXTENSION_MAP: dict[str, tuple[str, ...]] = {
    "nim": (".nim",),
    "erlang": (".erl", ".hrl"),
    "pascal": (".pas", ".pp"),
    "perl": (".pl", ".pm"),
    "ocaml": (".ml", ".mli"),
    "clojure": (".clj", ".cljs", ".cljc"),
    "groovy": (".groovy", ".gvy"),
    "fortran": (".f90", ".f95", ".f03"),
    "julia": (".jl",),
    "crystal": (".cr",),
    "d": (".d",),
    "v": (".v",),
    "gleam": (".gleam",),
    "odin": (".odin",),
}

_EXT_TO_LANG: dict[str, str] = {}


def _build_ext_map() -> None:
    """Lazily populate extension->language map from PROFILES."""
    if _EXT_TO_LANG:
        return
    for lang, profile in PROFILES.items():
        for ext in profile.extensions:
            _EXT_TO_LANG.setdefault(ext, lang)


_SKIP_DIRS_DETECT = frozenset(
    {
        ".git",
        ".trust5",
        "node_modules",
        "vendor",
        "__pycache__",
        ".venv",
        "venv",
        "target",
        "dist",
        "build",
        ".tox",
        ".mypy_cache",
    }
)
