#!/usr/bin/env python3
#
# Copyright 2025 Thomas Mühlbacher <tmuehlbacher@posteo.net>
#
# SPDX-License-Identifier: LGPL-2.1-or-later


import argparse
import json
import logging
import os
import subprocess
import sys

from typing import Iterator, List


def parse_version(ver):
    return tuple(map(int, ver.split(".")))


# see https://github.com/mesonbuild/meson/pull/14890
def old_meson_missing_vapi_deps_tag() -> bool:
    version_str = subprocess.check_output(
        ["meson", "--version"],
        text=True,
    )
    return parse_version(version_str.strip()) <= parse_version("1.9.0")


def objects_with_tag(obj) -> Iterator[dict]:
    match obj:
        case dict(d):
            if "tag" in d:
                yield d
            for v in d.values():
                yield from objects_with_tag(v)
        case list(l):
            for i in l:
                yield from objects_with_tag(i)


def collect_tags(install_plan) -> List[str]:
    tags = set()

    for obj in objects_with_tag(install_plan):
        tags.add(obj["tag"])

    return sorted(["null" if t is None else t for t in tags])


def collect_files(install_plan, tag) -> List[str]:
    files = list()

    if tag == "null":
        tag = None

    for obj in objects_with_tag(install_plan):
        if obj["tag"] == tag:
            files.append(obj["destination"])

    return sorted(files)


def print_tags(install_plan):
    for tag in collect_tags(install_plan):
        print(tag)


def print_files(install_plan, tag):
    for file in collect_files(install_plan, tag):
        print(file)


def print_all(install_plan):
    for tag in collect_tags(install_plan):
        for file in collect_files(install_plan, tag):
            print(f"{tag}: {file}")


def check(install_plan) -> int:
    exit_code = 0
    tags = collect_tags(install_plan)

    # check for files missing install tag
    ignore_vapi_deps = old_meson_missing_vapi_deps_tag()
    for null_file in collect_files(install_plan, None):
        if ignore_vapi_deps and null_file.endswith("fwupd.deps"):
            logging.warning(f"missing meson install tag: {null_file}")
        else:
            logging.error(f"missing meson install tag: {null_file}")
            exit_code = 1

    # check for incorrect test file tags
    for tag in [t for t in tags if t != "tests"]:
        files = collect_files(install_plan, tag)
        files = [f for f in files if "installed-tests" in f]
        for f in files:
            logging.error(f"file should have 'tests' tag: {f}")
            exit_code = 1

    if exit_code != 0:
        logging.warning(
            "meson build directory may be outdated which requires full wipe and new `meson setup`"
        )
    return exit_code


def main() -> int:
    parser = argparse.ArgumentParser(description="Meson install tag helper")
    parser.add_argument(
        "-C", dest="working_directory", default=".", help="Meson build directory"
    )
    parser.add_argument(
        "command",
        choices=[
            "check",
            "list",
            "list-files",
            "list-tags",
        ],
    )
    parser.add_argument("-t", "--tag", help="Optional tag for list-files command")

    args = parser.parse_args()

    install_plan = json.loads(
        subprocess.check_output(
            ["meson", "introspect", "--install-plan"],
            cwd=args.working_directory,
            text=True,
        )
    )

    exit_code = 0
    match args.command:
        case "check":
            exit_code = check(install_plan)
        case "list":
            print_all(install_plan)
        case "list-files":
            print_files(install_plan, args.tag)
        case "list-tags":
            print_tags(install_plan)

    return exit_code


if __name__ == "__main__":
    logging.basicConfig(
        level=logging.INFO, format="%(levelname)s: %(message)s", stream=sys.stderr
    )

    if "PRE_COMMIT" not in os.environ:
        sys.exit(main())

    if not os.path.exists("venv/build"):
        logging.info("no configured build directory, skipping check-meson-install-tag")
        sys.exit(0)

    install_plan = json.loads(
        subprocess.check_output(
            ["meson", "introspect", "--install-plan"],
            cwd="venv/build",
            text=True,
        )
    )

    if len(collect_files(install_plan, None)) > 10:
        logging.info("build dir is likely outdated, skipping check-meson-install-tag")
        sys.exit(0)

    check(install_plan)
    sys.exit(0)
