# -*- coding: utf-8 -*-
# Copyright (C) 2015 Joost van der Griendt <joostvdg@gmail.com>
# Copyright (C) 2018 Sorin Sbarnea <ssbarnea@users.noreply.github.com>
#
# 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.


"""
The Multibranch Pipeline project module handles creating Jenkins workflow
projects.
You may specify ``multibranch`` in the ``project-type`` attribute of
the :ref:`Job` definition.

Multibranch Pipeline implementantion in JJB is marked as **experimental**
which means that there is no guarantee that its behavior (or configuration)
will not change, even between minor releases.

Plugins required:

* :jenkins-plugins:`Workflow Plugin <workflow-aggregator>`.
* :jenkins-plugins:`Pipeline Multibranch Defaults Plugin
  <pipeline-multibranch-defaults>` (optional)
* :jenkins-plugins:`Basic Branch Build Strategies Plugin
  <basic-branch-build-strategies>` (optional)

:Job Parameters:

    * **scm** (`list`): The SCM definition.

        * **bitbucket** (`dict`): Refer to
          :func:`~bitbucket_scm <bitbucket_scm>` for documentation.

        * **gerrit** (`dict`): Refer to
          :func:`~gerrit_scm <gerrit_scm>` for documentation.

        * **git** (`dict`): Refer to
          :func:`~git_scm <git_scm>` for documentation.

        * **github** (`dict`): Refer to
          :func:`~github_scm <github_scm>` for documentation.

    * **periodic-folder-trigger** (`str`): How often to scan for new branches
      or pull/change requests. Valid values: 1m, 2m, 5m, 10m, 15m, 20m, 25m,
      30m, 1h, 2h, 4h, 8h, 12h, 1d, 2d, 1w, 2w, 4w. (default none)
    * **prune-dead-branches** (`bool`): If dead branches upon check should
      result in their job being dropped. (default true)
    * **number-to-keep** (`int`): How many builds should be kept.
      (default '-1, all')
    * **days-to-keep** (`int`): For how many days should a build be kept.
      (default '-1, forever')
    * **abort-builds** (`bool`): Abort all pending or ongoing builds for removed
      SCM heads (i.e. deleted branches). (default false)
    * **script-path** (`str`): Path to Jenkinsfile, relative to workspace.
      (default 'Jenkinsfile')
    * **script-id** (`str`): Script id from the global Jenkins script store
      provided by the config-file provider plugin. Mutually exclusive with
      **script-path** option.
    * **sandbox** (`bool`): This option is strongly recommended if the
      Jenkinsfile is using load to evaluate a groovy source file from an
      SCM repository. Usable only with **script-id** option. (default 'false')

Job examples:

.. literalinclude:: /../../tests/multibranch/fixtures/multibranch_defaults_id_mode.yaml

.. literalinclude:: /../../tests/multibranch/fixtures/multibranch_defaults_path_mode.yaml

.. literalinclude:: /../../tests/multibranch/fixtures/multi_scm_full.yaml

"""
import collections
import logging
import xml.etree.ElementTree as XML
import jenkins_jobs.modules.base
import jenkins_jobs.modules.helpers as helpers
import six

from jenkins_jobs.modules.scm import git_extensions
from jenkins_jobs.errors import InvalidAttributeError, MissingAttributeError
from jenkins_jobs.errors import Context, JenkinsJobsException
from jenkins_jobs.xml_config import remove_ignorable_whitespace

logger = logging.getLogger(str(__name__))


class WorkflowMultiBranch(jenkins_jobs.modules.base.Base):
    sequence = 0
    multibranch_path = "org.jenkinsci.plugins.workflow.multibranch"
    multibranch_defaults_path = "org.jenkinsci.pipeline.workflow.multibranch"
    jenkins_class = "".join([multibranch_path, ".WorkflowMultiBranchProject"])
    jenkins_factory = {
        "script_path": {
            "class": "".join([multibranch_path, ".WorkflowBranchProjectFactory"])
        },
        "script_id": {
            "class": "".join(
                [
                    multibranch_defaults_path,
                    ".defaults.PipelineBranchDefaultsProjectFactory",
                ]
            ),
            "plugin": "pipeline-multibranch-defaults",
        },
    }

    @staticmethod
    def _factory_opts_check(data):

        sandbox = data.get("sandbox", None)
        script_id = data.get("script-id", None)
        script_path = data.get("script-path", None)

        if script_id and script_path:
            error_msg = "script-id and script-path are mutually exclusive options"
            raise JenkinsJobsException(error_msg)
        elif not script_id and sandbox:
            error_msg = (
                "Sandbox mode is applicable only for multibranch with defaults"
                "project type used with script-id option"
            )
            raise JenkinsJobsException(error_msg)

    def root_xml(self, data):
        xml_parent = XML.Element(self.jenkins_class)
        xml_parent.attrib["plugin"] = "workflow-multibranch"
        XML.SubElement(xml_parent, "properties")

        #########
        # Views #
        #########

        views = XML.SubElement(xml_parent, "views")
        all_view = XML.SubElement(views, "hudson.model.AllView")
        all_view_mapping = [
            ("", "name", "All"),
            ("", "filterExecutors", False),
            ("", "filterQueue", False),
        ]
        helpers.convert_mapping_to_xml(
            all_view, {}, all_view_mapping, fail_required=True
        )

        XML.SubElement(
            all_view, "properties", {"class": "hudson.model.View$PropertyList"}
        )

        XML.SubElement(
            all_view, "owner", {"class": self.jenkins_class, "reference": "../../.."}
        )

        XML.SubElement(
            xml_parent, "viewsTabBar", {"class": "hudson.views.DefaultViewsTabBar"}
        )

        ################
        # Folder Views #
        ################

        folderViews = XML.SubElement(
            xml_parent,
            "folderViews",
            {
                "class": "jenkins.branch.MultiBranchProjectViewHolder",
                "plugin": "branch-api",
            },
        )

        XML.SubElement(
            folderViews, "owner", {"class": self.jenkins_class, "reference": "../.."}
        )

        ##################
        # Health Metrics #
        ##################

        hm = XML.SubElement(xml_parent, "healthMetrics")
        hm_path = "com.cloudbees.hudson.plugins.folder.health" ".WorstChildHealthMetric"
        hm_plugin = XML.SubElement(hm, hm_path, {"plugin": "cloudbees-folder"})
        XML.SubElement(hm_plugin, "nonRecursive").text = "false"

        ########
        # Icon #
        ########

        icon = XML.SubElement(
            xml_parent,
            "icon",
            {
                "class": "jenkins.branch.MetadataActionFolderIcon",
                "plugin": "branch-api",
            },
        )
        XML.SubElement(
            icon, "owner", {"class": self.jenkins_class, "reference": "../.."}
        )

        ########################
        # Orphan Item Strategy #
        ########################

        ois_default_strategy = (
            "com.cloudbees.hudson.plugins."
            "folder.computed.DefaultOrphanedItemStrategy"
        )
        ois = XML.SubElement(
            xml_parent,
            "orphanedItemStrategy",
            {"class": ois_default_strategy, "plugin": "cloudbees-folder"},
        )

        ois_mapping = [
            ("prune-dead-branches", "pruneDeadBranches", True, [True, False]),
            ("days-to-keep", "daysToKeep", -1),
            ("number-to-keep", "numToKeep", -1),
            ("abort-builds", "abortBuilds", False, [True, False]),
        ]
        helpers.convert_mapping_to_xml(ois, data, ois_mapping)

        ###########################
        # Periodic Folder Trigger #
        ###########################

        triggers = XML.SubElement(xml_parent, "triggers")

        # Valid options for the periodic trigger interval.
        pft_map = collections.OrderedDict(
            [
                ("1m", ("* * * * *", "60000")),
                ("2m", ("*/2 * * * *", "120000")),
                ("5m", ("*/5 * * * *", "300000")),
                ("10m", ("H/6 * * * *", "600000")),
                ("15m", ("H/6 * * * *", "900000")),
                ("20m", ("H/3 * * * *", "1200000")),
                ("25m", ("H/3 * * * *", "1500000")),
                ("30m", ("H/2 * * * *", "1800000")),
                ("1h", ("H * * * *", "3600000")),
                ("2h", ("H * * * *", "7200000")),
                ("4h", ("H * * * *", "14400000")),
                ("8h", ("H * * * *", "28800000")),
                ("12h", ("H H * * *", "43200000")),
                ("1d", ("H H * * *", "86400000")),
                ("2d", ("H H * * *", "172800000")),
                ("1w", ("H H * * *", "604800000")),
                ("2w", ("H H * * *", "1209600000")),
                ("4w", ("H H * * *", "2419200000")),
            ]
        )

        pft_val = data.get("periodic-folder-trigger")
        if pft_val:
            if not pft_map.get(pft_val):
                raise InvalidAttributeError(
                    "periodic-folder-trigger", pft_val, pft_map.keys()
                )

            pft_path = (
                "com.cloudbees.hudson.plugins.folder.computed." "PeriodicFolderTrigger"
            )
            pft = XML.SubElement(triggers, pft_path, {"plugin": "cloudbees-folder"})
            XML.SubElement(pft, "spec").text = pft_map[pft_val][0]
            XML.SubElement(pft, "interval").text = pft_map[pft_val][1]

        ###########
        # Sources #
        ###########

        sources = XML.SubElement(
            xml_parent,
            "sources",
            {
                "class": "jenkins.branch.MultiBranchProject$BranchSourceList",
                "plugin": "branch-api",
            },
        )
        sources_data = XML.SubElement(sources, "data")
        XML.SubElement(
            sources, "owner", {"class": self.jenkins_class, "reference": "../.."}
        )

        valid_scm = ["bitbucket", "gerrit", "git", "github"]
        for scm_data in data.get("scm", None):
            for scm in scm_data:
                bs = XML.SubElement(sources_data, "jenkins.branch.BranchSource")

                if scm == "bitbucket":
                    bitbucket_scm(bs, scm_data[scm])

                elif scm == "gerrit":
                    gerrit_scm(bs, scm_data[scm])

                elif scm == "git":
                    git_scm(bs, scm_data[scm])

                elif scm == "github":
                    github_scm(bs, scm_data[scm])

                else:
                    raise InvalidAttributeError("scm", scm_data, valid_scm)

        ###########
        # Factory #
        ###########

        self._factory_opts_check(data)

        if data.get("script-id"):
            mode = "script_id"
            fopts_map = (
                ("script-id", "scriptId", None),
                ("sandbox", "useSandbox", None),
            )
        else:
            mode = "script_path"
            fopts_map = (("script-path", "scriptPath", "Jenkinsfile"),)

        factory = XML.SubElement(xml_parent, "factory", self.jenkins_factory[mode])
        XML.SubElement(
            factory, "owner", {"class": self.jenkins_class, "reference": "../.."}
        )

        # multibranch default

        helpers.convert_mapping_to_xml(factory, data, fopts_map, fail_required=False)

        return xml_parent


