#-----------------------------------------------------------------------------
# Copyright (c) 2021-2023, PyInstaller Development Team.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
#
# The full license is in the file COPYING.txt, distributed with this software.
#
# SPDX-License-Identifier: Apache-2.0
#-----------------------------------------------------------------------------
#
# The run-time hook provides a custom module iteration function for our PyiFrozenFinder, which allows
# `pkgutil.iter_modules()` to return entries for modules that are embedded in the PYZ archive. The non-embedded modules
# (binary extensions, modules collected as only source .py files, etc.) are enumerated using the `fallback_finder`
# provided by `PyiFrozenFinder` (which typically would be the python's `FileFinder`).
def _pyi_rthook():
    import pkgutil

    import pyimod02_importers  # PyInstaller's bootstrap module

    # This could, in fact, be implemented as `iter_modules()` method of the `PyiFrozenFinder`. However, we want to
    # avoid importing `pkgutil` in that bootstrap module (i.e., for the `pkgutil.iter_importer_modules()` call on the
    # fallback finder).
    def _iter_pyi_frozen_finder_modules(finder, prefix=''):
        # Fetch PYZ TOC tree from pyimod02_importers
        pyz_toc_tree = pyimod02_importers.get_pyz_toc_tree()

        # Finder has already pre-computed the package prefix implied by the search path. Use it to find the starting
        # node in the prefix tree.
        if finder._pyz_entry_prefix:
            pkg_name_parts = finder._pyz_entry_prefix.split('.')
        else:
            pkg_name_parts = []

        tree_node = pyz_toc_tree
        for pkg_name_part in pkg_name_parts:
            tree_node = tree_node.get(pkg_name_part)
            if not isinstance(tree_node, dict):
                # This check handles two cases:
                #  a) path does not exist (`tree_node` is `None`)
                #  b) path corresponds to a module instead of a package (`tree_node` is a leaf node (`str`)).
                tree_node = {}
                break

        # Dump the contents of the tree node.
        for entry_name, entry_data in tree_node.items():
            is_pkg = isinstance(entry_data, dict)
            yield prefix + entry_name, is_pkg

        # If our finder has a fall-back finder available, iterate its modules as well. By using the public
        # `fallback_finder` attribute, we force creation of the fallback finder as necessary.
        # NOTE: we do not care about potential duplicates here, because `pkgutil.iter_modules()` itself
        # keeps track of yielded names for purposes of de-duplication.
        if finder.fallback_finder is not None:
            yield from pkgutil.iter_importer_modules(finder.fallback_finder, prefix)

    pkgutil.iter_importer_modules.register(
        pyimod02_importers.PyiFrozenFinder,
        _iter_pyi_frozen_finder_modules,
    )


_pyi_rthook()
del _pyi_rthook
