#!/usr/bin/env python3
"""Read-only, bounded diagnostics for a Vite project."""
from __future__ import annotations

import argparse
import json
import os
import shutil
import subprocess
import sys
from pathlib import Path

LOCKFILES = {
    "package-lock.json": "npm",
    "pnpm-lock.yaml": "pnpm",
    "yarn.lock": "yarn",
    "bun.lock": "bun",
    "bun.lockb": "bun",
}
MANAGERS = {
    "npm": ["npm", "--version"],
    "pnpm": ["pnpm", "--version"],
    "yarn": ["yarn", "--version"],
    "bun": ["bun", "--version"],
}


def bounded_version(command: list[str], timeout: float) -> dict[str, object]:
    executable = shutil.which(command[0])
    result: dict[str, object] = {"available": executable is not None}
    if executable is None:
        return result
    try:
        completed = subprocess.run(
            command, capture_output=True, text=True, timeout=timeout, check=False
        )
    except subprocess.TimeoutExpired:
        result["error"] = "timeout"
        return result
    value = (completed.stdout or completed.stderr).strip().splitlines()
    if value:
        result["version"] = value[0][:200]
    result["exit_code"] = completed.returncode
    return result


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--project", default=".", help="Vite project directory")
    parser.add_argument("--timeout", type=float, default=3.0, help="Version probe timeout")
    parser.add_argument("--json", action="store_true", help="Emit JSON (default)")
    args = parser.parse_args()
    if args.timeout <= 0:
        parser.error("--timeout must be positive")
    project = Path(args.project).expanduser().resolve()
    if not project.is_dir():
        print(json.dumps({"error": f"project is not a directory: {project}"}), file=sys.stderr)
        return 1

    package_file = project / "package.json"
    package_data: dict[str, object] = {}
    errors: list[str] = []
    if package_file.is_file():
        try:
            loaded = json.loads(package_file.read_text(encoding="utf-8"))
            if isinstance(loaded, dict):
                package_data = loaded
            else:
                errors.append("package.json is not an object")
        except (OSError, json.JSONDecodeError) as exc:
            errors.append(f"cannot read package.json: {exc}")
    else:
        errors.append("package.json not found")

    lockfiles = [name for name in LOCKFILES if (project / name).is_file()]
    manager_name = LOCKFILES[lockfiles[0]] if lockfiles else None
    configs = sorted(
        path.name for path in project.iterdir()
        if path.is_file() and path.name.startswith("vite.config")
    )
    dependencies: dict[str, object] = {}
    for section in ("dependencies", "devDependencies", "optionalDependencies"):
        values = package_data.get(section)
        if isinstance(values, dict) and "vite" in values:
            dependencies[section] = values["vite"]

    payload = {
        "project": str(project),
        "package_json": package_file.is_file(),
        "package_manager": {
            "name": manager_name,
            "lockfiles": lockfiles,
            "version": bounded_version(MANAGERS[manager_name], args.timeout)
            if manager_name else None,
        },
        "node": bounded_version(["node", "--version"], args.timeout),
        "vite_dependency": dependencies,
        "vite_installed": (project / "node_modules" / "vite" / "package.json").is_file(),
        "config_files": configs,
        "env_file_names": sorted(
            path.name for path in project.iterdir()
            if path.is_file() and path.name.startswith(".env")
        ),
        "errors": errors,
    }
    print(json.dumps(payload, indent=2, sort_keys=True))
    return 1 if errors else 0


if __name__ == "__main__":
    raise SystemExit(main())