class WorkflowMultiBranchDefaults(WorkflowMultiBranch):
    multibranch_path = "org.jenkinsci.plugins.workflow.multibranch"
    multibranch_defaults_path = "org.jenkinsci.plugins.pipeline.multibranch"
    jenkins_class = "".join(
        [multibranch_defaults_path, ".defaults.PipelineMultiBranchDefaultsProject"]
    )
    jenkins_factory = {
        "script_path": {
            "class": "".join([multibranch_path, ".WorkflowBranchProjectFactory"]),
            "plugin": "workflow-multibranch",
        },
        "script_id": {
            "class": "".join(
                [
                    multibranch_defaults_path,
                    ".defaults.PipelineBranchDefaultsProjectFactory",
                ]
            )
        },
    }


def bitbucket_scm(xml_parent, data):
    r"""Configure BitBucket scm

    Requires the :jenkins-plugins:`Bitbucket Branch Source Plugin
    <cloudbees-bitbucket-branch-source>`.

    :arg str credentials-id: The credential to use to scan BitBucket.
        (required)
    :arg str repo-owner: Specify the name of the Bitbucket Team or Bitbucket
        User Account. (required)
    :arg str repo: The BitBucket repo. (required)

    :arg bool discover-tags: Discovers tags on the repository.
        (default false)
    :arg bool lfs: Git LFS pull after checkout.
        (default false)
    :arg str server-url: The address of the bitbucket server. (optional)
    :arg str head-filter-regex: A regular expression for filtering
        discovered source branches. Requires the :jenkins-plugins:`SCM API
        Plugin <scm-api>`.
    :arg list head-pr-filter-behaviors: Definition of Filter Branch PR behaviors.
        Requires the :jenkins-plugins:`SCM Filter Branch PR Plugin
        <scm-filter-branch-pr>`.
        Refer to :func:`~add_filter_branch_pr_behaviors <add_filter_branch_pr_behaviors>`.
    :arg str discover-branch: Discovers branches on the repository.
        Valid options: ex-pr, only-pr, all.
        Value is not specified by default.
    :arg str discover-pr-origin: Discovers pull requests where the origin
        repository is the same as the target repository.
        Valid options: mergeOnly, headOnly, mergeAndHead.
        Value is not specified by default.
    :arg str discover-pr-forks-strategy: Fork strategy. Valid options:
        merge-current, current, both, false. (default 'merge-current')
    :arg str discover-pr-forks-trust: Discovers pull requests where the origin
        repository is a fork of the target repository.
        Valid options: contributors, everyone, permission or nobody.
        (default 'contributors')
    :arg list build-strategies: Provides control over whether to build a branch
        (or branch like things such as change requests and tags) whenever it is
        discovered initially or a change from the previous revision has been
        detected. (optional)
        Refer to :func:`~build_strategies <build_strategies>`.
    :arg dict property-strategies: Provides control over how to build a branch
        (like to disable SCM triggering or to override the pipeline durability)
        (optional)
        Refer to :func:`~property_strategies <property_strategies>`.
    :arg bool local-branch: Check out to matching local branch
        If given, checkout the revision to build as HEAD on this branch.
        If selected, then the branch name is computed from the remote branch
        without the origin. In that case, a remote branch origin/master will
        be checked out to a local branch named master, and a remote branch
        origin/develop/new-feature will be checked out to a local branch
        named develop/newfeature.
        Requires the :jenkins-plugins:`Git Plugin <git>`.
    :arg list(str) refspecs: Which refspecs to look for.
    :arg dict checkout-over-ssh: Checkout repo over ssh.

        * **credentials** ('str'): Credentials to use for
            checkout of the repo over ssh.

    :arg dict filter-by-name-wildcard: Enable filter by name with wildcards.
        Requires the :jenkins-plugins:`SCM API Plugin <scm-api>`.

        * **includes** ('str'): Space-separated list
            of name patterns to consider. You may use * as a wildcard;
            for example: `master release*`
        * **excludes** ('str'): Name patterns to
            ignore even if matched by the includes list.
            For example: `release*`

    :extensions:

        * **clean** (`dict`)
            * **after** (`dict`) - Clean the workspace after checkout
                * **remove-stale-nested-repos** (`bool`) - Deletes untracked
                  submodules and any other subdirectories which contain .git directories
                  (default false)
            * **before** (`dict`) - Clean the workspace before checkout
                * **remove-stale-nested-repos** (`bool`) - Deletes untracked
                  submodules and any other subdirectories which contain .git directories
                  (default false)
        * **prune** (`bool`) - Prune remote branches (default false)
        * **shallow-clone** (`bool`) - Perform shallow clone (default false)
        * **sparse-checkout** (dict)
            * **paths** (list) - List of paths to sparse checkout. (optional)
        * **depth** (`int`) - Set shallow clone depth (default 1)
        * **do-not-fetch-tags** (`bool`) - Perform a clone without tags
            (default false)
        * **submodule** (`dict`)
            * **disable** (`bool`) - By disabling support for submodules you
              can still keep using basic git plugin functionality and just have
              Jenkins to ignore submodules completely as if they didn't exist.
            * **recursive** (`bool`) - Retrieve all submodules recursively
              (uses '--recursive' option which requires git>=1.6.5)
            * **tracking** (`bool`) - Retrieve the tip of the configured
              branch in .gitmodules (Uses '\-\-remote' option which requires
              git>=1.8.2)
            * **parent-credentials** (`bool`) - Use credentials from default
              remote of parent repository (default false).
            * **reference-repo** (`str`) - Path of the reference repo to use
              during clone (optional)
            * **timeout** (`int`) - Specify a timeout (in minutes) for
              submodules operations (default 10).
        * **timeout** (`str`) - Timeout for git commands in minutes (optional)
        * **use-author** (`bool`): Use author rather than committer in Jenkin's
            build changeset (default false)
        * **wipe-workspace** (`bool`) - Wipe out workspace before build
            (default true)
        * **lfs-pull** (`bool`) - Call git lfs pull after checkout
            (default false)


    Minimal Example:

    .. literalinclude::
       /../../tests/multibranch/fixtures/scm_bitbucket_minimal.yaml

    Full Example:

    .. literalinclude::
       /../../tests/multibranch/fixtures/scm_bitbucket_full.yaml
    """
    source = XML.SubElement(
        xml_parent,
        "source",
        {
            "class": "com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource",
            "plugin": "cloudbees-bitbucket-branch-source",
        },
    )
    source_mapping = [
        ("", "id", "-".join(["bb", data.get("repo-owner", ""), data.get("repo", "")])),
        ("repo-owner", "repoOwner", None),
        ("repo", "repository", None),
    ]
    helpers.convert_mapping_to_xml(source, data, source_mapping, fail_required=True)

    mapping_optional = [
        ("credentials-id", "credentialsId", None),
        ("server-url", "serverUrl", None),
    ]
    helpers.convert_mapping_to_xml(source, data, mapping_optional, fail_required=False)

    traits = XML.SubElement(source, "traits")

    if data.get("refspecs"):
        refspec_trait = XML.SubElement(
            traits,
            "jenkins.plugins.git.traits.RefSpecsSCMSourceTrait",
            {"plugin": "git"},
        )
        templates = XML.SubElement(refspec_trait, "templates")
        refspecs = data.get("refspecs")
        for refspec in refspecs:
            e = XML.SubElement(
                templates,
                (
                    "jenkins.plugins.git.traits"
                    ".RefSpecsSCMSourceTrait_-RefSpecTemplate"
                ),
            )
            XML.SubElement(e, "value").text = refspec

    if data.get("discover-tags", False):
        XML.SubElement(
            traits, "com.cloudbees.jenkins.plugins.bitbucket.TagDiscoveryTrait"
        )

    if data.get("lfs", False):
        gitlfspull = XML.SubElement(
            traits, "jenkins.plugins.git.traits.GitLFSPullTrait", {"plugin": "git"}
        )
        XML.SubElement(
            gitlfspull,
            "extension",
            {"class": "hudson.plugins.git.extensions.impl.GitLFSPull"},
        )

    if data.get("head-filter-regex", None):
        rshf = XML.SubElement(traits, "jenkins.scm.impl.trait.RegexSCMHeadFilterTrait")
        XML.SubElement(rshf, "regex").text = data.get("head-filter-regex")

    if data.get("head-pr-filter-behaviors", None):
        add_filter_branch_pr_behaviors(traits, data.get("head-pr-filter-behaviors"))

    if data.get("discover-pr-origin", None):
        dpro = XML.SubElement(
            traits,
            "com.cloudbees.jenkins.plugins.bitbucket"
            ".OriginPullRequestDiscoveryTrait",
        )
        dpro_strategies = {"mergeOnly": "1", "headOnly": "2", "mergeAndHead": "3"}
        dpro_mapping = [("discover-pr-origin", "strategyId", None, dpro_strategies)]
        helpers.convert_mapping_to_xml(dpro, data, dpro_mapping, fail_required=True)

    if data.get("discover-pr-forks-strategy"):
        dprf = XML.SubElement(
            traits,
            "com.cloudbees.jenkins.plugins.bitbucket" ".ForkPullRequestDiscoveryTrait",
        )
        dprf_strategy = {"merge-current": "1", "current": "2", "both": "3"}
        dprf_mapping = [
            ("discover-pr-forks-strategy", "strategyId", "merge-current", dprf_strategy)
        ]
        helpers.convert_mapping_to_xml(dprf, data, dprf_mapping, fail_required=True)

        trust = data.get("discover-pr-forks-trust", "contributors")
        trust_map = {
            "contributors": "".join(
                [
                    "com.cloudbees.jenkins.plugins.bitbucket"
                    ".ForkPullRequestDiscoveryTrait$TrustContributors"
                ]
            ),
            "everyone": "".join(
                [
                    "com.cloudbees.jenkins.plugins.bitbucket"
                    ".ForkPullRequestDiscoveryTrait$TrustEveryone"
                ]
            ),
            "permission": "".join(
                [
                    "com.cloudbees.jenkins.plugins.bitbucket"
                    ".ForkPullRequestDiscoveryTrait$TrustPermission"
                ]
            ),
            "nobody": "".join(
                [
                    "com.cloudbees.jenkins.plugins.bitbucket"
                    ".ForkPullRequestDiscoveryTrait$TrustNobody"
                ]
            ),
        }
        if trust not in trust_map:
            raise InvalidAttributeError(
                "discover-pr-forks-trust", trust, trust_map.keys()
            )
        XML.SubElement(dprf, "trust").attrib["class"] = trust_map[trust]

    if data.get("discover-branch", None):
        dbr = XML.SubElement(
            traits, "com.cloudbees.jenkins.plugins.bitbucket.BranchDiscoveryTrait"
        )
        dbr_strategies = {"ex-pr": "1", "only-pr": "2", "all": "3"}
        dbr_mapping = [("discover-branch", "strategyId", None, dbr_strategies)]
        helpers.convert_mapping_to_xml(dbr, data, dbr_mapping, fail_required=True)

    if data.get("property-strategies", None):
        property_strategies(xml_parent, data)

    if data.get("build-strategies", None):
        build_strategies(xml_parent, data)

    if data.get("local-branch", False):
        lbr = XML.SubElement(
            traits, "jenkins.plugins.git.traits.LocalBranchTrait", {"plugin": "git"}
        )
        lbr_extension = XML.SubElement(
            lbr,
            "extension",
            {"class": "hudson.plugins.git.extensions.impl.LocalBranch"},
        )
        XML.SubElement(lbr_extension, "localBranch").text = "**"

    if data.get("checkout-over-ssh", None):
        cossh = XML.SubElement(
            traits, "com.cloudbees.jenkins.plugins.bitbucket.SSHCheckoutTrait"
        )
        cossh_credentials = [("credentials", "credentialsId", "")]
        helpers.convert_mapping_to_xml(
            cossh, data.get("checkout-over-ssh"), cossh_credentials, fail_required=True
        )

    add_filter_by_name_wildcard_behaviors(traits, data)

    # handle the default git extensions like:
    # - clean
    # - shallow-clone
    # - timeout
    # - do-not-fetch-tags
    # - submodule
    # - prune
    # - wipe-workspace
    # - use-author
    # - lfs-pull
    git_extensions(traits, data)


