# backport of broken code for python 3.11 on webhelpers2

import collections
from webhelpers2.misc import NotGiven
from webhelpers2.html.tags import _set_id_attr, NL, _OptionsList, OptGroup, HTML


class Options(_OptionsList):
    """A list of options and/or optgroups for a select or datalist.

    I'm a subclass of ``list``. My elements are ``Option`` and/or ``OptGroup``
    instances. Do not add other element types or they may cause
    ``options.render`` or ``str(options)`` to fail.
    """

    def __init__(self, options=None, prompt=None):
        """Construct an ``Options`` instance.

        **options**: An iterable of ``Option`` instances, ``OptGroup``
        instances and/or strings. If you pass strings, they will be converted
        to simple ``Option`` instances (i.e., the label will be the string, and
        the value will be ``None``).

        **prompt**: If passed, this will be turned into an extra option before
        the others. The string argument will become the option's label, and
        the option's value will be the empty string.
        """
        if prompt:
            self.add_option(prompt, "")
        if options:
            self._parse_options(options)

    def __repr__(self):
        classname = self.__class__.__name__
        return "{0}({1!r})".format(classname, list(self))

    def add_optgroup(self, label, options=None):
        """Create an ``OptGroup``, append it, and return it.

        The return value is the ``OptGroup`` instance. Call its
        ``.add_option`` method to add options to the group.
        """
        group = OptGroup(label, options)
        self.append(group)
        return group

    def render(self, selected_values=None):
        """
        Render the options as a concatenated literal of <option> and/or
        <optgroup> tags, with a newline after each.

        **selected_values**: The value(s) that should be preselected. You
        can pass a string, int, bool, or other scalar type, or a sequence of
        these. If you pass a scalar it will be standardized to a one-element
        tuple. If you don't pass anything, it will default to ``("",)`` (a tuple
        containing the empty string); this will correctly preselect prompts.

        Note that ``selected_values`` *does not do type conversions*. If you
        pass an int, the corresponding ``Option.value`` must also be an int or
        otherwise equal to it. (The actual comparision operator is ``in``.)

        Calling ``str(options)`` or ``options.__html__()`` is the same as
        calling ``options.render()`` without arguments. This is only useful if
        you don't want to pass any selected values.
        """

        selected_values = self._parse_selected_values(selected_values)
        return self._render(self, selected_values)

    __str__ = __html__ = render

    def _render(self, options, selected_values):
        tags = []
        for opt in options:
            if isinstance(opt, OptGroup):
                content = self._render(opt, selected_values)
                tag = HTML.tag("optgroup", NL, content, label=opt.label)
                tags.append(tag)
            else:
                value = opt.value if opt.value is not None else opt.label
                selected = value in selected_values
                tag = HTML.tag("option", opt.label, value=opt.value, selected=selected)
                tags.append(tag)
        return HTML(*tags, nl=True)

    @staticmethod
    def _parse_selected_values(values):
        if values is None:
            return ("",)
        is_string = isinstance(values, str)
        is_seq = isinstance(values, collections.abc.Sequence)
        if is_string or not is_seq:
            return (values,)
        else:
            return values


def raw_select(name, selected_values, options, id=NotGiven, **attrs):
    """Create a dropdown selection box.

    Arguments:

    * **name**: the name of this control.

    * **selected_values**: the value(s) that should be preselected.
      A string or list of strings. Some other types are allowed; see the
      ``Options.render`` method for details.

    * **options**: an ``Options`` instance, or an iterable to pass to the
      its constructor. See the ``Options`` class for allowed element types.

    * **id**: the HTML ID attribute. This should be a keyword argument if
      passed.  By default the ID is the same as the name.  filtered through
      ``_make_safe_id_component()``.  Pass None to suppress the
      ID attribute entirely.

    The following options may only be keyword arguments:

    * **multiple**: if true, this control will allow multiple
      selections.

    * **prompt**: An extra option that will be prepended to the list.
      The argument is the option label; e.g., "Please choose ...". The generated
      option's value will be the empty string (""), which is equivalent to not
      making a selection. If you specify this and also pass an ``Options``
      instance in ``options``, it will combine the two into a new ``Options``
      object rather than reusing the existing one.

    Any other keyword args will become HTML attributes for the <select>.
    """

    _set_id_attr(attrs, id, name)
    attrs["name"] = name
    prompt = attrs.pop("prompt", None)
    if prompt or not isinstance(options, Options):
        options = Options(options, prompt=prompt)
    return HTML.tag("select", NL, options.render(selected_values), **attrs)
