#     Copyright 2026, Kay Hayen, mailto:kay.hayen@gmail.com find license text at end of file


"""This module abstracts what site.py is normally doing in .pth files.

This tries to extract "namespaces" packages that were manually created and
point to package directories, which need no "__init__.py" to count as a
package. Nuitka will pretend for those that there be one, but without content.
"""

import os
import sys

from nuitka.Tracing import recursion_logger
from nuitka.utils.FileOperations import getFileContentByLine, listDir
from nuitka.utils.ModuleNames import ModuleName


def getLoadedPackages():
    """Extract packages with no __file__, i.e. they got added manually.

    They are frequently created with "*.pth" files that then check for the
    "__init__.py" to exist, and when it doesn't, then they create during the
    loading of "site.py" an package with "__path__" set.
    """

    for module_name, module in sys.modules.items():
        if not getattr(module, "__path__", None):
            continue

        if hasattr(module, "__file__"):
            continue

        yield module_name, module


def detectPreLoadedPackagePaths():
    result = {}

    for package_name, module in getLoadedPackages():
        paths = tuple(p for p in module.__path__ if os.path.exists(p))
        if paths:
            result[package_name] = paths

    return result


preloaded_packages = None


def getPreloadedPackagePaths():
    """Return dictionary with preloaded package paths from .pth files"""

    # We need to set this from the outside, pylint: disable=global-statement
    global preloaded_packages

    if preloaded_packages is None:
        preloaded_packages = detectPreLoadedPackagePaths()

    return preloaded_packages


def setPreloadedPackagePaths(value):
    # We need to set this from the outside, pylint: disable=global-statement
    global preloaded_packages

    preloaded_packages = value


def getPreloadedPackagePath(package_name):
    return getPreloadedPackagePaths().get(package_name)


def isPreloadedPackagePath(path):
    path = os.path.normcase(path)

    for paths in getPreloadedPackagePaths().values():
        for element in paths:
            if os.path.normcase(element) == path:
                return True

    return False


def _considerPthImportedPackage(module_name):
    if module_name in ("os", "sys", "nuitka_strings"):
        return None

    if module_name.startswith("__editable__"):
        finder_module = __import__(module_name)

        paths = set()

        mapping = getattr(finder_module, "MAPPING", {})
        for package_name, path in mapping.items():
            if os.path.basename(path) != package_name:
                continue

            paths.add(os.path.dirname(path))

        sys.path.extend(sorted(paths))

        return None

    return module_name


def detectPthImportedPackages():
    if not hasattr(sys.modules["site"], "getsitepackages"):
        return ()

    pth_imports = set()

    for prefix in sys.modules["site"].getsitepackages():
        if not os.path.isdir(prefix):
            continue

        for path, filename in listDir(prefix):
            if filename.endswith(".pth"):
                try:
                    for line in getFileContentByLine(path, "rU"):
                        if line.startswith("import "):
                            if ";" in line:
                                line = line[: line.find(";")]

                            for part in line[7:].split(","):
                                pth_import = _considerPthImportedPackage(part.strip())

                                if pth_import is not None:
                                    pth_imports.add(pth_import)
                except OSError:
                    recursion_logger.warning(
                        "Python installation problem, cannot read file '%s'."
                    )

    return tuple(sorted(pth_imports))


pth_imported_packages = ()


def setPthImportedPackages(value):
    # We need to set this from the outside, pylint: disable=global-statement
    global pth_imported_packages

    pth_imported_packages = tuple(ModuleName(module_name) for module_name in value)


def getPthImportedPackages():
    return pth_imported_packages


#     Part of "Nuitka", an optimizing Python compiler that is compatible and
#     integrates with CPython, but also works on its own.
#
#     Licensed under the GNU Affero General Public License, Version 3 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#        https://www.gnu.org/licenses/agpl-3.0.txt
#
#     See also: "Nuitka Runtime Library Exception, Version 1.0" in file
#     "LICENSE-RUNTIME.txt" for additional permissions granted under Section 7.
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.