def gerrit_scm(xml_parent, data):
    r"""Configure Gerrit SCM

    Requires the :jenkins-plugins:`Gerrit Code Review Plugin
    <gerrit-code-review>`.

    :arg str url: The git url. (required)
    :arg str credentials-id: The credential to use to connect to the GIT URL.
    :arg bool ignore-on-push-notifications: If a job should not trigger upon
        push notifications. (default false)
    :arg list(str) refspecs: Which refspecs to look for.
        (default ``['+refs/changes/*:refs/remotes/@{remote}/*',
        '+refs/heads/*:refs/remotes/@{remote}/*']``)
    :arg str includes: Comma-separated list of branches to be included.
        (default '*')
    :arg str excludes: Comma-separated list of branches to be excluded.
        (default '')
    :arg str head-filter-regex: A regular expression for filtering
        discovered source branches. Requires the :jenkins-plugins:`SCM API
        Plugin <scm-api>`.
    :arg list build-strategies: Provides control over whether to build a branch
        (or branch like things such as change requests and tags) whenever it is
        discovered initially or a change from the previous revision has been
        detected. (optional)
        Refer to :func:`~build_strategies <build_strategies>`.
    :arg dict property-strategies: Provides control over how to build a branch
        (like to disable SCM triggering or to override the pipeline durability)
        (optional)
        Refer to :func:`~property_strategies <property_strategies>`.
    :arg dict filter-checks: Enable the filtering by pending checks, allowing to
        discover the changes that need validation only. This feature is using
        the gerrit checks plugin.
        (optional)
        query-operator: Name of the query operator, supported values are:
        'SCHEME' or 'ID'.
        query-string: Value of the query operator.
    :arg dict change-discovery: Configure the query string in 'Discover open changes'.
        The default 'p:<project> status:open -age:24w' will be added prior to the
        query-string specified here.
        (optional)
        query-string: Value of the query operator.

    :extensions:

        * **clean** (`dict`)
            * **after** (`dict`) - Clean the workspace after checkout
                * **remove-stale-nested-repos** (`bool`) - Deletes untracked
                  submodules and any other subdirectories which contain .git directories
                  (default false)
            * **before** (`dict`) - Clean the workspace before checkout
                * **remove-stale-nested-repos** (`bool`) - Deletes untracked
                  submodules and any other subdirectories which contain .git directories
                  (default false)
        * **prune** (`bool`) - Prune remote branches (default false)
        * **shallow-clone** (`bool`) - Perform shallow clone (default false)
        * **sparse-checkout** (dict)
            * **paths** (list) - List of paths to sparse checkout. (optional)
        * **depth** (`int`) - Set shallow clone depth (default 1)
        * **do-not-fetch-tags** (`bool`) - Perform a clone without tags
            (default false)
        * **submodule** (`dict`)
            * **disable** (`bool`) - By disabling support for submodules you
              can still keep using basic git plugin functionality and just have
              Jenkins to ignore submodules completely as if they didn't exist.
            * **recursive** (`bool`) - Retrieve all submodules recursively
              (uses '--recursive' option which requires git>=1.6.5)
            * **tracking** (`bool`) - Retrieve the tip of the configured
              branch in .gitmodules (Uses '\-\-remote' option which requires
              git>=1.8.2)
            * **parent-credentials** (`bool`) - Use credentials from default
              remote of parent repository (default false).
            * **reference-repo** (`str`) - Path of the reference repo to use
              during clone (optional)
            * **timeout** (`int`) - Specify a timeout (in minutes) for
              submodules operations (default 10).
        * **timeout** (`str`) - Timeout for git commands in minutes (optional)
        * **use-author** (`bool`): Use author rather than committer in Jenkin's
            build changeset (default false)
        * **wipe-workspace** (`bool`) - Wipe out workspace before build
            (default true)
        * **lfs-pull** (`bool`) - Call git lfs pull after checkout
            (default false)

    Minimal Example:

    .. literalinclude::
       /../../tests/multibranch/fixtures/scm_gerrit_minimal.yaml

    Full Example:

    .. literalinclude::
       /../../tests/multibranch/fixtures/scm_gerrit_full.yaml
    """
    source = XML.SubElement(
        xml_parent,
        "source",
        {
            "class": "jenkins.plugins.gerrit.GerritSCMSource",
            "plugin": "gerrit-code-review",
        },
    )
    source_mapping = [
        ("", "id", "-".join(["gr", data.get("url", "")])),
        ("url", "remote", None),
        ("credentials-id", "credentialsId", ""),
        ("includes", "includes", "*"),
        ("excludes", "excludes", ""),
        ("ignore-on-push-notifications", "ignoreOnPushNotifications", True),
    ]
    helpers.convert_mapping_to_xml(source, data, source_mapping, fail_required=True)

    source_mapping_optional = [("api-uri", "apiUri", None)]
    helpers.convert_mapping_to_xml(
        source, data, source_mapping_optional, fail_required=False
    )

    # Traits
    traits = XML.SubElement(source, "traits")
    change_discovery_trait = XML.SubElement(
        traits, "jenkins.plugins.gerrit.traits.ChangeDiscoveryTrait"
    )
    change_discovery = data.get("change-discovery", None)
    if change_discovery:
        change_discovery_mapping = [("query-string", "queryString", None)]
        helpers.convert_mapping_to_xml(
            change_discovery_trait,
            change_discovery,
            change_discovery_mapping,
            fail_required=True,
        )

    # Refspec Trait
    refspec_trait = XML.SubElement(
        traits, "jenkins.plugins.git.traits.RefSpecsSCMSourceTrait", {"plugin": "git"}
    )
    templates = XML.SubElement(refspec_trait, "templates")
    refspecs = data.get(
        "refspecs",
        [
            "+refs/changes/*:refs/remotes/@{remote}/*",
            "+refs/heads/*:refs/remotes/@{remote}/*",
        ],
    )
    # convert single string to list
    if isinstance(refspecs, six.string_types):
        refspecs = [refspecs]
    for x in refspecs:
        e = XML.SubElement(
            templates,
            ("jenkins.plugins.git.traits" ".RefSpecsSCMSourceTrait_-RefSpecTemplate"),
        )
        XML.SubElement(e, "value").text = x

    if data.get("property-strategies", None):
        property_strategies(xml_parent, data)

    if data.get("build-strategies", None):
        build_strategies(xml_parent, data)

    if data.get("head-filter-regex", None):
        rshf = XML.SubElement(traits, "jenkins.scm.impl.trait.RegexSCMHeadFilterTrait")
        XML.SubElement(rshf, "regex").text = data.get("head-filter-regex")

    # FilterChecks Trait
    checks = data.get("filter-checks", None)
    if checks:
        checks_trait = XML.SubElement(
            traits, "jenkins.plugins.gerrit.traits.FilterChecksTrait"
        )
        checks_source_mapping = [
            ("query-operator", "queryOperator", None),
            ("query-string", "queryString", None),
        ]
        helpers.convert_mapping_to_xml(
            checks_trait, checks, checks_source_mapping, fail_required=True
        )

    # handle the default git extensions like:
    # - clean
    # - shallow-clone
    # - timeout
    # - do-not-fetch-tags
    # - submodule
    # - prune
    # - wipe-workspace
    # - use-author
    # - lfs-pull
    git_extensions(traits, data)


