# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# src/pdftl/registry_init.py

"""
Single entry point for initializing the application registry.
This function populates registry options and discovers all operations.
"""

import logging
import os
import pathlib
import sys
from typing import Any, cast

import pdftl

logger = logging.getLogger(__name__)


def _get_config_base():
    if os.name == "nt":
        return os.environ.get("APPDATA") or os.path.expanduser("~\\AppData\\Roaming")
    return os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config")


def _discover_external_operations():
    """Discover user-provided operations in the config directory."""
    import importlib.util

    base = _get_config_base()

    op_dir = pathlib.Path(os.path.join(base, "pdftl", "operations"))

    if not op_dir.exists():
        return

    # Add op_dir to sys.path so the modules can be imported by name
    # and can find each other if they have local imports.
    op_path_str = str(op_dir)
    if op_path_str not in sys.path:
        sys.path.insert(0, op_path_str)  # Priority over built-ins if you want shadowing

    for py_file in op_dir.glob("*.py"):
        if py_file.stem == "__init__":
            continue

        module_name = f"pdftl.external.{py_file.stem}"
        try:
            spec = importlib.util.spec_from_file_location(module_name, py_file)
            if spec is None or spec.loader is None:
                continue
            module = importlib.util.module_from_spec(spec)
            sys.modules[module_name] = module
            spec.loader.exec_module(module)

            logger.debug("Loaded external operation: %s", module_name)
        except ImportError as e:
            logger.error("Could not import external operation '%s': %s", module_name, e)
        except SyntaxError as e:
            logger.error(
                "Syntax error in external operation '%s' at line %s: %s",
                module_name,
                e.lineno,
                e.msg,
            )
        except Exception:
            # We catch bare Exception here to prevent a buggy user plugin
            # from stopping pdftl from starting entirely.
            logger.exception("Unexpected error loading external operation '%s'", module_name)


def _discover_modules(parent_modules, label):
    """
    Import submodules.

    This ensures that decorators are executed,
    so the global registry is fully populated before use.
    """
    import importlib
    import pkgutil

    loaded_modules = []
    for pkg in parent_modules:
        # iter_modules requires the path to be iterable (a list)
        path = getattr(pkg, "__path__", None)
        if path is None:
            logger.warning("Skipping discovery for %s (no __path__)", pkg.__name__)
            continue

        for _, module_name, _ in pkgutil.iter_modules(path):
            if module_name.startswith("_"):
                logger.debug("Skipping private module: %s", module_name)
                continue
            if not module_name.isidentifier():
                logger.error("Skipping invalid module name: %s", module_name)
                continue

            fq_name = f"{pkg.__name__}.{module_name}"
            if not fq_name.startswith("pdftl."):
                logger.error(
                    "Security violation: Attempted to load module outside pdftl: %s", fq_name
                )
                continue

            # Override semgrep here: Input is strictly validated to start with 'pdftl.' namespace
            # and must be a valid python identifier.

            # nosemgrep: python.lang.security.audit.dynamic-import
            importlib.import_module(fq_name)

            loaded_modules.append(fq_name)

    logger.debug("[registry_init] Loaded %s %s modules:", len(loaded_modules), label)
    for module in loaded_modules:
        logger.debug("  - %s", module)

    return loaded_modules


def initialize_registry():
    """
    Initialize the entire application registry.

    This function is idempotent (safe to call multiple times).
    It populates static options and discovers all operations.
    """
    import importlib

    if getattr(initialize_registry, "initialized", False):
        return

    # 1. Import the packages to be discovered
    # We must explicitly import 'utils' and 'cli' so their submodules (like
    # page_specs.py and pipeline.py) can be discovered and their decorators executed.
    for module in ["operations", "core", "output", "cli", "utils", "utils.images"]:
        importlib.import_module(f"pdftl.{module}")  # nosemgrep

    # 2. Discover and register all operations and help topics
    # We scan all relevant packages to find @register_operation and @register_help_topic
    _discover_modules(
        [pdftl.operations, pdftl.core, pdftl.cli, pdftl.utils, pdftl.utils.images], "operation"
    )
    _discover_modules([pdftl.output], "option")

    # 3. Discover external operations last (change order? depending on shadowing preference)
    _discover_external_operations()

    cast(Any, initialize_registry).initialized = True
