# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from dataclasses import dataclass
from functools import partial

from .root_base import ElementBase
from .expander import Expander, StringsOnlyExpander
from .yaml_objects import BaseYamlObject
from .loc_loader import LocDict
from .errors import JenkinsJobsException
from .position import Pos


macro_specs = [
    # type_name, elements_name (aka component_type, component_list_type for Registry).
    ("parameter", "parameters"),
    ("property", "properties"),
    ("builder", "builders"),
    ("wrapper", "wrappers"),
    ("trigger", "triggers"),
    ("publisher", "publishers"),
    ("scm", "scm"),
    ("pipeline-scm", "pipeline-scm"),
    ("reporter", "reporters"),
    ("notification", "notifications"),
]


@dataclass
class Macro(ElementBase):
    _expander: Expander
    _str_expander: StringsOnlyExpander
    _type_name: str
    name: str
    defaults_name: str
    pos: Pos
    elements: list

    @classmethod
    def add(
        cls,
        type_name,
        elements_name,
        config,
        roots,
        data,
        pos,
    ):
        d = data.copy()
        name = d.pop_required_loc_string("name")
        defaults = d.pop_loc_string("defaults", "global")
        elements = d.pop_required_element(elements_name)
        params = d
        expander = Expander(config)
        str_expander = StringsOnlyExpander(config)
        macro = cls(
            _defaults=roots.defaults,
            _expander=expander,
            _str_expander=str_expander,
            _type_name=type_name,
            name=name,
            defaults_name=defaults,
            pos=pos,
            params=params,
            elements=elements or [],
        )
        roots.assign(roots.macros[type_name], name, macro, "macro")

    def __str__(self):
        return f"{self._type_name} macro {self.name!r}"

    def dispatch_elements(self, registry, xml_parent, component_data, job_data, params):
        defaults = self._pick_defaults(self.defaults_name)
        full_params = LocDict.merge(
            defaults.params,
            self.params,
            params,
        )
        element_list = self.elements
        if isinstance(element_list, BaseYamlObject):
            # Expand !j2-yaml tag if it is right below macro body.
            # But do not expand yaml tags inside it - they will be expanded later.
            element_list = element_list.expand(self._str_expander, full_params)
        for element in element_list:
            try:
                expanded_element = self._expander.expand(element, full_params)
            except JenkinsJobsException as x:
                raise x.with_context(
                    f"While expanding {self}",
                    pos=self.pos,
                )
            # Pass component_data in as template data to this function
            # so that if the macro is invoked with arguments,
            # the arguments are interpolated into the real defn.
            registry.dispatch(
                self._type_name,
                xml_parent,
                expanded_element,
                component_data,
                job_data=job_data,
            )


macro_adders = {
    macro_type: partial(Macro.add, macro_type, elements_name)
    for macro_type, elements_name in macro_specs
}