def git_scm(xml_parent, data):
    r"""Configure Git SCM

    Requires the :jenkins-plugins:`Git Plugin <git>`.

    :arg str url: The git repo url. (required)
    :arg str credentials-id: The credential to use to connect to the GIT repo.
        (default '')

    :arg bool discover-branches: Discovers branches on the repository.
        (default true)
    :arg bool discover-tags: Discovers tags on the repository.
        (default false)
    :arg bool ignore-on-push-notifications: If a job should not trigger upon
        push notifications. (default false)
    :arg str head-filter-regex: A regular expression for filtering
        discovered source branches. Requires the :jenkins-plugins:`SCM API
        Plugin <scm-api>`.
    :arg list head-pr-filter-behaviors: Definition of Filter Branch PR behaviors.
        Requires the :jenkins-plugins:`SCM Filter Branch PR Plugin
        <scm-filter-branch-pr>`.
        Refer to :func:`~add_filter_branch_pr_behaviors <add_filter_branch_pr_behaviors>`.
    :arg list build-strategies: Provides control over whether to build a branch
        (or branch like things such as change requests and tags) whenever it is
        discovered initially or a change from the previous revision has been
        detected. (optional)
        Refer to :func:`~build_strategies <build_strategies>`.
    :arg dict property-strategies: Provides control over how to build a branch
        (like to disable SCM triggering or to override the pipeline durability)
        (optional)
        Refer to :func:`~property_strategies <property_strategies>`.

    :extensions:

        * **clean** (`dict`)
            * **after** (`dict`) - Clean the workspace after checkout
                * **remove-stale-nested-repos** (`bool`) - Deletes untracked
                  submodules and any other subdirectories which contain .git directories
                  (default false)
            * **before** (`dict`) - Clean the workspace before checkout
                * **remove-stale-nested-repos** (`bool`) - Deletes untracked
                  submodules and any other subdirectories which contain .git directories
                  (default false)
        * **prune** (`bool`) - Prune remote branches (default false)
        * **shallow-clone** (`bool`) - Perform shallow clone (default false)
        * **sparse-checkout** (dict)
            * **paths** (list) - List of paths to sparse checkout. (optional)
        * **depth** (`int`) - Set shallow clone depth (default 1)
        * **do-not-fetch-tags** (`bool`) - Perform a clone without tags
            (default false)
        * **submodule** (`dict`)
            * **disable** (`bool`) - By disabling support for submodules you
              can still keep using basic git plugin functionality and just have
              Jenkins to ignore submodules completely as if they didn't exist.
            * **recursive** (`bool`) - Retrieve all submodules recursively
              (uses '--recursive' option which requires git>=1.6.5)
            * **tracking** (`bool`) - Retrieve the tip of the configured
              branch in .gitmodules (Uses '\-\-remote' option which requires
              git>=1.8.2)
            * **parent-credentials** (`bool`) - Use credentials from default
              remote of parent repository (default false).
            * **reference-repo** (`str`) - Path of the reference repo to use
              during clone (optional)
            * **timeout** (`int`) - Specify a timeout (in minutes) for
              submodules operations (default 10).
        * **timeout** (`str`) - Timeout for git commands in minutes (optional)
        * **use-author** (`bool`): Use author rather than committer in Jenkin's
            build changeset (default false)
        * **wipe-workspace** (`bool`) - Wipe out workspace before build
            (default true)
        * **lfs-pull** (`bool`) - Call git lfs pull after checkout
            (default false)

    Minimal Example:

    .. literalinclude:: /../../tests/multibranch/fixtures/scm_git_minimal.yaml

    Full Example:

    .. literalinclude:: /../../tests/multibranch/fixtures/scm_git_full.yaml
    """
    source = XML.SubElement(
        xml_parent,
        "source",
        {"class": "jenkins.plugins.git.GitSCMSource", "plugin": "git"},
    )
    source_mapping = [
        ("", "id", "-".join(["gt", data.get("url", "")])),
        ("url", "remote", None),
        ("credentials-id", "credentialsId", ""),
    ]
    helpers.convert_mapping_to_xml(source, data, source_mapping, fail_required=True)

    ##########
    # Traits #
    ##########

    traits_path = "jenkins.plugins.git.traits"
    traits = XML.SubElement(source, "traits")

    if data.get("discover-branches", True):
        XML.SubElement(traits, "".join([traits_path, ".BranchDiscoveryTrait"]))

    if data.get("discover-tags", False):
        XML.SubElement(traits, "".join([traits_path, ".TagDiscoveryTrait"]))

    if data.get("ignore-on-push-notifications", False):
        XML.SubElement(traits, "".join([traits_path, ".IgnoreOnPushNotificationTrait"]))

    if data.get("head-filter-regex", None):
        rshf = XML.SubElement(traits, "jenkins.scm.impl.trait.RegexSCMHeadFilterTrait")
        XML.SubElement(rshf, "regex").text = data.get("head-filter-regex")

    if data.get("head-pr-filter-behaviors", None):
        add_filter_branch_pr_behaviors(traits, data.get("head-pr-filter-behaviors"))

    if data.get("property-strategies", None):
        property_strategies(xml_parent, data)

    if data.get("build-strategies", None):
        build_strategies(xml_parent, data)

    # handle the default git extensions like:
    # - clean
    # - shallow-clone
    # - timeout
    # - do-not-fetch-tags
    # - submodule
    # - prune
    # - wipe-workspace
    # - use-author
    # - lfs-pull
    git_extensions(traits, data)


