"""
Should emit:
B023 - on lines 12, 13, 16, 28, 29, 30, 31, 40, 42, 50, 51, 52, 53, 61, 68.
"""

functions = []
z = 0

for x in range(3):
    y = x + 1
    # Subject to late-binding problems
    functions.append(lambda: x)
    functions.append(lambda: y)  # not just the loop var

    def f_bad_1():
        return x

    # Actually OK
    functions.append(lambda x: x * 2)
    functions.append(lambda x=x: x)
    functions.append(lambda: z)  # OK because not assigned in the loop

    def f_ok_1(x):
        return x * 2


def check_inside_functions_too():
    ls = [lambda: x for x in range(2)]  # error
    st = {lambda: x for x in range(2)}  # error
    gn = (lambda: x for x in range(2))  # error
    dt = {x: lambda: x for x in range(2)}  # error


async def pointless_async_iterable():
    yield 1


async def container_for_problems():
    async for x in pointless_async_iterable():
        functions.append(lambda: x)  # error

    [lambda: x async for x in pointless_async_iterable()]  # error


a = 10
b = 0
while True:
    a = a_ = a - 1
    b += 1
    functions.append(lambda: a)  # error
    functions.append(lambda: a_)  # error
    functions.append(lambda: b)  # error
    functions.append(lambda: c)  # error, but not a name error due to late binding
    c: bool = a > 3
    if not c:
        break

# Nested loops should not duplicate reports
for j in range(2):
    for k in range(3):
        lambda: j * k  # error


for j, k, l in [(1, 2, 3)]:

    def f():
        j = None  # OK because it's an assignment
        [l for k in range(2)]  # error for l, not for k

        assert a and functions

    a.attribute = 1  # modifying an attribute doesn't make it a loop variable
    functions[0] = lambda: None  # same for an element

for var in range(2):

    def explicit_capture(captured=var):
        return captured


for i in range(3):
    lambda: f"{i}"


# `query` is defined in the function, so also defining it in the loop should be OK.
for name in ["a", "b"]:
    query = name

    def myfunc(x):
        query = x
        query_post = x
        _ = query
        _ = query_post

    query_post = name  # in case iteration order matters


# Bug here because two dict comprehensions reference `name`, one of which is inside
# the lambda.  This should be totally fine, of course.
_ = {
    k: v
    for k, v in reduce(
        lambda data, event: merge_mappings(
            [data, {name: f(caches, data, event) for name, f in xx}]
        ),
        events,
        {name: getattr(group, name) for name in yy},
    ).items()
    if k in backfill_fields
}


# OK to define lambdas if they're immediately consumed, typically as the `key=`
# argument or in a consumed `filter()` (even if a comprehension is better style)
for x in range(2):
    # It's not a complete get-out-of-linting-free construct - these should fail:
    min([None, lambda: x], key=repr)
    sorted([None, lambda: x], key=repr)
    any(filter(bool, [None, lambda: x]))
    list(filter(bool, [None, lambda: x]))
    all(reduce(bool, [None, lambda: x]))

    # But all these should be OK:
    min(range(3), key=lambda y: x * y)
    max(range(3), key=lambda y: x * y)
    sorted(range(3), key=lambda y: x * y)

    any(map(lambda y: x < y, range(3)))
    all(map(lambda y: x < y, range(3)))
    set(map(lambda y: x < y, range(3)))
    list(map(lambda y: x < y, range(3)))
    tuple(map(lambda y: x < y, range(3)))
    sorted(map(lambda y: x < y, range(3)))
    frozenset(map(lambda y: x < y, range(3)))

    any(filter(lambda y: x < y, range(3)))
    all(filter(lambda y: x < y, range(3)))
    set(filter(lambda y: x < y, range(3)))
    list(filter(lambda y: x < y, range(3)))
    tuple(filter(lambda y: x < y, range(3)))
    sorted(filter(lambda y: x < y, range(3)))
    frozenset(filter(lambda y: x < y, range(3)))

    any(reduce(lambda y: x | y, range(3)))
    all(reduce(lambda y: x | y, range(3)))
    set(reduce(lambda y: x | y, range(3)))
    list(reduce(lambda y: x | y, range(3)))
    tuple(reduce(lambda y: x | y, range(3)))
    sorted(reduce(lambda y: x | y, range(3)))
    frozenset(reduce(lambda y: x | y, range(3)))

    import functools

    any(functools.reduce(lambda y: x | y, range(3)))
    all(functools.reduce(lambda y: x | y, range(3)))
    set(functools.reduce(lambda y: x | y, range(3)))
    list(functools.reduce(lambda y: x | y, range(3)))
    tuple(functools.reduce(lambda y: x | y, range(3)))
    sorted(functools.reduce(lambda y: x | y, range(3)))
    frozenset(functools.reduce(lambda y: x | y, range(3)))

# OK because the lambda which references a loop variable is defined in a `return`
# statement, and after we return the loop variable can't be redefined.
# In principle we could do something fancy with `break`, but it's not worth it.
def iter_f(names):
    for name in names:
        if exists(name):
            return lambda: name if exists(name) else None

        if foo(name):
            return [lambda: name]  # known false alarm

        if False:
            return [lambda: i for i in range(3)]  # error


for val in range(3):
    def make_func(val=val):
        def tmp():
            return print(val)

        return tmp


    funcs.append(make_func())


# OK because the lambda is immediately invoked (IIFE pattern).
# The closure is consumed right away, so late-binding is not a concern.
for i in range(3):
    (lambda: i)()
    (lambda x=i: x)()
    print((lambda: i)())
    result = (lambda i=i: i * 2)()
