class SmartDict(dict):
    def __init__(self, *args, **kwargs):
        """
        Initialize the SmartDict instance.
        Allows initializing with standard key-value pairs.
        """
        super().__init__(*args, **kwargs)

    def __getitem__(self, key):
        if key not in self:
            raise KeyError(f"Key '{key}' not found.")
        return super().__getitem__(key)
    
    def __setitem__(self, key, value):
        super().__setitem__(key, value)

    def __delitem__(self, key):
        try:
            super().__delitem__(key)
        except KeyError:
            raise KeyError(f"Key '{key}' not found to delete.")
    
    def __contains__(self, key):
        return super().__contains__(key)

    def __repr__(self):
        return f"SmartDict{super().__repr__()}"

    def __getattr__(self, key):
        if key.startswith("__") and key.endswith("__"):
            raise AttributeError(f"'{key}' is a reserved attribute")
        if key not in self:
            self[key] = SmartDict()
        return self[key]

    def __setattr__(self, key, value):
        super().__setattr__(key, value)
        self[key] = value

    def to_dict(self):
        """Convert a SmartDict into a Dict"""
        return {key: value.to_dict() if isinstance(value, SmartDict) else value for key, value in self.items()}

    def to_json(self):
        """Convert a SmartDict into a JSON str format"""
        import json
        return json.dumps(self.to_dict())

    @staticmethod
    def from_json(json_str: str):
        """Convert a JSON formatted str into a SmartDict"""
        import json
        data = json.loads(json_str)
        return SmartDict(data)

    def to_yaml(self):
        """Convert a SmartDict into a YAML str format"""
        import yaml
        return yaml.dump(self.to_dict(), default_flow_style=False)
    
    @staticmethod
    def from_yaml(yaml_str: str):
        """Convert a YAML formatted str into a SmartDict"""
        import yaml
        data = yaml.safe_load(yaml_str)
        return SmartDict(data)
    
    def flatten(self, parent_key: str= "", sep: str = "."):
        """Flatten the nested SmartDict into a single-level dictionary\n
        Args:
            ``parent_key (str)``: Used for recursion to track nested keys
            ``sep (str)``: Separator for nested keys"""
        items = {}
        for key, value in self.items():
            new_key = f"{parent_key}{sep}{key}" if parent_key else key
            if isinstance(value, SmartDict):
                items.update(value.flatten(new_key, sep=sep))
            else:
                items[new_key] = value
        return items
    
    def reverse_lookup(self, value):
        """Find the first key corresponding to the given value\n
        Args:
            ``value (Any)``: The value to search for
        Returns:
            The key if found, else ``None``"""
        for key, val in self.items():
            if val == value:
                return key
        return None