def github_scm(xml_parent, data):
    r"""Configure GitHub SCM

    Requires the :jenkins-plugins:`GitHub Branch Source Plugin
    <github-branch-source>`.

    :arg str api-uri: The GitHub API uri for hosted / on-site GitHub. Must
        first be configured in Global Configuration. (default GitHub)
    :arg bool ssh-checkout: Checkout over SSH.

        * **credentials** ('str'): Credentials to use for
            checkout of the repo over ssh.

    :arg str credentials-id: Credentials used to scan branches and pull
        requests, check out sources and mark commit statuses. (optional)
    :arg str repo-owner: Specify the name of the GitHub Organization or
        GitHub User Account. (required)
    :arg str repo: The GitHub repo. (required)

    :arg str branch-discovery: Discovers branches on the repository.
        Valid options: no-pr, only-pr, all, false. (default 'no-pr')
    :arg str discover-pr-forks-strategy: Fork strategy. Valid options:
        merge-current, current, both, false. (default 'merge-current')
    :arg str discover-pr-forks-trust: Discovers pull requests where the origin
        repository is a fork of the target repository.
        Valid options: contributors, everyone, permission or nobody.
        (default 'contributors')
    :arg str discover-pr-origin: Discovers pull requests where the origin
        repository is the same as the target repository.
        Valid options: merge-current, current, both, false.  (default 'merge-current')
    :arg bool discover-tags: Discovers tags on the repository.
        (default false)
    :arg list head-pr-filter-behaviors: Definition of Filter Branch PR behaviors.
        Requires the :jenkins-plugins:`SCM Filter Branch PR Plugin
        <scm-filter-branch-pr>`.
        Refer to :func:`~add_filter_branch_pr_behaviors <add_filter_branch_pr_behaviors>`.
    :arg list build-strategies: Provides control over whether to build a branch
        (or branch like things such as change requests and tags) whenever it is
        discovered initially or a change from the previous revision has been
        detected. (optional)
        Refer to :func:`~build_strategies <build_strategies>`.
    :arg dict notification-context: Change the default GitHub check notification
        context from "continuous-integration/jenkins/SUFFIX" to a custom label / suffix.
        (set a label and suffix to true or false, optional)
        Requires the :jenkins-plugins:`Github Custom Notification Context SCM
        Behaviour <github-scm-trait-notification-context>`.
        Refer to :func:`~add_notification_context_trait <add_notification_context_trait>`.
    :arg dict property-strategies: Provides control over how to build a branch
        (like to disable SCM triggering or to override the pipeline durability)
        (optional)
        Refer to :func:`~property_strategies <property_strategies>`.
    :arg dict filter-by-name-wildcard: Enable filter by name with wildcards.
        Requires the :jenkins-plugins:`SCM API Plugin <scm-api>`.

        * **includes** ('str'): Space-separated list
            of name patterns to consider. You may use * as a wildcard;
            for example: `master release*`
        * **excludes** ('str'): Name patterns to
            ignore even if matched by the includes list.
            For example: `release*`

    :extensions:

        * **clean** (`dict`)
            * **after** (`dict`) - Clean the workspace after checkout
                * **remove-stale-nested-repos** (`bool`) - Deletes untracked
                    submodules and any other subdirectories which contain .git directories
                    (default false)
            * **before** (`dict`) - Clean the workspace before checkout
                * **remove-stale-nested-repos** (`bool`) - Deletes untracked
                    submodules and any other subdirectories which contain .git directories
                    (default false)
        * **prune** (`bool`) - Prune remote branches (default false)
        * **shallow-clone** (`bool`) - Perform shallow clone (default false)
        * **sparse-checkout** (dict)
            * **paths** (list) - List of paths to sparse checkout. (optional)
        * **depth** (`int`) - Set shallow clone depth (default 1)
        * **do-not-fetch-tags** (`bool`) - Perform a clone without tags
            (default false)
        * **disable-pr-notifications** (`bool`) - Disable default github status
            notifications on pull requests (default false) (Requires the
            :jenkins-plugins:`GitHub Branch Source Plugin
            <disable-github-multibranch-status>`.)
        * **refspecs** (`list(str)`): Which refspecs to fetch.
        * **submodule** (`dict`)
            * **disable** (`bool`) - By disabling support for submodules you
              can still keep using basic git plugin functionality and just have
              Jenkins to ignore submodules completely as if they didn't exist.
            * **recursive** (`bool`) - Retrieve all submodules recursively
              (uses '--recursive' option which requires git>=1.6.5)
            * **tracking** (`bool`) - Retrieve the tip of the configured
              branch in .gitmodules (Uses '\-\-remote' option which requires
              git>=1.8.2)
            * **parent-credentials** (`bool`) - Use credentials from default
              remote of parent repository (default false).
            * **reference-repo** (`str`) - Path of the reference repo to use
              during clone (optional)
            * **timeout** (`int`) - Specify a timeout (in minutes) for
              submodules operations (default 10).
        * **timeout** (`str`) - Timeout for git commands in minutes (optional)
        * **use-author** (`bool`): Use author rather than committer in Jenkin's
            build changeset (default false)
        * **wipe-workspace** (`bool`) - Wipe out workspace before build
            (default true)
        * **lfs-pull** (`bool`) - Call git lfs pull after checkout
            (default false)

    Minimal Example:

    .. literalinclude::
       /../../tests/multibranch/fixtures/scm_github_minimal.yaml

    Full Example:

    .. literalinclude::
       /../../tests/multibranch/fixtures/scm_github_full.yaml
    """
    github_path = "org.jenkinsci.plugins.github_branch_source"
    github_path_dscore = "org.jenkinsci.plugins.github__branch__source"

    source = XML.SubElement(
        xml_parent,
        "source",
        {
            "class": "".join([github_path, ".GitHubSCMSource"]),
            "plugin": "github-branch-source",
        },
    )
    mapping = [
        ("", "id", "-".join(["gh", data.get("repo-owner", ""), data.get("repo", "")])),
        ("repo-owner", "repoOwner", None),
        ("repo", "repository", None),
    ]
    helpers.convert_mapping_to_xml(source, data, mapping, fail_required=True)

    mapping_optional = [
        ("api-uri", "apiUri", None),
        ("credentials-id", "credentialsId", None),
    ]
    helpers.convert_mapping_to_xml(source, data, mapping_optional, fail_required=False)

    traits = XML.SubElement(source, "traits")

    # no-pr value is assumed if branch-discovery not mentioned.
    if data.get("branch-discovery", "no-pr"):
        bd = XML.SubElement(
            traits, "".join([github_path_dscore, ".BranchDiscoveryTrait"])
        )
        bd_strategy = {"no-pr": "1", "only-pr": "2", "all": "3"}
        bd_mapping = [("branch-discovery", "strategyId", "no-pr", bd_strategy)]
        helpers.convert_mapping_to_xml(bd, data, bd_mapping, fail_required=True)

    if data.get("ssh-checkout", None):
        cossh = XML.SubElement(
            traits, "".join([github_path_dscore, ".SSHCheckoutTrait"])
        )
        if not isinstance(data.get("ssh-checkout"), bool):
            cossh_credentials = [("credentials", "credentialsId", "")]
            helpers.convert_mapping_to_xml(
                cossh, data.get("ssh-checkout"), cossh_credentials, fail_required=True
            )

    if data.get("discover-tags", False):
        XML.SubElement(traits, "".join([github_path_dscore, ".TagDiscoveryTrait"]))

    if data.get("discover-pr-forks-strategy", "merged-current"):
        dprf = XML.SubElement(
            traits, "".join([github_path_dscore, ".ForkPullRequestDiscoveryTrait"])
        )
        dprf_strategy = {"merge-current": "1", "current": "2", "both": "3"}
        dprf_mapping = [
            ("discover-pr-forks-strategy", "strategyId", "merge-current", dprf_strategy)
        ]
        helpers.convert_mapping_to_xml(dprf, data, dprf_mapping, fail_required=True)

        trust = data.get("discover-pr-forks-trust", "contributors")
        trust_map = {
            "contributors": "".join(
                [github_path, ".ForkPullRequestDiscoveryTrait$TrustContributors"]
            ),
            "everyone": "".join(
                [github_path, ".ForkPullRequestDiscoveryTrait$TrustEveryone"]
            ),
            "permission": "".join(
                [github_path, ".ForkPullRequestDiscoveryTrait$TrustPermission"]
            ),
            "nobody": "".join(
                [github_path, ".ForkPullRequestDiscoveryTrait$TrustNobody"]
            ),
        }
        if trust not in trust_map:
            raise InvalidAttributeError(
                "discover-pr-forks-trust", trust, trust_map.keys()
            )
        XML.SubElement(dprf, "trust").attrib["class"] = trust_map[trust]

    dpro_strategy = data.get("discover-pr-origin", "merge-current")
    if dpro_strategy:
        dpro = XML.SubElement(
            traits, "".join([github_path_dscore, ".OriginPullRequestDiscoveryTrait"])
        )
        dpro_strategy_map = {"merge-current": "1", "current": "2", "both": "3"}
        if dpro_strategy not in dpro_strategy_map:
            raise InvalidAttributeError(
                "discover-pr-origin", dpro_strategy, dpro_strategy_map.keys()
            )
        dpro_mapping = [
            ("discover-pr-origin", "strategyId", "merge-current", dpro_strategy_map)
        ]
        helpers.convert_mapping_to_xml(dpro, data, dpro_mapping, fail_required=True)

    if data.get("head-filter-regex", None):
        rshf = XML.SubElement(traits, "jenkins.scm.impl.trait.RegexSCMHeadFilterTrait")
        XML.SubElement(rshf, "regex").text = data.get("head-filter-regex")

    if data.get("head-pr-filter-behaviors", None):
        add_filter_branch_pr_behaviors(traits, data.get("head-pr-filter-behaviors"))

    if data.get("property-strategies", None):
        property_strategies(xml_parent, data)

    if data.get("build-strategies", None):
        build_strategies(xml_parent, data)

    add_github_checks_traits(traits, data)
    add_notification_context_trait(traits, data)
    add_filter_by_name_wildcard_behaviors(traits, data)

    # handle the default git extensions like:
    # - clean
    # - shallow-clone
    # - timeout
    # - do-not-fetch-tags
    # - submodule
    # - prune
    # - wipe-workspace
    # - use-author
    # - lfs-pull
    git_extensions(traits, data)

    if data.get("refspecs"):
        refspec_trait = XML.SubElement(
            traits,
            "jenkins.plugins.git.traits.RefSpecsSCMSourceTrait",
            {"plugin": "git"},
        )
        templates = XML.SubElement(refspec_trait, "templates")
        refspecs = data.get("refspecs")
        for refspec in refspecs:
            e = XML.SubElement(
                templates,
                (
                    "jenkins.plugins.git.traits"
                    ".RefSpecsSCMSourceTrait_-RefSpecTemplate"
                ),
            )
            XML.SubElement(e, "value").text = refspec

    # github-only extensions
    disable_github_status_path_dscore = (
        "com.adobe.jenkins.disable__github__multibranch__status"
    )
    if data.get("disable-pr-notifications", False):
        XML.SubElement(
            traits,
            "".join([disable_github_status_path_dscore, ".DisableStatusUpdateTrait"]),
            {"plugin": "disable-github-multibranch-status"},
        )


