"""Object serializing for Python scripts namespaces."""

from __future__ import annotations

import re
import typing as t

from project_config.compat import TypeAlias


Namespace: TypeAlias = t.Dict[str, t.Any]


def loads(string: str, namespace: Namespace = {}) -> Namespace:
    """Execute a Python file and exposes their namespace as a dictionary.

    The logic is based in Sphinx's configuration file loader:
    https://github.com/sphinx-doc/sphinx/blob/4d7558e9/sphinx/config.py#L332

    Args:
        string (str): Python script.
        namespace (dict): Namespace to update.

    Returns:
        dict: Global namespace of the Python script as an object.
    """
    code = compile(string, "utf-8", "exec")
    exec(code, namespace)
    del namespace["__builtins__"]  # we don't care about builtins
    return namespace


def _pyobject_to_string(value: t.Any) -> str:
    if isinstance(value, str):
        escaped_value = re.sub(r"([\"])", r"\\\1", value)
        return f'"{escaped_value}"'
    if isinstance(value, bool):
        return "True" if value else "False"
    elif isinstance(value, type):
        value = value.__name__
    elif isinstance(value, dict):
        if len(str(value)) > 60:
            newline = "\n    "
            delimiter = "\n"
        else:
            newline = " "
            delimiter = ""
        result = (
            "{"
            + (newline if newline != " " else "")
            + f",{newline}".join(
                f'"{key}": {str(_pyobject_to_string(item))}'
                for key, item in value.items()
            )
        )
        if newline != " ":
            result += ","

        result += delimiter + "}"
        return result
    elif isinstance(value, list):
        if len(str(value)) > 60:
            newline = "\n    "
            delimiter = "\n"
        else:
            newline = " "
            delimiter = ""
        result = (
            "["
            + (newline if newline != " " else "")
            + f",{newline}".join(
                str(_pyobject_to_string(item)) for item in value
            )
        )
        if newline != " ":
            result += ","

        result += delimiter + "]"
        return result
    return t.cast(str, value)


def dumps(obj: t.Any) -> str:
    """Converts a namespace to a Python script.

    Args:
        obj (dict): Namespace to convert.

    Returns:
        str: Python script.
    """
    result = ""
    for key, value in obj.items():
        result += f"{key} = {_pyobject_to_string(value)}\n"

    return result
