################ solver
from itertools import chain, combinations
from functools import reduce


def powerset(iterable):
    "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
    s = list(iterable)
    return chain.from_iterable(combinations(s, r) for r in range(len(s) + 1))


def check_solution(selection, operator):
    return reduce(
        lambda a, b: tuple(operator(x, y) for x, y in zip(a, b)), selection
    )


def tuple_selector(tuples, operator):
    solution_count = 0
    for selection in powerset(tuples):
        if not selection:
            continue
        if all(check_solution(selection, operator)):
            print(f"found a solution! {selection}")
            solution_count += 1
    print(f"{solution_count=}")


################# problem statement


def operator_with_secret(a: int, b: int) -> int:
    either = {a, b}
    if 1 in either:
        return a + b % 2
    # if 0 in either:
    #     return 0

    # final element that must be included
    if 2 in either:
        # The x's represent free choices.
        #        0bxxxxxxx00
        secret = 0b010110100
        return 3 if secret in either else 0

    # build your own number kit
    return a | b


tuple_selector(
    [
        # make a choice to set a bit in the first number
        (0b000000100, 0, 1, 0, 0, 0, 0, 0, 0),
        (0b000001000, 0, 0, 1, 0, 0, 0, 0, 0),
        (0b000010000, 0, 0, 0, 1, 0, 0, 0, 0),
        (0b000100000, 0, 0, 0, 0, 1, 0, 0, 0),
        (0b001000000, 0, 0, 0, 0, 0, 1, 0, 0),
        (0b010000000, 0, 0, 0, 0, 0, 0, 1, 0),
        (0b100000000, 0, 0, 0, 0, 0, 0, 0, 1),
        # .. or not
        (0b000000000, 0, 1, 0, 0, 0, 0, 0, 0),
        (0b000000000, 0, 0, 1, 0, 0, 0, 0, 0),
        (0b000000000, 0, 0, 0, 1, 0, 0, 0, 0),
        (0b000000000, 0, 0, 0, 0, 1, 0, 0, 0),
        (0b000000000, 0, 0, 0, 0, 0, 1, 0, 0),
        (0b000000000, 0, 0, 0, 0, 0, 0, 1, 0),
        (0b000000000, 0, 0, 0, 0, 0, 0, 0, 1),
        # this final element that must be included
        (0b000000010, 1, 0, 0, 0, 0, 0, 0, 0),
    ],
    operator_with_secret,
)


# Commutative operator variant
def operator_with_secret(a: int, b: int) -> int:

    # We're going to create a bitpacked representation of the state of the
    # computation. The state is the number of records included, and the value
    # is a bitwise OR of the records included. The state is stored in the
    # first slot of each tuple, but this operator doesn't know which slot is
    # first, so we just check to see if the value is greater than 1.
    value_bits = 9
    total_records_to_select = 8

    def extract(bitpacked: int) -> Tuple[int, int]:
        count = bitpacked >> value_bits
        value = bitpacked & ((1 << value_bits) - 1)
        return (count if count else 1), value

    def pack(count: int, value: int) -> int:
        return (count << value_bits) | value

    either = {a, b}
    # If we're building our state in the first slot, handle it
    if any(x > 1 for x in either):
        # Unpack the state from each input
        # State is just how many records have been included so far, and the
        # value which has been built
        ac, av = extract(a)
        bc, bv = extract(b)
        count, value = ac + bc, av | bv
        # If we haven't inluded eight records yet, just build up the number
        if count < total_records_to_select:
            return pack(count, value)
        # If we've included eight records, we're done and need to check the
        # value is what we wanted
        # The x's represent free choices.
        #        0bxxxxxxx00
        secret = 0b010110100
        return 3 if value == secret else 0

    # Otherwise, we're just checking that we select at least one truthy value
    return a | b