def build_strategies(xml_parent, data):
    """Configure Basic Branch Build Strategies.

    Requires the :jenkins-plugins:`Basic Branch Build Strategies Plugin
    <basic-branch-build-strategies>`.

    Other build strategies can be configured via raw XML injection.

    :arg list build-strategies: Definition of build strategies.

        * **all-strategies-match** (dict): All sub strategies must match for
            this strategy to match.
            * **strategies** (list): Sub strategies
        * **any-strategies-match** (dict): Builds whenever any of the sub
            strategies match.
            * **strategies** (list): Sub strategies
        * **tags** (dict): Builds tags
            * **ignore-tags-newer-than** (int) The number of days since the tag
                was created before it is eligible for automatic building.
                (optional, default -1)
            * **ignore-tags-older-than** (int) The number of days since the tag
                was created after which it is no longer eligible for automatic
                building. (optional, default -1)
        * **change-request** (dict): Builds change requests / pull requests
            * **ignore-target-only-changes** (bool) Ignore rebuilding merge
                branches when only the target branch changed.
                (optional, default false)
            * **ignore-untrusted-changes** (bool) Ignore change requests
                flagged as originating from an untrusted source.
                (optional, default false)
        * **regular-branches** (bool): Builds regular branches whenever a
            change is detected. (optional, default None)
        * **skip-initial-build** (bool): Skip initial build on first branch
            indexing. (optional, default None)
        * **named-branches** (list): Builds named branches whenever a change
          is detected.

            * **exact-name** (dict) Matches the name verbatim.
                * **name** (str) The name to match. (optional)
                * **case-sensitive** (bool) Check this box if the name should
                    be matched case sensitively. (default false)
            * **regex-name** (dict) Matches the name against a regular
              expression.

                * **regex** (str) A Java regular expression to restrict the
                    names. Names that do not match the supplied regular
                    expression will be ignored. (default `^.*$`)
                * **case-sensitive** (bool) Check this box if the name should
                    be matched case sensitively. (default false)
            * **wildcards-name** (dict) Matches the name against an
              include/exclude set of wildcards.

                * **includes** (str) Space-separated list of name patterns to
                    consider. You may use `*` as a wildcard;
                    for example: `master release*` (default `*`)
                * **excludes** (str) Name patterns to ignore even if matched
                    by the includes list. For example: release (optional)
        * **raw** (dict): Injects raw BuildStrategy XML to use other build
            strategy plugins.

    """

    basic_build_strategies = "jenkins.branch.buildstrategies.basic"
    if data.get("build-strategies", None):
        bbs_data = data.get("build-strategies", None)
        bbs = XML.SubElement(xml_parent, "buildStrategies")
    else:
        bbs_data = data.get("strategies", None)
        bbs = XML.SubElement(xml_parent, "strategies")
    for bbs_list in bbs_data:
        if "all-strategies-match" in bbs_list:
            all_elem = XML.SubElement(
                bbs,
                "".join([basic_build_strategies, ".AllBranchBuildStrategyImpl"]),
                {"plugin": "basic-branch-build-strategies"},
            )
            build_strategies(all_elem, bbs_list["all-strategies-match"])

        if "any-strategies-match" in bbs_list:
            any_elem = XML.SubElement(
                bbs,
                "".join([basic_build_strategies, ".AnyBranchBuildStrategyImpl"]),
                {"plugin": "basic-branch-build-strategies"},
            )
            build_strategies(any_elem, bbs_list["any-strategies-match"])

        if "tags" in bbs_list:
            tags = bbs_list["tags"]
            tags_elem = XML.SubElement(
                bbs,
                "".join([basic_build_strategies, ".TagBuildStrategyImpl"]),
                {"plugin": "basic-branch-build-strategies"},
            )

            newer_than = -1
            if "ignore-tags-newer-than" in tags and tags["ignore-tags-newer-than"] >= 0:
                newer_than = str(tags["ignore-tags-newer-than"] * 86400000)
            XML.SubElement(tags_elem, "atLeastMillis").text = str(newer_than)

            older_than = -1
            if "ignore-tags-older-than" in tags and tags["ignore-tags-older-than"] >= 0:
                older_than = str(tags["ignore-tags-older-than"] * 86400000)
            XML.SubElement(tags_elem, "atMostMillis").text = str(older_than)

        if bbs_list.get("regular-branches", False):
            XML.SubElement(
                bbs,
                "".join([basic_build_strategies, ".BranchBuildStrategyImpl"]),
                {"plugin": "basic-branch-build-strategies"},
            )

        if bbs_list.get("skip-initial-build", False):
            XML.SubElement(
                bbs,
                "".join(
                    [basic_build_strategies, ".SkipInitialBuildOnFirstBranchIndexing"]
                ),
                {"plugin": "basic-branch-build-strategies"},
            )

        if "change-request" in bbs_list:
            cr = bbs_list["change-request"]
            cr_elem = XML.SubElement(
                bbs,
                "".join([basic_build_strategies, ".ChangeRequestBuildStrategyImpl"]),
                {"plugin": "basic-branch-build-strategies"},
            )
            itoc = cr.get("ignore-target-only-changes", False)
            iuc = cr.get("ignore-untrusted-changes", False)
            XML.SubElement(cr_elem, "ignoreTargetOnlyChanges").text = str(itoc).lower()
            XML.SubElement(cr_elem, "ignoreUntrustedChanges").text = str(iuc).lower()

        if "named-branches" in bbs_list:
            named_branch_elem = XML.SubElement(
                bbs,
                "".join([basic_build_strategies, ".NamedBranchBuildStrategyImpl"]),
                {"plugin": "basic-branch-build-strategies"},
            )

            filters = XML.SubElement(named_branch_elem, "filters")

            for nb in bbs_list["named-branches"]:
                if "exact-name" in nb:
                    exact_name_elem = XML.SubElement(
                        filters,
                        "".join(
                            [
                                basic_build_strategies,
                                ".NamedBranchBuildStrategyImpl",
                                "_-ExactNameFilter",
                            ]
                        ),
                    )
                    exact_name_mapping = [
                        ("name", "name", ""),
                        ("case-sensitive", "caseSensitive", False),
                    ]
                    helpers.convert_mapping_to_xml(
                        exact_name_elem,
                        nb["exact-name"],
                        exact_name_mapping,
                        fail_required=False,
                    )

                if "regex-name" in nb:
                    regex_name_elem = XML.SubElement(
                        filters,
                        "".join(
                            [
                                basic_build_strategies,
                                ".NamedBranchBuildStrategyImpl",
                                "_-RegexNameFilter",
                            ]
                        ),
                    )
                    regex_name_mapping = [
                        ("regex", "regex", "^.*$"),
                        ("case-sensitive", "caseSensitive", False),
                    ]
                    helpers.convert_mapping_to_xml(
                        regex_name_elem,
                        nb["regex-name"],
                        regex_name_mapping,
                        fail_required=False,
                    )

                if "wildcards-name" in nb:
                    wildcards_name_elem = XML.SubElement(
                        filters,
                        "".join(
                            [
                                basic_build_strategies,
                                ".NamedBranchBuildStrategyImpl",
                                "_-WildcardsNameFilter",
                            ]
                        ),
                    )
                    wildcards_name_mapping = [
                        ("includes", "includes", "*"),
                        ("excludes", "excludes", ""),
                    ]
                    helpers.convert_mapping_to_xml(
                        wildcards_name_elem,
                        nb["wildcards-name"],
                        wildcards_name_mapping,
                        fail_required=False,
                    )

        if "raw" in bbs_list:
            raw_xml = XML.fromstring(bbs_list["raw"].get("xml"))
            remove_ignorable_whitespace(raw_xml)
            XML.SubElement(bbs, None).append(raw_xml)


def property_strategies(xml_parent, data):
    """Configure Basic Branch Property Strategies.

    Requires the :jenkins-plugins:`Branch API Plugin <branch-api>`.

    :arg dict property-strategies: Definition of property strategies.  Either
        `named-branches` or `all-branches` may be specified, but not both.

        * **all-branches** (list): A list of property strategy definitions
            for use with all branches.

            * **suppress-scm-triggering** (dict):
                Suppresses automatic SCM triggering (optional).

                * **branch-regex** (str):
                    Regex matching branch names.
                    Only these branches will be affected by the selected
                    suppression strategy. Default ``^$``.
                * **suppress-strategy** (str):
                    Select what to suppress for branches matching the regex.
                    Valid values: ``suppress-nothing`` (default),
                    ``suppress-branch-indexing``, or ``suppress-webhooks``.

                    .. note::
                        Using ``suppress-scm-triggering: true`` is deprecated
                        since Branch API 2.1045.v4ec3ed07b_e4f.
            * **pipeline-branch-durability-override** (str): Set a custom
                branch speed/durability level. Valid values:
                performance-optimized, survivable-nonatomic, or
                max-survivability (optional)
                Requires the :jenkins-plugins:`Pipeline Multibranch Plugin
                <workflow-multibranch>`
            * **trigger-build-on-pr-comment** (str or dict): The comment body to
                trigger a new build for a PR job when it is received. This
                is compiled as a case-insensitive regular expression, so
                use ``".*"`` to trigger a build on any comment whatsoever.
                (optional)
                If dictionary syntax is used, the option takes 3 fields:
                ``comment`` (str, required) with the comment body,
                ``allow-untrusted-users`` (bool, optional) causing the plugin
                to skip checking if the comment author is a collaborator of the
                GitHub project, and ``add-reaction`` (bool, optional) causing
                the plugin to add a thumbs-up reaction to the comment when it
                successfully triggers a build.
                Requires the :jenkins-plugins:`GitHub PR Comment Build Plugin
                <github-pr-comment-build>`
            * **trigger-build-on-pr-label** (str or dict): PR label to trigger
                a new build for a PR job when it is applied to the PR.
                If dictionary syntax is used, the option requires 1 field:
                ``label-regex`` with the label regular expression.
                Requires the :jenkins-plugins:`GitHub PR Comment Build Plugin
                <github-pr-comment-build>`
            * **trigger-build-on-pr-review** (bool or dict): This property will
                cause a job for a pull request ``(PR-*)`` to be triggered
                immediately when a review is made on the PR in GitHub.
                This has no effect on jobs that are not for pull requests.
                (optional)
                If dictionary syntax is used, the option requires
                ``allow-untrusted-users`` (bool) causing the plugin to skip
                checking if the review author is a collaborator of the GitHub
                project.
                Requires the :jenkins-plugins:`GitHub PR Comment Build Plugin
                <github-pr-comment-build>`
            * **trigger-build-on-pr-update** (bool or dict): This property will
                cause a job for a pull request ``(PR-*)`` to be triggered
                immediately when the PR title or description is edited in
                GitHub. This has no effect on jobs that are not for pull
                requests. (optional)
                If dictionary syntax is used, the option requires
                ``allow-untrusted-users`` (bool) causing the plugin to skip
                checking if the update author is a collaborator of the GitHub
                project.
                Requires the :jenkins-plugins:`GitHub PR Comment Build Plugin
                <github-pr-comment-build>`
        * **named-branches** (dict): Named branches get different properties.
            Comprised of a list of defaults and a list of property strategy
            exceptions for use with specific branches.

            * **defaults** (list): A list of property strategy definitions
                to be applied by default to all branches, unless overridden
                by an entry in `exceptions`

                * **suppress-scm-triggering** (dict):
                    Suppresses automatic SCM triggering (optional).

                    * **branch-regex** (str):
                        Regex matching branch names.
                        Only these branches will be affected by the selected
                        suppression strategy. Default ``^$``.
                    * **suppress-strategy** (str):
                        Select what to suppress for branches matching the regex.
                        Valid values: ``suppress-nothing`` (default),
                        ``suppress-branch-indexing``, or ``suppress-webhooks``.

                        .. note::
                            Using ``suppress-scm-triggering: true`` is deprecated
                            since Branch API 2.1045.v4ec3ed07b_e4f.
                * **pipeline-branch-durability-override** (str): Set a custom
                    branch speed/durability level. Valid values:
                    performance-optimized, survivable-nonatomic, or
                    max-survivability (optional)
                    Requires the :jenkins-plugins:`Pipeline Multibranch Plugin
                    <workflow-multibranch>`
                * **trigger-build-on-pr-comment** (str or dict): The comment body to
                    trigger a new build for a PR job when it is received. This
                    is compiled as a case-insensitive regular expression, so
                    use ``".*"`` to trigger a build on any comment whatsoever.
                    (optional)
                    If dictionary syntax is used, the option takes 3 fields:
                    ``comment`` (str, required) with the comment body,
                    ``allow-untrusted-users`` (bool, optional) causing the
                    plugin to skip checking if the comment author is a
                    collaborator of the GitHub project, and ``add-reaction``
                    (bool, optional) causing the plugin to add a thumbs-up
                    reaction to the comment when it successfully triggers a build.
                    Requires the :jenkins-plugins:`GitHub PR Comment Build Plugin
                    <github-pr-comment-build>`
                * **trigger-build-on-pr-review** (bool or dict): This property will
                    cause a job for a pull request ``(PR-*)`` to be triggered
                    immediately when a review is made on the PR in GitHub.
                    This has no effect on jobs that are not for pull requests.
                    (optional)
                    If dictionary syntax is used, the option requires
                    ``allow-untrusted-users`` (bool) causing the plugin to skip
                    checking if the review author is a collaborator of the GitHub
                    project.
                    Requires the :jenkins-plugins:`GitHub PR Comment Build Plugin
                    <github-pr-comment-build>`
                * **trigger-build-on-pr-update** (bool or dict): This property will
                    cause a job for a pull request ``(PR-*)`` to be triggered
                    immediately when the PR title or description is edited in
                    GitHub. This has no effect on jobs that are not for pull
                    requests. (optional)
                    If dictionary syntax is used, the option requires
                    ``allow-untrusted-users`` (bool) causing the plugin to skip
                    checking if the update author is a collaborator of the GitHub
                    project.
                    Requires the :jenkins-plugins:`GitHub PR Comment Build Plugin
                    <github-pr-comment-build>`

            * **exceptions** (list): A list of branch names and the property
                strategies to be used on that branch, instead of any listed
                in `defaults`.

                * **exception** (dict): Defines exception
                    * **branch-name** (str): Name of the branch to which these
                        properties will be applied.
                    * **properties** (list): A list of properties to apply to
                        this branch.

                        * **suppress-scm-triggering** (dict):
                            Suppresses automatic SCM triggering (optional).

                            * **branch-regex** (str):
                                Regex matching branch names.
                                Only these branches will be affected by the selected
                                suppression strategy. Default ``^$``.
                            * **suppress-strategy** (str):
                                Select what to suppress for branches matching the regex.
                                Valid values: ``suppress-nothing`` (default),
                                ``suppress-branch-indexing``, or ``suppress-webhooks``.

                                .. note::
                                    Using ``suppress-scm-triggering: true`` is deprecated
                                    since Branch API 2.1045.v4ec3ed07b_e4f.

                        * **pipeline-branch-durability-override** (str): Set a
                            custom branch speed/durability level. Valid values:
                            performance-optimized, survivable-nonatomic, or
                            max-survivability (optional)
                            Requires the :jenkins-plugins:`Pipeline
                            Multibranch Plugin <workflow-multibranch>`
    """

    valid_prop_strats = ["all-branches", "named-branches"]

    basic_property_strategies = "jenkins.branch"

    prop_strats = data.get("property-strategies", None)

    if prop_strats:

        for prop_strat in prop_strats:
            if prop_strat not in valid_prop_strats:
                raise InvalidAttributeError(
                    "property-strategies", prop_strat, valid_prop_strats
                )
        if len(prop_strats) > 1:
            raise JenkinsJobsException("Only one property strategy may be specified")

        all_branches = prop_strats.get("all-branches", None)
        named_branches = prop_strats.get("named-branches", None)

        if all_branches:

            strat_elem = XML.SubElement(
                xml_parent,
                "strategy",
                {
                    "class": "".join(
                        [basic_property_strategies, ".DefaultBranchPropertyStrategy"]
                    )
                },
            )
            props_elem = XML.SubElement(
                strat_elem, "properties", {"class": "java.util.Arrays$ArrayList"}
            )
            props_elem = XML.SubElement(
                props_elem,
                "a",
                {
                    "class": "".join(
                        [basic_property_strategies, ".BranchProperty-array"]
                    )
                },
            )

            apply_property_strategies(props_elem, all_branches)

        elif named_branches:

            strat_elem = XML.SubElement(
                xml_parent,
                "strategy",
                {
                    "class": "".join(
                        [
                            basic_property_strategies,
                            ".NamedExceptionsBranchPropertyStrategy",
                        ]
                    )
                },
            )

            nbs_defaults = named_branches.get("defaults", None)
            if nbs_defaults:

                props_elem = XML.SubElement(
                    strat_elem,
                    "defaultProperties",
                    {"class": "java.util.Arrays$ArrayList"},
                )
                props_elem = XML.SubElement(
                    props_elem,
                    "a",
                    {
                        "class": "".join(
                            [basic_property_strategies, ".BranchProperty-array"]
                        )
                    },
                )

                apply_property_strategies(props_elem, nbs_defaults)

            nbs_exceptions = named_branches.get("exceptions", None)
            if nbs_exceptions:

                props_elem = XML.SubElement(
                    strat_elem,
                    "namedExceptions",
                    {"class": "java.util.Arrays$ArrayList"},
                )
                props_elem = XML.SubElement(
                    props_elem,
                    "a",
                    {
                        "class": "".join(
                            [
                                basic_property_strategies,
                                ".NamedExceptionsBranchPropertyStrategy$Named-array",
                            ]
                        )
                    },
                )

                for named_exception in nbs_exceptions:
                    named_exception = named_exception.get("exception", None)
                    if not named_exception:
                        continue

                    exc_elem = XML.SubElement(
                        props_elem,
                        "".join(
                            [
                                basic_property_strategies,
                                ".NamedExceptionsBranchPropertyStrategy_-Named",
                            ]
                        ),
                    )

                    ne_branch_name = named_exception.get("branch-name", None)
                    if ne_branch_name is not None:
                        XML.SubElement(exc_elem, "name").text = ne_branch_name

                    ne_properties = named_exception.get("properties", None)
                    if ne_properties:
                        exc_elem = XML.SubElement(
                            exc_elem, "props", {"class": "java.util.Arrays$ArrayList"}
                        )
                        exc_elem = XML.SubElement(
                            exc_elem,
                            "a",
                            {
                                "class": "".join(
                                    [basic_property_strategies, ".BranchProperty-array"]
                                )
                            },
                        )
                        apply_property_strategies(exc_elem, ne_properties)


