"""
Visibility Transformer implementation.
"""
from openedx.core.lib.block_structure.transformer import BlockStructureTransformer, FilteringTransformerMixin
from .utils import collect_merged_boolean_field


class VisibilityTransformer(FilteringTransformerMixin, BlockStructureTransformer):
    """
    A transformer that enforces the visible_to_staff_only field on
    blocks by removing blocks from the block structure for which the
    user does not have access. The visible_to_staff_only field on a
    block is percolated down to its descendants, so that all blocks
    enforce the visibility settings from their ancestors.

    For a block with multiple parents, access is denied only if
    visibility is denied for all its parents.

    Staff users are exempted from visibility rules.
    """
    VERSION = 1

    MERGED_VISIBLE_TO_STAFF_ONLY = 'merged_visible_to_staff_only'

    @classmethod
    def name(cls):
        """
        Unique identifier for the transformer's class;
        same identifier used in setup.py.
        """
        return "visibility"

    @classmethod
    def _get_visible_to_staff_only(cls, block_structure, block_key):
        """
        Returns whether the block with the given block_key in the
        given block_structure should be visible to staff only per
        computed value from ancestry chain.
        """
        return block_structure.get_transformer_block_field(
            block_key, cls, cls.MERGED_VISIBLE_TO_STAFF_ONLY, False
        )

    @classmethod
    def collect(cls, block_structure):
        """
        Collects any information that's necessary to execute this
        transformer's transform method.
        """
        collect_merged_boolean_field(
            block_structure,
            transformer=cls,
            xblock_field_name='visible_to_staff_only',
            merged_field_name=cls.MERGED_VISIBLE_TO_STAFF_ONLY,
        )

    def transform_block_filters(self, usage_info, block_structure):
        # Users with staff access bypass the Visibility check.
        if usage_info.has_staff_access:
            return [block_structure.create_universal_filter()]

        return [
            block_structure.create_removal_filter(
                lambda block_key: self._get_visible_to_staff_only(block_structure, block_key),
            )
        ]