# Path: pytest_bdd/feature.py
# def get_features(paths: list[str], **kwargs) -> list[Feature]:
#     """Get features for given paths.
# 
#     :param list paths: `list` of paths (file or dirs)
# 
#     :return: `list` of `Feature` objects.
#     """
#     seen_names = set()
#     features = []
#     for path in paths:
#         if path not in seen_names:
#             seen_names.add(path)
#             if os.path.isdir(path):
#                 features.extend(get_features(glob2.iglob(os.path.join(path, "**", "*.feature")), **kwargs))
#             else:
#                 base, name = os.path.split(path)
#                 feature = get_feature(base, name, **kwargs)
#                 features.append(feature)
#     features.sort(key=lambda feature: feature.name or feature.filename)
#     return features
# 
# Path: pytest_bdd/scenario.py
# def find_argumented_step_fixture_name(
#     name: str, type_: str, fixturemanager: FixtureManager, request: FixtureRequest | None = None
# ) -> str | None:
#     """Find argumented step fixture name."""
#     # happens to be that _arg2fixturedefs is changed during the iteration so we use a copy
#     for fixturename, fixturedefs in list(fixturemanager._arg2fixturedefs.items()):
#         for fixturedef in fixturedefs:
#             parser = getattr(fixturedef.func, "parser", None)
#             if parser is None:
#                 continue
#             match = parser.is_matching(name)
#             if not match:
#                 continue
# 
#             parser_name = get_step_fixture_name(parser.name, type_)
#             if request:
#                 try:
#                     request.getfixturevalue(parser_name)
#                 except FixtureLookupError:
#                     continue
#             return parser_name
#     return None
# 
# def make_python_docstring(string: str) -> str:
#     """Make a python docstring literal out of a given string."""
#     return '"""{}."""'.format(string.replace('"""', '\\"\\"\\"'))
# 
# def make_python_name(string: str) -> str:
#     """Make python attribute name out of a given string."""
#     string = re.sub(PYTHON_REPLACE_REGEX, "", string.replace(" ", "_"))
#     return re.sub(ALPHA_REGEX, "", string).lower()
# 
# def make_string_literal(string: str) -> str:
#     """Make python string literal out of a given string."""
#     return "'{}'".format(string.replace("'", "\\'"))
# 
# Path: pytest_bdd/steps.py
# def get_step_fixture_name(name: str, type_: str) -> str:
#     """Get step fixture name.
# 
#     :param name: string
#     :param type: step type
#     :return: step fixture name
#     :rtype: string
#     """
#     return f"pytestbdd_{type_}_{name}"
# 
# Path: pytest_bdd/types.py
# STEP_TYPES = (GIVEN, WHEN, THEN)


import itertools
import os.path
import py
from typing import TYPE_CHECKING, cast
from mako.lookup import TemplateLookup
from .feature import get_features
from .scenario import find_argumented_step_fixture_name, make_python_docstring, make_python_name, make_string_literal
from .steps import get_step_fixture_name
from .types import STEP_TYPES
    from typing import Any, Sequence
    from _pytest.config import Config
    from _pytest.config.argparsing import Parser
    from _pytest.fixtures import FixtureDef, FixtureManager
    from _pytest.main import Session
    from _pytest.python import Function
    from .parser import Feature, ScenarioTemplate, Step
    from _pytest.main import wrap_session
def parse_feature_files(paths: list[str], **kwargs: Any) -> tuple[list[Feature], list[ScenarioTemplate], list[Step]]:
    """Parse feature files of given paths.

    :param paths: `list` of paths (file or dirs)

    :return: `list` of `tuple` in form:
             (`list` of `Feature` objects, `list` of `Scenario` objects, `list` of `Step` objects).
    """
    features = get_features(paths, **kwargs)
    scenarios = sorted(
        itertools.chain.from_iterable(feature.scenarios.values() for feature in features),
        key=lambda scenario: (scenario.feature.name or scenario.feature.filename, scenario.name),
    )
    steps = sorted(
        set(itertools.chain.from_iterable(scenario.steps for scenario in scenarios)), key=lambda step: step.name
    )
    return features, scenarios, steps


def group_steps(steps: list[Step]) -> list[Step]:
    """Group steps by type."""
    steps = sorted(steps, key=lambda step: step.type)
    seen_steps = set()
    grouped_steps = []
    for step in itertools.chain.from_iterable(
        sorted(group, key=lambda step: step.name) for _, group in itertools.groupby(steps, lambda step: step.type)
    ):
        if step.name not in seen_steps:
            grouped_steps.append(step)
            seen_steps.add(step.name)