def apply_property_strategies(props_elem, props_list):
    # there are 3 locations at which property strategies can be defined:
    # globally (all-branches), defaults (named-branches), exceptions
    # (also named-branches)

    basic_property_strategies = "jenkins.branch"
    workflow_multibranch = "org.jenkinsci.plugins.workflow.multibranch"
    pr_comment_build = "com.adobe.jenkins.github__pr__comment__build"
    # Valid options for the pipeline branch durability override.
    pbdo_map = collections.OrderedDict(
        [
            ("max-survivability", "MAX_SURVIVABILITY"),
            ("performance-optimized", "PERFORMANCE_OPTIMIZED"),
            ("survivable-nonatomic", "SURVIVABLE_NONATOMIC"),
        ]
    )

    pcb_bool_opts = collections.OrderedDict(
        [
            ("trigger-build-on-pr-review", ".TriggerPRReviewBranchProperty"),
            ("trigger-build-on-pr-update", ".TriggerPRUpdateBranchProperty"),
        ]
    )

    # valid options for suppress SCM trigger property
    # strategy name says what's blocked
    sst_map = collections.OrderedDict(
        [
            ("suppress-nothing", "NONE"),
            ("suppress-webhooks", "EVENTS"),
            ("suppress-branch-indexing", "INDEXING"),
        ]
    )

    for dbs_list in props_list:

        sst_val = dbs_list.get("suppress-scm-triggering", False)
        if sst_val:
            sst_elem = XML.SubElement(
                props_elem,
                "".join([basic_property_strategies, ".NoTriggerBranchProperty"]),
            )

            if isinstance(sst_val, dict):
                if "branch-regex" not in sst_val:
                    raise MissingAttributeError("suppress-scm-triggering[branch-regex]")
                if "suppression-strategy" not in sst_val:
                    raise MissingAttributeError(
                        "suppress-scm-triggering[suppression-strategy]"
                    )

                sst_ss_val = sst_val["suppression-strategy"]
                if not sst_map.get(sst_ss_val):
                    raise InvalidAttributeError(
                        "suppression-strategy", sst_ss_val, sst_map.keys()
                    )
                XML.SubElement(sst_elem, "triggeredBranchesRegex").text = sst_val[
                    "branch-regex"
                ]
                XML.SubElement(sst_elem, "strategy").text = sst_map[sst_ss_val]

            elif isinstance(sst_val, bool):
                # no sub-elements in this case
                pass
            else:
                raise InvalidAttributeError("suppress-scm-triggering", sst_val)

        pbdo_val = dbs_list.get("pipeline-branch-durability-override", None)
        if pbdo_val:
            if not pbdo_map.get(pbdo_val):
                raise InvalidAttributeError(
                    "pipeline-branch-durability-override", pbdo_val, pbdo_map.keys()
                )
            pbdo_elem = XML.SubElement(
                props_elem,
                "".join([workflow_multibranch, ".DurabilityHintBranchProperty"]),
                {"plugin": "workflow-multibranch"},
            )
            XML.SubElement(pbdo_elem, "hint").text = pbdo_map.get(pbdo_val)

        tbopc_val = dbs_list.get("trigger-build-on-pr-comment", None)
        if tbopc_val:
            tbopc_elem = XML.SubElement(
                props_elem,
                "".join([pr_comment_build, ".TriggerPRCommentBranchProperty"]),
                {"plugin": "github-pr-comment-build"},
            )
            if isinstance(tbopc_val, dict):
                if "comment" not in tbopc_val:
                    raise MissingAttributeError(
                        "trigger-build-on-pr-comment[comment]",
                        pos=dbs_list.key_pos.get("trigger-build-on-pr-comment"),
                    )
                XML.SubElement(tbopc_elem, "commentBody").text = tbopc_val["comment"]
                if tbopc_val.get("allow-untrusted-users", False):
                    XML.SubElement(tbopc_elem, "allowUntrusted").text = "true"
                if tbopc_val.get("add-reaction", False):
                    XML.SubElement(tbopc_elem, "addReaction").text = "true"
            elif isinstance(tbopc_val, str):
                XML.SubElement(tbopc_elem, "commentBody").text = tbopc_val
            else:
                attr = "trigger-build-on-pr-comment"
                ctx = [Context(f"For attribute {attr!r}", dbs_list.key_pos.get(attr))]
                raise InvalidAttributeError(
                    attr,
                    tbopc_val,
                    pos=dbs_list.value_pos.get(attr),
                    ctx=ctx,
                )

        tbopl_val = dbs_list.get("trigger-build-on-pr-label", None)
        if tbopl_val:
            tbopl_elem = XML.SubElement(
                props_elem,
                "".join([pr_comment_build, ".TriggerPRLabelBranchProperty"]),
                {"plugin": "github-pr-comment-build"},
            )
            if isinstance(tbopl_val, dict):
                if "label-regex" not in tbopl_val:
                    raise MissingAttributeError(
                        "trigger-build-on-pr-label[label-regex]",
                        pos=dbs_list.key_pos.get("trigger-build-on-pr-label"),
                    )

                XML.SubElement(tbopl_elem, "label").text = tbopl_val["label-regex"]
            elif isinstance(tbopl_val, str):
                XML.SubElement(tbopl_elem, "label").text = str(tbopl_val)
            else:
                attr = "trigger-build-on-pr-label"
                ctx = [Context(f"For attribute {attr!r}", dbs_list.key_pos.get(attr))]
                raise InvalidAttributeError(
                    attr,
                    tbopl_val,
                    pos=dbs_list.value_pos.get(attr),
                    ctx=ctx,
                )

            # currently the only permissions available is WRITE and untrusted users are not allowed
            XML.SubElement(tbopl_elem, "allowUntrusted").text = "false"
            XML.SubElement(tbopl_elem, "minimumPermissions").text = "WRITE"

        for opt in pcb_bool_opts:
            opt_value = dbs_list.get(opt, None)
            if opt_value:
                opt_elem = XML.SubElement(
                    props_elem,
                    "".join([pr_comment_build, pcb_bool_opts.get(opt)]),
                    {"plugin": "github-pr-comment-build"},
                )
                if isinstance(opt_value, dict):
                    if opt_value.get("allow-untrusted-users", False):
                        XML.SubElement(opt_elem, "allowUntrusted").text = "true"
                elif isinstance(opt_value, bool):
                    # no sub-elements in this case
                    pass
                else:
                    ctx = Context(f"For attribute {opt!r}", dbs_list.key_pos.get(opt))
                    raise InvalidAttributeError(
                        opt, opt_value, pos=dbs_list.value_pos.get(opt), ctx=[ctx]
                    )


