#
# pyproject.toml
#

import dataclasses
import re
from typing import Dict, List, Optional, Union

_arch_re = re.compile(r"\{\{\s*ARCH\s*\}\}")
_os_re = re.compile(r"\{\{\s*OS\s*\}\}")


@dataclasses.dataclass
class TypeCasterHeader:

    #: Header file to include when one of the types are detected in a wrapper
    header: str

    #: Types to look for to indicate that this type caster header should be
    #: included.
    types: List[str]

    #: If a parameter type that requires this type caster requires a default
    #: argument, a C-style ``(type)`` cast is used on the default argument.
    #:
    #: The default cast can be disabled via param_override's ``disable_type_caster_default_cast``
    default_arg_cast: bool = False


@dataclasses.dataclass
class TypeCasterConfig:
    """
    Specifies type casters to be exported as a pkgconf package. When a semiwrap package has a
    dependency on such a package, it will use this type information at generation time and
    include the type caster headers in generated wrappers.

    .. code-block:: toml

       [tool.semiwrap.export_type_casters.NAME]
       pypackage = "mypkg"
       includedirs = ["src/mypkg/type-casters"]

       [[tool.semiwrap.export_type_casters.NAME.headers]]
       header = "my_type_caster.h"
       types = ["foo_t", "ns::ins::bar_t"]

    .. seealso:: :ref:`type_casters`
    """

    #: Python package to write the .pc and .json file to
    pypackage: str

    #: Location of type caster header files, relative to the root of the project
    includedir: List[str]

    #: pkgconf 'requires'
    requires: List[str] = dataclasses.field(default_factory=list)

    #: Contains header/type mappings for type casters
    headers: List[TypeCasterHeader] = dataclasses.field(default_factory=list)


@dataclasses.dataclass
class ConditionalHeader:
    """
    Allows specifying that a header will only be autogenerated if the specified
    condition is true.

    .. code-block:: toml

        [tool.semiwrap.extension_modules."PACKAGE.NAME".headers]
        Name = { header="header.h", enable_if = "platform_machine == 'aarch64'" }

    """

    #: Name of the header file
    header: str

    #: This is a PEP 508 environment marker specification.
    #:
    #: The header will not be parsed if this does not evaluate to true
    enable_if: str


@dataclasses.dataclass
class ExtensionModuleConfig:
    """
    Configuration for generating pybind11 wrappers around specified header files.

    .. code-block:: toml

        [tool.semiwrap.extension_modules."PACKAGE.NAME"]

    """

    #: The name used internally to refer to this extension module. The name is
    #: used as the prefix of variable names in build files.
    #:
    #: If not specified, the default name is the package name with invalid
    #: characters replaced by underscores.
    name: Optional[str] = None

    #: Name of generated file that ensures the shared libraries and any
    #: dependencies are loaded. Defaults to ``_init_XXX.py``, where XXX
    #: is the last element of the package name
    #:
    #: Generally, you should create an ``__init__.py`` file that imports
    #: this module, otherwise your users will need to do so.
    libinit: Optional[str] = None

    #: Name of pkgconf libraries that this is wrapping. This is also added to
    #: depends list.
    wraps: List[str] = dataclasses.field(default_factory=list)

    #: List of pkgconf library dependencies. This affects this wrapper
    #: library in the following ways:
    #:
    #: * Any include file directories exported by the dependency will be added
    #:   to the include path for any source files compiled by this wrapper
    #: * It will be linked to any libraries the dependency contains
    #: * The python module for the dependency will be imported in the
    #:   ``_init{extension}.py`` file.
    depends: List[str] = dataclasses.field(default_factory=list)

    #: List of include directories for this module relative to the project root
    includes: List[str] = dataclasses.field(default_factory=list)

    #: #define macros to apply to the compilation
    #:
    #: .. code-block:: toml
    #:
    #:    [tool.semiwrap.extension_modules."package.name".defines]
    #:    SOMETHING_DEFINED = 1
    #:
    defines: Dict[str, Union[str, int]] = dataclasses.field(default_factory=dict)

    #: Specifies header files that autogenerated pybind11 wrappers will be
    #: created for. Simple C++ headers will most likely 'just work', but
    #: complex headers will need to have an accompanying :attr:`generation_data`
    #: file specified that can customize the autogenerated files.
    #:
    #: List of dictionaries: each dictionary key is used for the function
    #: name of the initialization function, the value is the header that is
    #: being wrapped. The header is searched for in the following order:
    #:
    #: - Relative to include directories specified by 'wraps' dependencies
    #: - Relative to the extra include directories for this module
    #: - Relative to the package directory
    #:
    #: .. code-block:: toml
    #:
    #:    [tool.semiwrap.extension_modules."PACKAGE.NAME".headers]
    #:    Name = "header.h"
    #:
    #: .. seealso:: :ref:`autowrap`
    #:
    headers: Dict[str, Union[str, ConditionalHeader]] = dataclasses.field(
        default_factory=dict
    )

    #: Path to a directory of yaml files. Generation data will be looked up
    #: using the key in the headers dictionary.
    #:
    #: These YAML files can be generated via the semiwrap command line tool:
    #:
    #: .. code-block:: sh
    #:
    #:    semiwrap update-yaml --write
    #:
    #: .. seealso:: :ref:`gendata`
    #:
    yaml_path: Optional[str] = None

    #: If True, skip this wrapper
    ignore: bool = False


@dataclasses.dataclass
class SemiwrapToolConfig:
    """
    Contains information for configuring the project

    .. code-block:: toml

       [tool.semiwrap]

    .. note:: This section is required
    """

    #: List of headers for the scan-headers tool to ignore
    scan_headers_ignore: List[str] = dataclasses.field(default_factory=list)

    #: List of python packages with __init__.py to update when ``python setup.py update_init``
    #: is called -- this is an argument to the ``semiwrap create-imports`` command, and
    #: may contain a space and the second argument to create-imports.
    update_init: List[str] = dataclasses.field(default_factory=list)

    #: Modules to autogenerate. The key is the name of the python package that will
    #: be generated. For example, "package.name" will cause the shared library
    #: 'package/name-{abi tag}.so' to be installed
    extension_modules: Dict[str, ExtensionModuleConfig] = dataclasses.field(
        default_factory=dict
    )

    #: Type casters to export. The key is the pkgconf package name.
    export_type_casters: Dict[str, TypeCasterConfig] = dataclasses.field(
        default_factory=dict
    )


@dataclasses.dataclass
class SemiwrapHatchlingConfig:
    """
    semiwrap hatchling hook configuration

    .. code-block:: toml

       [tool.hatch.build.hooks.semiwrap]

    .. note:: This section is required
    """

    #: Output directory to write meson.build to
    autogen_build_path: str = "semiwrap"

    #: Output directory to write second stage meson.build to (defaults
    #: to ``autogen_build_path / 'modules'``)
    module_build_path: Optional[str] = None