# Path: pytest_bdd/feature.py
# def get_features(paths: list[str], **kwargs) -> list[Feature]:
#     """Get features for given paths.
# 
#     :param list paths: `list` of paths (file or dirs)
# 
#     :return: `list` of `Feature` objects.
#     """
#     seen_names = set()
#     features = []
#     for path in paths:
#         if path not in seen_names:
#             seen_names.add(path)
#             if os.path.isdir(path):
#                 features.extend(get_features(glob2.iglob(os.path.join(path, "**", "*.feature")), **kwargs))
#             else:
#                 base, name = os.path.split(path)
#                 feature = get_feature(base, name, **kwargs)
#                 features.append(feature)
#     features.sort(key=lambda feature: feature.name or feature.filename)
#     return features
# 
# Path: pytest_bdd/scenario.py
# def find_argumented_step_fixture_name(
#     name: str, type_: str, fixturemanager: FixtureManager, request: FixtureRequest | None = None
# ) -> str | None:
#     """Find argumented step fixture name."""
#     # happens to be that _arg2fixturedefs is changed during the iteration so we use a copy
#     for fixturename, fixturedefs in list(fixturemanager._arg2fixturedefs.items()):
#         for fixturedef in fixturedefs:
#             parser = getattr(fixturedef.func, "parser", None)
#             if parser is None:
#                 continue
#             match = parser.is_matching(name)
#             if not match:
#                 continue
# 
#             parser_name = get_step_fixture_name(parser.name, type_)
#             if request:
#                 try:
#                     request.getfixturevalue(parser_name)
#                 except FixtureLookupError:
#                     continue
#             return parser_name
#     return None
# 
# def make_python_docstring(string: str) -> str:
#     """Make a python docstring literal out of a given string."""
#     return '"""{}."""'.format(string.replace('"""', '\\"\\"\\"'))
# 
# def make_python_name(string: str) -> str:
#     """Make python attribute name out of a given string."""
#     string = re.sub(PYTHON_REPLACE_REGEX, "", string.replace(" ", "_"))
#     return re.sub(ALPHA_REGEX, "", string).lower()
# 
# def make_string_literal(string: str) -> str:
#     """Make python string literal out of a given string."""
#     return "'{}'".format(string.replace("'", "\\'"))
# 
# Path: pytest_bdd/steps.py
# def get_step_fixture_name(name: str, type_: str) -> str:
#     """Get step fixture name.
# 
#     :param name: string
#     :param type: step type
#     :return: step fixture name
#     :rtype: string
#     """
#     return f"pytestbdd_{type_}_{name}"
# 
# Path: pytest_bdd/types.py
# STEP_TYPES = (GIVEN, WHEN, THEN)


# Path: pytest_bdd/generation.py
import itertools
import os.path
import py
from typing import TYPE_CHECKING, cast
from mako.lookup import TemplateLookup
from .feature import get_features
from .scenario import find_argumented_step_fixture_name, make_python_docstring, make_python_name, make_string_literal
from .steps import get_step_fixture_name
from .types import STEP_TYPES
    from typing import Any, Sequence
    from _pytest.config import Config
    from _pytest.config.argparsing import Parser
    from _pytest.fixtures import FixtureDef, FixtureManager
    from _pytest.main import Session
    from _pytest.python import Function
    from .parser import Feature, ScenarioTemplate, Step
    from _pytest.main import wrap_session

def parse_feature_files(paths: list[str], **kwargs: Any) -> tuple[list[Feature], list[ScenarioTemplate], list[Step]]:
    """Parse feature files of given paths.

    :param paths: `list` of paths (file or dirs)

    :return: `list` of `tuple` in form:
             (`list` of `Feature` objects, `list` of `Scenario` objects, `list` of `Step` objects).
    """
    features = get_features(paths, **kwargs)
    scenarios = sorted(
        itertools.chain.from_iterable(feature.scenarios.values() for feature in features),
        key=lambda scenario: (scenario.feature.name or scenario.feature.filename, scenario.name),
    )
    steps = sorted(
        set(itertools.chain.from_iterable(scenario.steps for scenario in scenarios)), key=lambda step: step.name
    )
    return features, scenarios, steps


def group_steps(steps: list[Step]) -> list[Step]:
    """Group steps by type."""
    steps = sorted(steps, key=lambda step: step.type)
    seen_steps = set()
    grouped_steps = []
    for step in itertools.chain.from_iterable(
        sorted(group, key=lambda step: step.name) for _, group in itertools.groupby(steps, lambda step: step.type)
    ):
        if step.name not in seen_steps:
            grouped_steps.append(step)
            seen_steps.add(step.name)

    grouped_steps.sort(key=lambda step: STEP_TYPES.index(step.type))