def add_filter_branch_pr_behaviors(traits, data):
    """Configure Filter Branch PR behaviors

    Requires the :jenkins-plugins:`SCM Filter Branch PR Plugin
    <scm-filter-branch-pr>`.

    :arg list head-pr-filter-behaviors: Definition of filters.

        * **head-pr-destined-regex** (dict): Filter by name incl. PR destined to
            this branch with regexp

            * **branch-regexp** (str) Regular expression to filter branches and
                PRs (optional, default ".*")
            * **tag-regexp** (str) Regular expression to filter tags
                (optional, default "(?!.*)")

        * **head-pr-destined-wildcard** (dict): Filter by name incl. PR
            destined to this branch with wildcard

            * **branch-includes** (str) Wildcard expression to include branches
                and PRs (optional, default "*")
            * **tag-includes** (str) Wildcard expression to include tags
                (optional, default "")
            * **branch-excludes** (str) Wildcard expression to exclude branches
                and PRs (optional, default "")
            * **tag-excludes** (str) Wildcard expression to exclude tags
                (optional, default "*")

        * **head-pr-originated-regex** (dict): Filter by name incl. PR destined
            to this branch with regexp

            * **branch-regexp** (str) Regular expression to filter branches
                and PRs (optional, default ".*")
            * **tag-regexp** (str) Regular expression to filter tags
                (optional, default "(?!.*)")

        * **head-pr-originated-wildcard** (dict): Filter by name incl. PR
            destined to this branch with wildcard

            * **branch-includes** (str) Wildcard expression to include branches
                and PRs (optional, default "*")
            * **tag-includes** (str) Wildcard expression to include tags
                (optional, default "")
            * **branch-excludes** (str) Wildcard expression to exclude branches
                and PRs (optional, default "")
            * **tag-excludes** (str) Wildcard expression to exclude tags
                (optional, default "*")
    """

    regexp_mapping = [
        ("branch-regexp", "regex", ".*"),
        ("tag-regexp", "tagRegex", "(?!.*)"),
    ]
    wildcard_mapping = [
        ("branch-includes", "includes", "*"),
        ("branch-excludes", "excludes", ""),
        ("tag-includes", "tagIncludes", ""),
        ("tag-excludes", "tagExcludes", "*"),
    ]

    if data.get("head-pr-destined-regex"):
        rshf = XML.SubElement(
            traits,
            "net.gleske.scmfilter.impl.trait.RegexSCMHeadFilterTrait",
            {"plugin": "scm-filter-branch-pr"},
        )
        helpers.convert_mapping_to_xml(
            rshf, data.get("head-pr-destined-regex"), regexp_mapping, fail_required=True
        )

    if data.get("head-pr-destined-wildcard"):
        wshf = XML.SubElement(
            traits,
            "net.gleske.scmfilter.impl.trait.WildcardSCMHeadFilterTrait",
            {"plugin": "scm-filter-branch-pr"},
        )
        helpers.convert_mapping_to_xml(
            wshf,
            data.get("head-pr-destined-wildcard"),
            wildcard_mapping,
            fail_required=True,
        )

    if data.get("head-pr-originated-regex"):
        rsof = XML.SubElement(
            traits,
            "net.gleske.scmfilter.impl.trait.RegexSCMOriginFilterTrait",
            {"plugin": "scm-filter-branch-pr"},
        )
        helpers.convert_mapping_to_xml(
            rsof,
            data.get("head-pr-originated-regex"),
            regexp_mapping,
            fail_required=True,
        )

    if data.get("head-pr-originated-wildcard"):
        wsof = XML.SubElement(
            traits,
            "net.gleske.scmfilter.impl.trait.WildcardSCMOriginFilterTrait",
            {"plugin": "scm-filter-branch-pr"},
        )
        helpers.convert_mapping_to_xml(
            wsof,
            data.get("head-pr-originated-wildcard"),
            wildcard_mapping,
            fail_required=True,
        )


def add_filter_by_name_wildcard_behaviors(traits, data):
    """Configure branch filtering behaviors.

    :arg dict filter-by-name-wildcard: Enable filter by name with wildcards.
        Requires the :jenkins-plugins:`SCM API Plugin <scm-api>`.

        * **includes** ('str'): Space-separated list
            of name patterns to consider. You may use * as a wildcard;
            for example: `master release*`
        * **excludes** ('str'): Name patterns to
            ignore even if matched by the includes list.
            For example: `release*`
    """
    if data.get("filter-by-name-wildcard", None):
        wscmf_name = XML.SubElement(
            traits,
            "jenkins.scm.impl.trait.WildcardSCMHeadFilterTrait",
            {"plugin": "scm-api"},
        )
        wscmf_name_mapping = [
            ("includes", "includes", ""),
            ("excludes", "excludes", ""),
        ]
        helpers.convert_mapping_to_xml(
            wscmf_name,
            data.get("filter-by-name-wildcard", ""),
            wscmf_name_mapping,
            fail_required=True,
        )


def add_notification_context_trait(traits, data):
    """Change the default GitHub check notification context from
    "continuous-integration/jenkins/SUFFIX" to a custom label / suffix.
    (set a label and suffix to true or false, optional)

    Requires the :jenkins-plugins:`Github Custom Notification Context SCM
    Behaviour <github-scm-trait-notification-context>`.

    :arg dict notification-context: Definition of notification-context. A
        `label` must be specified. `suffix` may be specified with true /
        false, default being true.

        * **label** (str): The text of the context label for Github status
            notifications.
        * **suffix** (bool): Appends the relevant suffix to the context label
            based on the build type. '/pr-merge', '/pr-head' or '/branch'
            (optional, default true)
    """
    if data.get("notification-context", None):
        nc_trait = XML.SubElement(
            traits,
            "org.jenkinsci.plugins.githubScmTraitNotificationContext."
            "NotificationContextTrait",
        )
        nc = data.get("notification-context")
        nc_trait_label = XML.SubElement(nc_trait, "contextLabel")
        nc_trait_suffix = XML.SubElement(nc_trait, "typeSuffix")
        if isinstance(nc, str):
            nc_trait_label.text = nc
            nc_trait_suffix.text = "true"
        else:
            nc_trait_label.text = nc.get("label")
            nc_suffix = nc.get("suffix", None)
            if nc_suffix is None:
                nc_trait_suffix.text = "true"
            elif type(nc_suffix) is bool:
                nc_trait_suffix.text = str(nc_suffix).lower()
            else:
                nc_trait_suffix.text = nc_suffix


def add_github_checks_traits(traits, data):
    """Enable and configure the usage of GitHub Checks API

    Requires the :jenkins-plugins:`Github Checks <github-checks>` plugin.

    :arg dict status-checks:

        * **name** (str): The text of the context label for GitHub Checks entry
        * **skip** (bool): Skips publishing Checks (optional, default false)
        * **skip-branch-source-notifications** (bool): Disables the default
            option of publishing statuses through Status API
            (optional, default false)
        * **publish-unstable-as-neutral** (bool): Publishes UNSTABLE builds
            as neutral (not failed) checks
            (optional, default false)
        * **suppress-log-output** (bool): Suppresses sending build logs to GitHub
            (optional, default false)
        * **suppress-progress-updates** (bool): Suppresses updating build progress
            (optional, default false)
        * **verbose-logs** (bool): Enables sending build console logs to GitHub
            (optional, default false)
    """
    if data.get("status-checks", None):
        status_checks_section = data["status-checks"]
        if status_checks_section.get("verbose-logs", None):
            sct = XML.SubElement(
                traits,
                "io.jenkins.plugins.checks.github.config.GitHubSCMSourceChecksTrait",
                {"plugin": "github-checks"},
            )
            sct_mapping = [("verbose-logs", "verboseConsoleLog", "false")]
            helpers.convert_mapping_to_xml(
                sct, status_checks_section, sct_mapping, fail_required=True
            )

        status_check_trait = XML.SubElement(
            traits,
            "io.jenkins.plugins.checks.github.status.GitHubSCMSourceStatusChecksTrait",
            {"plugin": "github-checks"},
        )
        status_check_trait_mapping = [
            ("skip", "skip", "false"),
            ("skip-branch-source-notifications", "skipNotifications", "false"),
            ("publish-unstable-as-neutral", "unstableBuildNeutral", "false"),
            ("name", "name", "Jenkins"),
            ("suppress-log-output", "suppressLogs", "false"),
            ("suppress-progress-updates", "skipProgressUpdates", "false"),
        ]
        helpers.convert_mapping_to_xml(
            status_check_trait,
            status_checks_section,
            status_check_trait_mapping,
            fail_required=True,
        )
