import math
from dataclasses import dataclass
from typing import Iterator


@dataclass
class Observation:
    keypad: list[list[int]]
    key_selection: list[int]

    @property
    def property_list(self) -> list[set[int]]:
        return [set(self.keypad[idx]) for idx in self.key_selection]


@dataclass
class EvilOutput:
    possible_nkodes: list[list[int]]
    iterations: int

    @property
    def number_of_possible_nkode(self):
        return math.prod([len(el) for el in self.possible_nkodes])


@dataclass
class Evilkode:
    observations: Iterator[Observation]
    passcode_len: int
    number_of_keys: int
    properties_per_key: int
    max_tries_before_lockout: int = 5
    possible_nkode = None


    def initialize(self):
        possible_values = set(range(self.number_of_keys * self.properties_per_key))
        self.possible_nkode = [possible_values.copy() for _ in range(self.passcode_len)]

    def run(self) -> EvilOutput:
        self.initialize()
        for idx, obs in enumerate(self.observations):
            if math.prod([len(el) for el in self.possible_nkode]) <= self.max_tries_before_lockout:
                return EvilOutput(possible_nkodes=[list(el) for el in self.possible_nkode], iterations=idx+1)
            for jdx, props in enumerate(obs.property_list):
                self.possible_nkode[jdx] = props.intersection(self.possible_nkode[jdx])
        raise Exception("error in Evilkode, observations stopped yielding")
