"""The certified dependency-install contract for generated JavaScript apps."""

from __future__ import annotations

import json
from pathlib import Path

NODE_VERSION = "22"
PACKAGE_MANAGER = "npm"
LOCKFILE = "package-lock.json"
LOCKFILE_VERSION = 3


def dependency_contract_manifest() -> dict:
    return {
        "schema_version": 1,
        "authority": "P2HS Core generated-app contract",
        "package_manager": PACKAGE_MANAGER,
        "node_version": NODE_VERSION,
        "lockfile": {
            "path": LOCKFILE,
            "required_for_generated_projects": True,
            "expected_lockfile_version": LOCKFILE_VERSION,
            "install_command": "npm ci",
            "legacy_adoption": "certified_migration_metadata",
        },
    }


def validate_package_lock(repo_path: str | Path) -> dict:
    """Read-only validation used by tests and migration dry-runs."""
    root = Path(repo_path)
    package = root / "package.json"
    lock = root / LOCKFILE
    if not package.is_file():
        return {"status": "not_applicable", "reason": "package_json_missing"}
    if not lock.is_file():
        return {"status": "blocked", "reason": "package_lock_missing"}
    try:
        package_data = json.loads(package.read_text(encoding="utf-8"))
        lock_data = json.loads(lock.read_text(encoding="utf-8"))
    except (OSError, UnicodeDecodeError, json.JSONDecodeError):
        return {"status": "blocked", "reason": "package_manifest_invalid"}
    if not isinstance(package_data, dict) or not isinstance(lock_data, dict):
        return {"status": "blocked", "reason": "package_manifest_invalid"}
    if lock_data.get("lockfileVersion") != LOCKFILE_VERSION:
        return {"status": "blocked", "reason": "unsupported_lockfile_version"}
    root_packages = (lock_data.get("packages") or {}).get("")
    if not isinstance(root_packages, dict):
        return {"status": "blocked", "reason": "lockfile_root_missing"}
    declared = {
        **(package_data.get("dependencies") or {}),
        **(package_data.get("devDependencies") or {}),
        **(package_data.get("optionalDependencies") or {}),
    }
    locked = {
        **(root_packages.get("dependencies") or {}),
        **(root_packages.get("devDependencies") or {}),
        **(root_packages.get("optionalDependencies") or {}),
    }
    missing = sorted(set(declared) - set(locked))
    return {
        "status": "conformant" if not missing else "blocked",
        "reason": None if not missing else "package_lock_manifest_mismatch",
        "missing_dependencies": missing,
    }
