"""Parse user configuration."""

# =======================
# Python internal package
# =======================
# J
import json

# P
import pathlib

# R
import re

# T
import tomllib

# ================
# External package
# ================
# T
import typing

# Y
import yaml

# ==============
# Module package
# ==============
# U
from ..utils.data import (
    create_drawning_configuration,
    InteractionConfiguration,
    create_miscellaneous_configuration,
)
from ..utils.messenger import (
    Error,
)
from ..utils.van_der_waals_radius import VanDerWaalsRadius


class Configuration(dict):
    """Configuration manager for parsing and organizing application settings.

    This class dynamically loads and validates configuration data from
    supported file formats. The `Configuration` class extends `dict` to allow
    dictionary-style access to configuration categories.
    """

    REGISTRY_EXTENSION: dict[str, typing.Callable] = {
        ".json": json.load,
        ".toml": tomllib.load,
        ".yml": yaml.safe_load,
        ".yaml": yaml.safe_load,
    }

    def __init__(self, path: pathlib.Path | None = None) -> None:
        """Initialize a configuration object.

        Parameters
        ----------
        path : `pathlib.Path` | `None`
            The path to the configuration file. If `None`, only default
            configuration values are used.
        """
        if path is not None:
            self.parsed_configuration: dict[str, typing.Any] = (
                self.__read_configuration(path)
            )
        else:
            self.parsed_configuration = {}

        # Here, we need to add to `self` and not replace it.
        self.update(
            {
                "miscellaneous": create_miscellaneous_configuration(),
                "interaction_configuration": InteractionConfiguration,
                "visualization": create_drawning_configuration(),
                "vdw_radius": VanDerWaalsRadius,
            }
        )

        self.__configure()

    def __read_configuration(
        self, path: pathlib.Path
    ) -> dict[str, typing.Any]:
        """Read and parse a configuration file based on its extension.

        Parameters
        ----------
        path : `pathlib.Path`
            The path to the configuration file to be parsed.

        Returns
        -------
        `dict[str, typing.Any]`
            A dictionary representation of the parsed configuration file.
        """
        with open(path, "rb") as file:
            parsed_configuration: dict[str, typing.Any] = (
                self.REGISTRY_EXTENSION[path.suffix.lower()](file)
            )

            return parsed_configuration

    def __try_configure(self, category) -> None:
        """Attempt to instantiate a configuration category with parsed values.

        Parameters
        ----------
        category : `str`
            The configuration category to initialize.

        Raises
        ------
        `ValueError`
            - If a provided configuration key does not match the expected
              parameters of the category.
        """
        try:
            self[category] = self[category](
                **self.parsed_configuration[category]
            )
        except TypeError as error:
            regex: re.Pattern = re.compile(r"'(.+)'$")
            key: str = regex.search(str(error)).group(1)

            Error.WRONG_KEY(ValueError, category=category, key=key)

    def __configure(self) -> None:
        """Initialize and validate all configuration categories.

        Raises
        ------
        `ValueError`
            - If a configuration section contains invalid keys.
            - If unknown top-level categories are found in the configuration
              file.
        """
        for key, value in self.items():
            if key not in self.parsed_configuration:
                self[key] = value()
                continue

            self.__try_configure(key)

        bad_key: set[str] = set(self.parsed_configuration.keys()) - set(
            self.keys()
        )

        if len(bad_key) > 0:
            Error.BAD_MAIN_KEY(ValueError, key=", ".join(bad_key))
