"""Split per-platform install trees into vendor/common/ + vendor/platform/<tag>/.

Called by vendor_chattolib.sh. Each distribution is classified via its
dist-info/WHEEL marker: ``Root-Is-Purelib: true`` means the wheel contains no
compiled code and can be shared across platforms; everything else carries
platform-specific binaries and goes into the per-platform directory.

Files are moved individually (as listed in each dist-info/RECORD) rather than by
top-level directory, so namespace packages shared by several distributions —
``google/`` today — end up in the right place.

Usage: vendor_split.py <build_dir> <vendor_dir> <platform_tag>...
"""

from __future__ import annotations

import csv
import filecmp
import shutil
import sys
from pathlib import Path


def _is_pure(dist_info: Path) -> bool:
    wheel = dist_info / "WHEEL"
    if not wheel.is_file():
        # No marker: assume platform-specific, which is the safe direction —
        # the file gets duplicated per platform instead of being shared wrongly.
        return False
    for line in wheel.read_text(encoding="utf-8").splitlines():
        if line.lower().startswith("root-is-purelib:"):
            return line.split(":", 1)[1].strip().lower() == "true"
    return False


def _record_files(dist_info: Path) -> list[str]:
    record = dist_info / "RECORD"
    if not record.is_file():
        return []
    with record.open(encoding="utf-8", newline="") as handle:
        return [row[0] for row in csv.reader(handle) if row and row[0]]


def classify(tree: Path) -> tuple[set[str], set[str]]:
    """Return (pure_files, binary_files) as paths relative to ``tree``."""
    pure: set[str] = set()
    binary: set[str] = set()
    for dist_info in sorted(tree.glob("*.dist-info")):
        target = pure if _is_pure(dist_info) else binary
        target.update(_record_files(dist_info))
        target.add(f"{dist_info.name}/")
    return pure, binary


def copy_files(tree: Path, names: set[str], dest: Path) -> int:
    copied = 0
    for name in sorted(names):
        src = tree / name
        if name.endswith("/"):
            if src.is_dir():
                shutil.copytree(src, dest / name, dirs_exist_ok=True)
                copied += 1
            continue
        if not src.is_file():
            continue  # RECORD lists entries that --target does not create
        out = dest / name
        out.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(src, out)
        copied += 1
    return copied


def main(argv: list[str]) -> int:
    if len(argv) < 4:
        print(__doc__, file=sys.stderr)
        return 2

    build_dir, vendor_dir, tags = Path(argv[1]), Path(argv[2]), argv[3:]
    common_dir = vendor_dir / "common"
    reference: dict[str, Path] = {}

    for tag in tags:
        tree = build_dir / tag
        if not tree.is_dir():
            print(f"❌ Build-Verzeichnis fehlt: {tree}", file=sys.stderr)
            return 1

        pure, binary = classify(tree)
        overlap = {p for p in pure & binary if not p.endswith("/")}
        if overlap:
            print(
                f"❌ {tag}: Dateien gehören zugleich zu einem puren und einem "
                f"binären Paket: {sorted(overlap)[:5]}",
                file=sys.stderr,
            )
            return 1

        n_bin = copy_files(tree, binary, vendor_dir / "platform" / tag)
        print(f"   {tag}: {n_bin} plattformspezifische Dateien")

        if not reference:
            n_pure = copy_files(tree, pure, common_dir)
            reference = {"tag": tag, "files": pure}  # type: ignore[dict-item]
            print(f"   common: {n_pure} plattformunabhängige Dateien (aus {tag})")
        else:
            # The shared part must really be identical, otherwise common/ would
            # silently carry one platform's variant for all of them.
            ref_files: set[str] = reference["files"]  # type: ignore[assignment]
            if pure != ref_files:
                only_here = sorted(pure - ref_files)[:5]
                only_ref = sorted(ref_files - pure)[:5]
                print(
                    f"⚠️  {tag}: pure Paketliste weicht von {reference['tag']} ab "
                    f"(nur hier: {only_here}, nur dort: {only_ref})",
                    file=sys.stderr,
                )
            mismatched = [
                name
                for name in sorted(pure & ref_files)
                if not name.endswith("/")
                and (tree / name).is_file()
                and (common_dir / name).is_file()
                and not filecmp.cmp(tree / name, common_dir / name, shallow=False)
            ]
            if mismatched:
                print(
                    f"⚠️  {tag}: {len(mismatched)} gemeinsame Datei(en) unterscheiden "
                    f"sich inhaltlich, z.B. {mismatched[:3]}",
                    file=sys.stderr,
                )

    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv))
