#!/usr/bin/env python3
"""
deptree-node - Dependency tree with sizes for a Node.js project.

Reads package-lock.json (v2/v3) for the dependency graph and measures
sizes from node_modules on disk.

Usage:
    python deptree-node.py [PROJECT_DIR] [OPTIONS]

Options:
    --no-dev        Exclude devDependencies
    --depth N       Max tree depth (default: unlimited)
    -h, --help      Show help
"""

import json
import sys
from pathlib import Path

from dep_orbits.utils import walk_size


def package_size(node_modules: Path, name: str) -> int:
    """Return on-disk size of a package from node_modules."""
    pkg_path = node_modules.joinpath(*name.split("/"))
    print(pkg_path)
    if pkg_path.exists():
        return walk_size([pkg_path])
    else:
        print("Not found")
    return 0


def parse_lockfile(project_dir: Path) -> dict:
    lock_path = project_dir / "package-lock.json"
    if not lock_path.exists():
        print(f"Error: no package-lock.json found in {project_dir}", file=sys.stderr)
        sys.exit(1)
    lock = json.loads(lock_path.read_text())
    version = lock.get("lockfileVersion", 1)
    if version < 2:
        print(
            f"Error: lockfileVersion {version} is not supported. "
            "Run `npm install` to upgrade to v2/v3.",
            file=sys.stderr,
        )
        sys.exit(1)
    return lock


def read_root_package(project_dir: Path) -> tuple[str, str]:
    pkg_path = project_dir / "package.json"
    if pkg_path.exists():
        pkg = json.loads(pkg_path.read_text())
        return (
            pkg.get("name", project_dir.name),
            pkg.get("version", "?"),
        )
    return (project_dir.name, "?")


def build_pkg_map(lock: dict) -> dict[str, dict]:
    """
    Build a flat map of package name → lock entry, preferring the
    top-level entry (shortest node_modules/ path) when there are
    multiple versions.
    """
    pkg_map: dict[str, dict] = {}
    for key, entry in (lock.get("packages") or {}).items():
        if not key:
            continue  # skip root entry ""
        # key is e.g. "node_modules/express" or "node_modules/a/node_modules/b"
        # extract the package name as the part after the last "node_modules/"
        name = key.split("node_modules/")[-1]
        depth = key.count("node_modules/")
        existing = pkg_map.get(name)
        if existing is None or depth < existing.get("_depth", 999):
            pkg_map[name] = {**entry, "_depth": depth}
    return pkg_map


def _resolve_tree(
    lock: dict,
    pkg_map: dict[str, dict],
    node_modules: Path,
    include_dev: bool,
) -> list[dict]:
    root_entry = (lock.get("packages") or {}).get("", {})
    root_deps: dict[str, str] = {
        **root_entry.get("dependencies", {}),
        **(root_entry.get("devDependencies", {}) if include_dev else {}),
    }

    visited: dict[str, dict] = dict()

    def resolve(name: str, parents: list[str]) -> dict | None:
        if name in parents:
            return None

        if name in visited:
            return visited[name]

        entry = pkg_map.get(name)
        node: dict = {
            "name": name,
            "version": entry["version"] if entry else "?",
            "sizeBytes": 0,
            "dependencies": [],
        }

        node["sizeBytes"] = package_size(node_modules, name)

        parents = parents + [name]
        if entry:
            for child_name in entry.get("dependencies") or {}:
                resolved = resolve(child_name, parents)
                if resolved is not None:
                    node["dependencies"].append(resolved)
                    node["sizeBytes"] += resolved["sizeBytes"]

        visited[name] = node
        return node

    return [resolve(name, []) for name in root_deps]


def build_tree(project_dir: Path, include_dev: bool) -> dict:
    node_modules = project_dir / "node_modules"
    if not node_modules.exists():
        print(
            "Warning: node_modules not found — all sizes will be 0. "
            "Run `npm install` first.",
            file=sys.stderr,
        )

    lock = parse_lockfile(project_dir)
    root_name, root_version = read_root_package(project_dir)
    pkg_map = build_pkg_map(lock)
    deps = _resolve_tree(lock, pkg_map, node_modules, include_dev)

    proj_size = sum((dep["sizeBytes"] for dep in deps))

    return {
        "name": root_name,
        "version": root_version,
        "sizeBytes": proj_size,
        "dependencies": deps,
    }
