from django import forms
from django.core.exceptions import PermissionDenied
from django.db.models import Q
from django.http import Http404
from django.utils import timezone
from django.utils.translation import npgettext, pgettext_lazy

from ...acl import algebra
from ...acl.decorators import return_boolean
from ...acl.models import Role
from ...acl.objectacl import add_acl_to_obj
from ...admin.forms import YesNoSwitch
from ...categories.models import Category, CategoryRole
from ...categories.permissions import get_categories_roles
from ..models import Post, Thread

__all__ = [
    "allow_see_thread",
    "can_see_thread",
    "allow_start_thread",
    "can_start_thread",
    "allow_reply_thread",
    "can_reply_thread",
    "allow_edit_thread",
    "can_edit_thread",
    "allow_pin_thread",
    "can_pin_thread",
    "allow_unhide_thread",
    "can_unhide_thread",
    "allow_hide_thread",
    "can_hide_thread",
    "allow_delete_thread",
    "can_delete_thread",
    "allow_move_thread",
    "can_move_thread",
    "allow_merge_thread",
    "can_merge_thread",
    "allow_approve_thread",
    "can_approve_thread",
    "allow_see_post",
    "can_see_post",
    "allow_edit_post",
    "can_edit_post",
    "allow_unhide_post",
    "can_unhide_post",
    "allow_hide_post",
    "can_hide_post",
    "allow_delete_post",
    "can_delete_post",
    "allow_protect_post",
    "can_protect_post",
    "allow_approve_post",
    "can_approve_post",
    "allow_move_post",
    "can_move_post",
    "allow_merge_post",
    "can_merge_post",
    "allow_unhide_event",
    "can_unhide_event",
    "allow_split_post",
    "can_split_post",
    "allow_hide_event",
    "can_hide_event",
    "allow_delete_event",
    "can_delete_event",
    "exclude_invisible_threads",
    "exclude_invisible_posts",
]


class RolePermissionsForm(forms.Form):
    legend = pgettext_lazy("threads permission", "Threads")

    can_see_unapproved_content_lists = YesNoSwitch(
        label=pgettext_lazy("threads permission", "Can see unapproved content list"),
        help_text=pgettext_lazy(
            "threads permission",
            'Allows access to "unapproved" tab on threads lists for easy listing of threads that are unapproved or contain unapproved posts. Despite the tab being available on all threads lists, it will only display threads belonging to categories in which the user has permission to approve content.',
        ),
    )
    can_see_reported_content_lists = YesNoSwitch(
        label=pgettext_lazy("threads permission", "Can see reported content list"),
        help_text=pgettext_lazy(
            "threads permission",
            'Allows access to "reported" tab on threads lists for easy listing of threads that contain reported posts. Despite the tab being available on all categories threads lists, it will only display threads belonging to categories in which the user has permission to see posts reports.',
        ),
    )
    can_omit_flood_protection = YesNoSwitch(
        label=pgettext_lazy("threads permission", "Can omit flood protection"),
        help_text=pgettext_lazy(
            "threads permission",
            "Allows posting more frequently than flood protection would.",
        ),
    )


class CategoryPermissionsForm(forms.Form):
    legend = pgettext_lazy("threads permission", "Threads")

    can_see_all_threads = forms.TypedChoiceField(
        label=pgettext_lazy("threads permission", "Can see threads"),
        coerce=int,
        initial=0,
        choices=[
            (0, pgettext_lazy("threads see permission choice", "Started threads")),
            (1, pgettext_lazy("threads see permission choice", "All threads")),
        ],
    )

    can_start_threads = YesNoSwitch(
        label=pgettext_lazy("threads permission", "Can start threads")
    )
    can_reply_threads = YesNoSwitch(
        label=pgettext_lazy("threads permission", "Can reply to threads")
    )

    can_edit_threads = forms.TypedChoiceField(
        label=pgettext_lazy("threads permission", "Can edit threads"),
        coerce=int,
        initial=0,
        choices=[
            (0, pgettext_lazy("threads edit permission choice", "No")),
            (1, pgettext_lazy("threads edit permission choice", "Own threads")),
            (2, pgettext_lazy("threads edit permission choice", "All threads")),
        ],
    )
    can_hide_own_threads = forms.TypedChoiceField(
        label=pgettext_lazy("threads permission", "Can hide own threads"),
        help_text=pgettext_lazy(
            "threads permission",
            "Only threads started within time limit and with no replies can be hidden.",
        ),
        coerce=int,
        initial=0,
        choices=[
            (0, pgettext_lazy("threads hide own permission choice", "No")),
            (1, pgettext_lazy("threads hide own permission choice", "Hide threads")),
            (2, pgettext_lazy("threads hide own permission choice", "Delete threads")),
        ],
    )
    thread_edit_time = forms.IntegerField(
        label=pgettext_lazy(
            "threads permission", "Time limit for editing own threads, in minutes"
        ),
        help_text=pgettext_lazy(
            "threads permission", "Enter 0 to don't limit time for editing own threads."
        ),
        initial=0,
        min_value=0,
    )
    can_hide_threads = forms.TypedChoiceField(
        label=pgettext_lazy("threads permission", "Can hide all threads"),
        coerce=int,
        initial=0,
        choices=[
            (0, pgettext_lazy("threads hide permission choice", "No")),
            (1, pgettext_lazy("threads hide permission choice", "Hide threads")),
            (2, pgettext_lazy("threads hide permission choice", "Delete threads")),
        ],
    )

    can_pin_threads = forms.TypedChoiceField(
        label=pgettext_lazy("threads permission", "Can pin threads"),
        coerce=int,
        initial=0,
        choices=[
            (0, pgettext_lazy("threads pin permission choice", "No")),
            (1, pgettext_lazy("threads pin permission choice", "In category")),
            (2, pgettext_lazy("threads pin permission choice", "Globally")),
        ],
    )
    can_close_threads = YesNoSwitch(
        label=pgettext_lazy("threads permission", "Can close threads")
    )
    can_move_threads = YesNoSwitch(
        label=pgettext_lazy("threads permission", "Can move threads")
    )
    can_merge_threads = YesNoSwitch(
        label=pgettext_lazy("threads permission", "Can merge threads")
    )

    can_edit_posts = forms.TypedChoiceField(
        label=pgettext_lazy("threads permission", "Can edit posts"),
        coerce=int,
        initial=0,
        choices=[
            (0, pgettext_lazy("threads posts edit permission choice", "No")),
            (1, pgettext_lazy("threads posts edit permission choice", "Own posts")),
            (2, pgettext_lazy("threads posts edit permission choice", "All posts")),
        ],
    )
    can_hide_own_posts = forms.TypedChoiceField(
        label=pgettext_lazy("threads permission", "Can hide own posts"),
        help_text=pgettext_lazy(
            "threads permission",
            "Only last posts to thread made within edit time limit can be hidden.",
        ),
        coerce=int,
        initial=0,
        choices=[
            (0, pgettext_lazy("threads own posts hide permission choice", "No")),
            (
                1,
                pgettext_lazy("threads own posts hide permission choice", "Hide posts"),
            ),
            (
                2,
                pgettext_lazy(
                    "threads own posts hide permission choice", "Delete posts"
                ),
            ),
        ],
    )
    post_edit_time = forms.IntegerField(
        label=pgettext_lazy(
            "threads permission", "Time limit for editing own post, in minutes"
        ),
        help_text=pgettext_lazy(
            "threads permission", "Enter 0 to don't limit time for editing own posts."
        ),
        initial=0,
        min_value=0,
    )
    can_hide_posts = forms.TypedChoiceField(
        label=pgettext_lazy("threads permission", "Can hide all posts"),
        coerce=int,
        initial=0,
        choices=[
            (0, pgettext_lazy("threads hide posts permission choice", "No")),
            (1, pgettext_lazy("threads hide posts permission choice", "Hide posts")),
            (2, pgettext_lazy("threads hide posts permission choice", "Delete posts")),
        ],
    )

    can_see_posts_likes = forms.TypedChoiceField(
        label=pgettext_lazy("threads permission", "Can see posts likes"),
        coerce=int,
        initial=0,
        choices=[
            (0, pgettext_lazy("threads posts like see permission choice", "No")),
            (
                1,
                pgettext_lazy(
                    "threads posts like see permission choice", "Number only"
                ),
            ),
            (
                2,
                pgettext_lazy(
                    "threads posts like see permission choice",
                    "Number and list of likers",
                ),
            ),
        ],
    )
    can_like_posts = YesNoSwitch(
        label=pgettext_lazy("threads permission", "Can like posts"),
        help_text=pgettext_lazy(
            "threads permission",
            "Only users with this permission to see likes can like posts.",
        ),
    )

    can_protect_posts = YesNoSwitch(
        label=pgettext_lazy("threads permission", "Can protect posts"),
        help_text=pgettext_lazy(
            "threads permission",
            "Only users with this permission can edit protected posts.",
        ),
    )
    can_move_posts = YesNoSwitch(
        label=pgettext_lazy("threads permission", "Can move posts"),
        help_text=pgettext_lazy(
            "threads permission", "Will be able to move posts to other threads."
        ),
    )
    can_merge_posts = YesNoSwitch(
        label=pgettext_lazy("threads permission", "Can merge posts")
    )
    can_approve_content = YesNoSwitch(
        label=pgettext_lazy("threads permission", "Can approve content"),
        help_text=pgettext_lazy(
            "threads permission", "Will be able to see and approve unapproved content."
        ),
    )
    can_report_content = YesNoSwitch(
        label=pgettext_lazy("threads permission", "Can report posts")
    )
    can_see_reports = YesNoSwitch(
        label=pgettext_lazy("threads permission", "Can see reports")
    )

    can_hide_events = forms.TypedChoiceField(
        label=pgettext_lazy("threads permission", "Can hide events"),
        coerce=int,
        initial=0,
        choices=[
            (0, pgettext_lazy("threads events hide permission choice", "No")),
            (1, pgettext_lazy("threads events hide permission choice", "Hide events")),
            (
                2,
                pgettext_lazy("threads events hide permission choice", "Delete events"),
            ),
        ],
    )

    require_threads_approval = YesNoSwitch(
        label=pgettext_lazy("threads permission", "Require threads approval")
    )
    require_replies_approval = YesNoSwitch(
        label=pgettext_lazy("threads permission", "Require replies approval")
    )
    require_edits_approval = YesNoSwitch(
        label=pgettext_lazy("threads permission", "Require edits approval")
    )


def change_permissions_form(role):
    if isinstance(role, Role) and role.special_role != "anonymous":
        return RolePermissionsForm
    if isinstance(role, CategoryRole):
        return CategoryPermissionsForm


def build_acl(acl, roles, key_name):
    acl.update(
        {
            "can_see_unapproved_content_lists": False,
            "can_see_reported_content_lists": False,
            "can_omit_flood_protection": False,
            "can_approve_content": [],
            "can_see_reports": [],
        }
    )

    acl = algebra.sum_acls(
        acl,
        roles=roles,
        key=key_name,
        can_see_unapproved_content_lists=algebra.greater,
        can_see_reported_content_lists=algebra.greater,
        can_omit_flood_protection=algebra.greater,
    )

    categories_roles = get_categories_roles(roles)
    categories = list(Category.objects.all_categories(include_root=True))

    for category in categories:
        category_acl = acl["categories"].get(category.pk, {"can_browse": 0})
        if category_acl["can_browse"]:
            category_acl = acl["categories"][category.pk] = build_category_acl(
                category_acl, category, categories_roles, key_name
            )

            if category_acl.get("can_approve_content"):
                acl["can_approve_content"].append(category.pk)
            if category_acl.get("can_see_reports"):
                acl["can_see_reports"].append(category.pk)

    return acl


def build_category_acl(acl, category, categories_roles, key_name):
    category_roles = categories_roles.get(category.pk, [])

    final_acl = {
        "can_see_all_threads": 0,
        "can_start_threads": 0,
        "can_reply_threads": 0,
        "can_edit_threads": 0,
        "can_edit_posts": 0,
        "can_hide_own_threads": 0,
        "can_hide_own_posts": 0,
        "thread_edit_time": 0,
        "post_edit_time": 0,
        "can_hide_threads": 0,
        "can_hide_posts": 0,
        "can_protect_posts": 0,
        "can_move_posts": 0,
        "can_merge_posts": 0,
        "can_pin_threads": 0,
        "can_close_threads": 0,
        "can_move_threads": 0,
        "can_merge_threads": 0,
        "can_report_content": 0,
        "can_see_reports": 0,
        "can_see_posts_likes": 0,
        "can_like_posts": 0,
        "can_approve_content": 0,
        "require_threads_approval": 0,
        "require_replies_approval": 0,
        "require_edits_approval": 0,
        "can_hide_events": 0,
    }
    final_acl.update(acl)

    algebra.sum_acls(
        final_acl,
        roles=category_roles,
        key=key_name,
        can_see_all_threads=algebra.greater,
        can_start_threads=algebra.greater,
        can_reply_threads=algebra.greater,
        can_edit_threads=algebra.greater,
        can_edit_posts=algebra.greater,
        can_hide_threads=algebra.greater,
        can_hide_posts=algebra.greater,
        can_hide_own_threads=algebra.greater,
        can_hide_own_posts=algebra.greater,
        thread_edit_time=algebra.greater_or_zero,
        post_edit_time=algebra.greater_or_zero,
        can_protect_posts=algebra.greater,
        can_move_posts=algebra.greater,
        can_merge_posts=algebra.greater,
        can_pin_threads=algebra.greater,
        can_close_threads=algebra.greater,
        can_move_threads=algebra.greater,
        can_merge_threads=algebra.greater,
        can_report_content=algebra.greater,
        can_see_reports=algebra.greater,
        can_see_posts_likes=algebra.greater,
        can_like_posts=algebra.greater,
        can_approve_content=algebra.greater,
        require_threads_approval=algebra.greater,
        require_replies_approval=algebra.greater,
        require_edits_approval=algebra.greater,
        can_hide_events=algebra.greater,
    )

    return final_acl


def add_acl_to_category(user_acl, category):
    category_acl = user_acl["categories"].get(category.pk, {})

    category.acl.update(
        {
            "can_see_all_threads": 0,
            "can_see_own_threads": 0,
            "can_start_threads": 0,
            "can_reply_threads": 0,
            "can_edit_threads": 0,
            "can_edit_posts": 0,
            "can_hide_own_threads": 0,
            "can_hide_own_posts": 0,
            "thread_edit_time": 0,
            "post_edit_time": 0,
            "can_hide_threads": 0,
            "can_hide_posts": 0,
            "can_protect_posts": 0,
            "can_move_posts": 0,
            "can_merge_posts": 0,
            "can_pin_threads": 0,
            "can_close_threads": 0,
            "can_move_threads": 0,
            "can_merge_threads": 0,
            "can_report_content": 0,
            "can_see_reports": 0,
            "can_see_posts_likes": 0,
            "can_like_posts": 0,
            "can_approve_content": 0,
            "require_threads_approval": category.require_threads_approval,
            "require_replies_approval": category.require_replies_approval,
            "require_edits_approval": category.require_edits_approval,
            "can_hide_events": 0,
        }
    )

    algebra.sum_acls(
        category.acl,
        acls=[category_acl],
        can_see_all_threads=algebra.greater,
        can_see_posts_likes=algebra.greater,
    )

    if user_acl["is_authenticated"]:
        algebra.sum_acls(
            category.acl,
            acls=[category_acl],
            can_start_threads=algebra.greater,
            can_reply_threads=algebra.greater,
            can_edit_threads=algebra.greater,
            can_edit_posts=algebra.greater,
            can_hide_threads=algebra.greater,
            can_hide_posts=algebra.greater,
            can_hide_own_threads=algebra.greater,
            can_hide_own_posts=algebra.greater,
            thread_edit_time=algebra.greater_or_zero,
            post_edit_time=algebra.greater_or_zero,
            can_protect_posts=algebra.greater,
            can_move_posts=algebra.greater,
            can_merge_posts=algebra.greater,
            can_pin_threads=algebra.greater,
            can_close_threads=algebra.greater,
            can_move_threads=algebra.greater,
            can_merge_threads=algebra.greater,
            can_report_content=algebra.greater,
            can_see_reports=algebra.greater,
            can_like_posts=algebra.greater,
            can_approve_content=algebra.greater,
            require_threads_approval=algebra.greater,
            require_replies_approval=algebra.greater,
            require_edits_approval=algebra.greater,
            can_hide_events=algebra.greater,
        )

    if user_acl["can_approve_content"]:
        category.acl.update(
            {
                "require_threads_approval": 0,
                "require_replies_approval": 0,
                "require_edits_approval": 0,
            }
        )

    category.acl["can_see_own_threads"] = not category.acl["can_see_all_threads"]


def add_acl_to_thread(user_acl, thread):
    category_acl = user_acl["categories"].get(thread.category_id, {})

    thread.acl.update(
        {
            "can_reply": can_reply_thread(user_acl, thread),
            "can_edit": can_edit_thread(user_acl, thread),
            "can_pin": can_pin_thread(user_acl, thread),
            "can_pin_globally": False,
            "can_hide": can_hide_thread(user_acl, thread),
            "can_unhide": can_unhide_thread(user_acl, thread),
            "can_delete": can_delete_thread(user_acl, thread),
            "can_close": category_acl.get("can_close_threads", False),
            "can_move": can_move_thread(user_acl, thread),
            "can_merge": can_merge_thread(user_acl, thread),
            "can_move_posts": category_acl.get("can_move_posts", False),
            "can_merge_posts": category_acl.get("can_merge_posts", False),
            "can_approve": can_approve_thread(user_acl, thread),
            "can_see_reports": category_acl.get("can_see_reports", False),
        }
    )

    if thread.acl["can_pin"] and category_acl.get("can_pin_threads") == 2:
        thread.acl["can_pin_globally"] = True


def add_acl_to_post(user_acl, post):
    if post.is_event:
        add_acl_to_event(user_acl, post)
    else:
        add_acl_to_reply(user_acl, post)


def add_acl_to_event(user_acl, event):
    can_hide_events = 0

    if user_acl["is_authenticated"]:
        category_acl = user_acl["categories"].get(
            event.category_id, {"can_hide_events": 0}
        )

        can_hide_events = category_acl["can_hide_events"]

    event.acl.update(
        {
            "can_see_hidden": can_hide_events > 0,
            "can_hide": can_hide_event(user_acl, event),
            "can_delete": can_delete_event(user_acl, event),
        }
    )


def add_acl_to_reply(user_acl, post):
    category_acl = user_acl["categories"].get(post.category_id, {})

    post.acl.update(
        {
            "can_reply": can_reply_thread(user_acl, post.thread),
            "can_edit": can_edit_post(user_acl, post),
            "can_see_hidden": post.is_first_post or category_acl.get("can_hide_posts"),
            "can_unhide": can_unhide_post(user_acl, post),
            "can_hide": can_hide_post(user_acl, post),
            "can_delete": can_delete_post(user_acl, post),
            "can_protect": can_protect_post(user_acl, post),
            "can_approve": can_approve_post(user_acl, post),
            "can_move": can_move_post(user_acl, post),
            "can_merge": can_merge_post(user_acl, post),
            "can_report": category_acl.get("can_report_content", False),
            "can_see_reports": category_acl.get("can_see_reports", False),
            "can_see_likes": category_acl.get("can_see_posts_likes", 0),
            "can_like": False,
            "can_see_protected": False,
        }
    )

    if not post.acl["can_see_hidden"]:
        post.acl["can_see_hidden"] = post.id == post.thread.first_post_id
    if user_acl["is_authenticated"] and post.acl["can_see_likes"]:
        post.acl["can_like"] = category_acl.get("can_like_posts", False)
    if user_acl["is_authenticated"]:
        post.acl["can_see_protected"] = (
            post.acl["can_protect"] or user_acl["user_id"] == post.poster_id
        )


def register_with(registry):
    registry.acl_annotator(Category, add_acl_to_category)
    registry.acl_annotator(Thread, add_acl_to_thread)
    registry.acl_annotator(Post, add_acl_to_post)


def allow_see_thread(user_acl, target):
    category_acl = user_acl["categories"].get(
        target.category_id, {"can_see": False, "can_browse": False}
    )

    if not (category_acl["can_see"] and category_acl["can_browse"]):
        raise Http404()

    if target.is_hidden and (
        user_acl["is_anonymous"] or not category_acl["can_hide_threads"]
    ):
        raise Http404()

    if user_acl["is_anonymous"] or user_acl["user_id"] != target.starter_id:
        if not category_acl["can_see_all_threads"]:
            raise Http404()

        if target.is_unapproved and not category_acl["can_approve_content"]:
            raise Http404()


can_see_thread = return_boolean(allow_see_thread)


def allow_start_thread(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to start threads.")
        )

    category_acl = user_acl["categories"].get(target.pk, {"can_start_threads": False})

    if not category_acl["can_start_threads"]:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission",
                "You don't have permission to start new threads in this category.",
            )
        )

    if target.is_closed and not category_acl["can_close_threads"]:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission",
                "This category is closed. You can't start new threads in it.",
            )
        )


can_start_thread = return_boolean(allow_start_thread)


def allow_reply_thread(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to reply threads.")
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_reply_threads": False}
    )

    if not category_acl["can_reply_threads"]:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't reply to threads in this category."
            )
        )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't reply to threads in it.",
                )
            )
        if target.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "You can't reply to closed threads in this category.",
                )
            )


can_reply_thread = return_boolean(allow_reply_thread)


def allow_edit_thread(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to edit threads.")
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_edit_threads": False}
    )

    if not category_acl["can_edit_threads"]:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't edit threads in this category."
            )
        )

    if category_acl["can_edit_threads"] == 1:
        if user_acl["user_id"] != target.starter_id:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "You can't edit other users threads in this category.",
                )
            )

        if not has_time_to_edit_thread(user_acl, target):
            message = npgettext(
                "threads permission",
                "You can't edit threads that are older than %(minutes)s minute.",
                "You can't edit threads that are older than %(minutes)s minutes.",
                category_acl["thread_edit_time"],
            )
            raise PermissionDenied(
                message % {"minutes": category_acl["thread_edit_time"]}
            )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't edit threads in it.",
                )
            )
        if target.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission", "This thread is closed. You can't edit it."
                )
            )


can_edit_thread = return_boolean(allow_edit_thread)


def allow_pin_thread(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You have to sign in to change threads weights."
            )
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_pin_threads": 0}
    )

    if not category_acl["can_pin_threads"]:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission",
                "You can't change threads weights in this category.",
            )
        )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't change threads weights in it.",
                )
            )
        if target.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This thread is closed. You can't change its weight.",
                )
            )


can_pin_thread = return_boolean(allow_pin_thread)


def allow_unhide_thread(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to hide threads.")
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_close_threads": False}
    )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't reveal threads in it.",
                )
            )
        if target.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission", "This thread is closed. You can't reveal it."
                )
            )


can_unhide_thread = return_boolean(allow_unhide_thread)


def allow_hide_thread(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to hide threads.")
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_hide_threads": 0, "can_hide_own_threads": 0}
    )

    if (
        not category_acl["can_hide_threads"]
        and not category_acl["can_hide_own_threads"]
    ):
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't hide threads in this category."
            )
        )

    if not category_acl["can_hide_threads"] and category_acl["can_hide_own_threads"]:
        if user_acl["user_id"] != target.starter_id:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "You can't hide other users theads in this category.",
                )
            )

        if not has_time_to_edit_thread(user_acl, target):
            message = npgettext(
                "threads permission",
                "You can't hide threads that are older than %(minutes)s minute.",
                "You can't hide threads that are older than %(minutes)s minutes.",
                category_acl["thread_edit_time"],
            )
            raise PermissionDenied(
                message % {"minutes": category_acl["thread_edit_time"]}
            )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't hide threads in it.",
                )
            )
        if target.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission", "This thread is closed. You can't hide it."
                )
            )


can_hide_thread = return_boolean(allow_hide_thread)


def allow_delete_thread(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You have to sign in to delete threads."
            )
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_hide_threads": 0, "can_hide_own_threads": 0}
    )

    if (
        category_acl["can_hide_threads"] != 2
        and category_acl["can_hide_own_threads"] != 2
    ):
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't delete threads in this category."
            )
        )

    if (
        category_acl["can_hide_threads"] != 2
        and category_acl["can_hide_own_threads"] == 2
    ):
        if user_acl["user_id"] != target.starter_id:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "You can't delete other users theads in this category.",
                )
            )

        if not has_time_to_edit_thread(user_acl, target):
            message = npgettext(
                "threads permission",
                "You can't delete threads that are older than %(minutes)s minute.",
                "You can't delete threads that are older than %(minutes)s minutes.",
                category_acl["thread_edit_time"],
            )
            raise PermissionDenied(
                message % {"minutes": category_acl["thread_edit_time"]}
            )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't delete threads in it.",
                )
            )
        if target.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission", "This thread is closed. You can't delete it."
                )
            )


can_delete_thread = return_boolean(allow_delete_thread)


def allow_move_thread(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to move threads.")
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_move_threads": 0}
    )

    if not category_acl["can_move_threads"]:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't move threads in this category."
            )
        )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't move it's threads.",
                )
            )
        if target.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission", "This thread is closed. You can't move it."
                )
            )


can_move_thread = return_boolean(allow_move_thread)


def allow_merge_thread(user_acl, target, otherthread=False):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to merge threads.")
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_merge_threads": 0}
    )

    if not category_acl["can_merge_threads"]:
        if otherthread:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission", "Other thread can't be merged with."
                )
            )
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't merge threads in this category."
            )
        )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            if otherthread:
                raise PermissionDenied(
                    pgettext_lazy(
                        "threads permission",
                        "Other thread's category is closed. You can't merge with it.",
                    )
                )
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't merge it's threads.",
                )
            )
        if target.is_closed:
            if otherthread:
                raise PermissionDenied(
                    pgettext_lazy(
                        "threads permission",
                        "Other thread is closed and can't be merged with.",
                    )
                )
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This thread is closed. You can't merge it with other threads.",
                )
            )


can_merge_thread = return_boolean(allow_merge_thread)


def allow_approve_thread(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You have to sign in to approve threads."
            )
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_approve_content": 0}
    )

    if not category_acl["can_approve_content"]:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't approve threads in this category."
            )
        )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't approve threads in it.",
                )
            )
        if target.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission", "This thread is closed. You can't approve it."
                )
            )


can_approve_thread = return_boolean(allow_approve_thread)


def allow_see_post(user_acl, target):
    category_acl = user_acl["categories"].get(
        target.category_id, {"can_approve_content": False, "can_hide_events": False}
    )

    if not target.is_event and target.is_unapproved:
        if user_acl["is_anonymous"]:
            raise Http404()

        if (
            not category_acl["can_approve_content"]
            and user_acl["user_id"] != target.poster_id
        ):
            raise Http404()

    if target.is_event and target.is_hidden and not category_acl["can_hide_events"]:
        raise Http404()


can_see_post = return_boolean(allow_see_post)


def allow_edit_post(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to edit posts.")
        )

    if target.is_event:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "Events can't be edited.")
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_edit_posts": False}
    )

    if not category_acl["can_edit_posts"]:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't edit posts in this category."
            )
        )

    if (
        target.is_hidden
        and not target.is_first_post
        and not category_acl["can_hide_posts"]
    ):
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "This post is hidden, you can't edit it."
            )
        )

    if category_acl["can_edit_posts"] == 1:
        if target.poster_id != user_acl["user_id"]:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "You can't edit other users posts in this category.",
                )
            )

        if target.is_protected and not category_acl["can_protect_posts"]:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission", "This post is protected. You can't edit it."
                )
            )

        if not has_time_to_edit_post(user_acl, target):
            message = npgettext(
                "threads permission",
                "You can't edit posts that are older than %(minutes)s minute.",
                "You can't edit posts that are older than %(minutes)s minutes.",
                category_acl["post_edit_time"],
            )
            raise PermissionDenied(
                message % {"minutes": category_acl["post_edit_time"]}
            )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't edit posts in it.",
                )
            )
        if target.thread.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This thread is closed. You can't edit posts in it.",
                )
            )


can_edit_post = return_boolean(allow_edit_post)


def allow_unhide_post(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to reveal posts.")
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_hide_posts": 0, "can_hide_own_posts": 0}
    )

    if not category_acl["can_hide_posts"]:
        if not category_acl["can_hide_own_posts"]:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission", "You can't reveal posts in this category."
                )
            )

        if user_acl["user_id"] != target.poster_id:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "You can't reveal other users posts in this category.",
                )
            )

        if target.is_protected and not category_acl["can_protect_posts"]:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission", "This post is protected. You can't reveal it."
                )
            )

        if not has_time_to_edit_post(user_acl, target):
            message = npgettext(
                "threads permission",
                "You can't reveal posts that are older than %(minutes)s minute.",
                "You can't reveal posts that are older than %(minutes)s minutes.",
                category_acl["post_edit_time"],
            )
            raise PermissionDenied(
                message % {"minutes": category_acl["post_edit_time"]}
            )

    if target.is_first_post:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission",
                "Thread's first post can only be revealed using the reveal thread option.",
            )
        )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't reveal posts in it.",
                )
            )
        if target.thread.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This thread is closed. You can't reveal posts in it.",
                )
            )


can_unhide_post = return_boolean(allow_unhide_post)


def allow_hide_post(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to hide posts.")
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_hide_posts": 0, "can_hide_own_posts": 0}
    )

    if not category_acl["can_hide_posts"]:
        if not category_acl["can_hide_own_posts"]:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission", "You can't hide posts in this category."
                )
            )

        if user_acl["user_id"] != target.poster_id:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "You can't hide other users posts in this category.",
                )
            )

        if target.is_protected and not category_acl["can_protect_posts"]:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission", "This post is protected. You can't hide it."
                )
            )

        if not has_time_to_edit_post(user_acl, target):
            message = npgettext(
                "threads permission",
                "You can't hide posts that are older than %(minutes)s minute.",
                "You can't hide posts that are older than %(minutes)s minutes.",
                category_acl["post_edit_time"],
            )
            raise PermissionDenied(
                message % {"minutes": category_acl["post_edit_time"]}
            )

    if target.is_first_post:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission",
                "Thread's first post can only be hidden using the hide thread option.",
            )
        )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't hide posts in it.",
                )
            )
        if target.thread.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This thread is closed. You can't hide posts in it.",
                )
            )


can_hide_post = return_boolean(allow_hide_post)


def allow_delete_post(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to delete posts.")
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_hide_posts": 0, "can_hide_own_posts": 0}
    )

    if category_acl["can_hide_posts"] != 2:
        if category_acl["can_hide_own_posts"] != 2:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission", "You can't delete posts in this category."
                )
            )

        if user_acl["user_id"] != target.poster_id:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "You can't delete other users posts in this category.",
                )
            )

        if target.is_protected and not category_acl["can_protect_posts"]:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission", "This post is protected. You can't delete it."
                )
            )

        if not has_time_to_edit_post(user_acl, target):
            message = npgettext(
                "threads permission",
                "You can't delete posts that are older than %(minutes)s minute.",
                "You can't delete posts that are older than %(minutes)s minutes.",
                category_acl["post_edit_time"],
            )
            raise PermissionDenied(
                message % {"minutes": category_acl["post_edit_time"]}
            )

    if target.is_first_post:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission",
                "Thread's first post can only be deleted together with thread.",
            )
        )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't delete posts in it.",
                )
            )
        if target.thread.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This thread is closed. You can't delete posts in it.",
                )
            )


can_delete_post = return_boolean(allow_delete_post)


def allow_protect_post(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to protect posts.")
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_protect_posts": False}
    )

    if not category_acl["can_protect_posts"]:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't protect posts in this category."
            )
        )
    if not can_edit_post(user_acl, target):
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't protect posts you can't edit."
            )
        )


can_protect_post = return_boolean(allow_protect_post)


def allow_approve_post(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to approve posts.")
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_approve_content": False}
    )

    if not category_acl["can_approve_content"]:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't approve posts in this category."
            )
        )
    if target.is_first_post:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission",
                "Thread's first post can only be approved together with thread.",
            )
        )
    if (
        not target.is_first_post
        and not category_acl["can_hide_posts"]
        and target.is_hidden
    ):
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission",
                "You can't approve posts the content you can't see.",
            )
        )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't approve posts in it.",
                )
            )
        if target.thread.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This thread is closed. You can't approve posts in it.",
                )
            )


can_approve_post = return_boolean(allow_approve_post)


def allow_move_post(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to move posts.")
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_move_posts": False}
    )

    if not category_acl["can_move_posts"]:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't move posts in this category."
            )
        )
    if target.is_event:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "Events can't be moved.")
        )
    if target.is_first_post:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission",
                "Thread's first post can only be moved together with thread.",
            )
        )
    if not category_acl["can_hide_posts"] and target.is_hidden:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't move posts the content you can't see."
            )
        )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't move posts in it.",
                )
            )
        if target.thread.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This thread is closed. You can't move posts in it.",
                )
            )


can_move_post = return_boolean(allow_move_post)


def allow_merge_post(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to merge posts.")
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_merge_posts": False}
    )

    if not category_acl["can_merge_posts"]:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't merge posts in this category."
            )
        )
    if target.is_event:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "Events can't be merged.")
        )
    if (
        target.is_hidden
        and not category_acl["can_hide_posts"]
        and not target.is_first_post
    ):
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't merge posts the content you can't see."
            )
        )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't merge posts in it.",
                )
            )
        if target.thread.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This thread is closed. You can't merge posts in it.",
                )
            )


can_merge_post = return_boolean(allow_merge_post)


def allow_split_post(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to split posts.")
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_move_posts": False}
    )

    if not category_acl["can_move_posts"]:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't split posts in this category."
            )
        )
    if target.is_event:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "Events can't be split.")
        )
    if target.is_first_post:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "Thread's first post can't be split.")
        )
    if not category_acl["can_hide_posts"] and target.is_hidden:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't split posts the content you can't see."
            )
        )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't split posts in it.",
                )
            )
        if target.thread.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This thread is closed. You can't split posts in it.",
                )
            )


can_split_post = return_boolean(allow_split_post)


def allow_unhide_event(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to reveal events.")
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_hide_events": 0}
    )

    if not category_acl["can_hide_events"]:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't reveal events in this category."
            )
        )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't reveal events in it.",
                )
            )
        if target.thread.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This thread is closed. You can't reveal events in it.",
                )
            )


can_unhide_event = return_boolean(allow_unhide_event)


def allow_hide_event(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to hide events.")
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_hide_events": 0}
    )

    if not category_acl["can_hide_events"]:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't hide events in this category."
            )
        )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't hide events in it.",
                )
            )
        if target.thread.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This thread is closed. You can't hide events in it.",
                )
            )


can_hide_event = return_boolean(allow_hide_event)


def allow_delete_event(user_acl, target):
    if user_acl["is_anonymous"]:
        raise PermissionDenied(
            pgettext_lazy("threads permission", "You have to sign in to delete events.")
        )

    category_acl = user_acl["categories"].get(
        target.category_id, {"can_hide_events": 0}
    )

    if category_acl["can_hide_events"] != 2:
        raise PermissionDenied(
            pgettext_lazy(
                "threads permission", "You can't delete events in this category."
            )
        )

    if not category_acl["can_close_threads"]:
        if target.category.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This category is closed. You can't delete events in it.",
                )
            )
        if target.thread.is_closed:
            raise PermissionDenied(
                pgettext_lazy(
                    "threads permission",
                    "This thread is closed. You can't delete events in it.",
                )
            )


can_delete_event = return_boolean(allow_delete_event)


def can_change_owned_thread(user_acl, target):
    if user_acl["is_anonymous"] or user_acl["user_id"] != target.starter_id:
        return False

    if target.category.is_closed or target.is_closed:
        return False

    return has_time_to_edit_thread(user_acl, target)


def has_time_to_edit_thread(user_acl, target):
    edit_time = (
        user_acl["categories"].get(target.category_id, {}).get("thread_edit_time", 0)
    )
    if edit_time:
        diff = timezone.now() - target.started_on
        diff_minutes = int(diff.total_seconds() / 60)
        return diff_minutes < edit_time

    return True


def has_time_to_edit_post(user_acl, target):
    edit_time = (
        user_acl["categories"].get(target.category_id, {}).get("post_edit_time", 0)
    )
    if edit_time:
        diff = timezone.now() - target.posted_on
        diff_minutes = int(diff.total_seconds() / 60)
        return diff_minutes < edit_time

    return True


def exclude_invisible_threads(
    user_acl, categories, queryset
):  # pylint: disable=too-many-branches
    show_all = []
    show_accepted_visible = []
    show_accepted = []
    show_visible = []
    show_owned = []
    show_owned_visible = []

    for category in categories:
        add_acl_to_obj(user_acl, category)

        if not (category.acl["can_see"] and category.acl["can_browse"]):
            continue

        can_hide = category.acl["can_hide_threads"]
        if category.acl["can_see_all_threads"]:
            can_mod = category.acl["can_approve_content"]

            if can_mod and can_hide:
                show_all.append(category)
            elif user_acl["is_authenticated"]:
                if not can_mod and not can_hide:
                    show_accepted_visible.append(category)
                elif not can_mod:
                    show_accepted.append(category)
                elif not can_hide:
                    show_visible.append(category)
            else:
                show_accepted_visible.append(category)
        elif user_acl["is_authenticated"]:
            if can_hide:
                show_owned.append(category)
            else:
                show_owned_visible.append(category)

    conditions = None
    if show_all:
        conditions = Q(category__in=show_all)

    if show_accepted_visible:
        if user_acl["is_authenticated"]:
            condition = Q(
                Q(starter_id=user_acl["user_id"]) | Q(is_unapproved=False),
                category__in=show_accepted_visible,
                is_hidden=False,
            )
        else:
            condition = Q(
                category__in=show_accepted_visible, is_hidden=False, is_unapproved=False
            )

        if conditions:
            conditions = conditions | condition
        else:
            conditions = condition

    if show_accepted:
        condition = Q(
            Q(starter_id=user_acl["user_id"]) | Q(is_unapproved=False),
            category__in=show_accepted,
        )

        if conditions:
            conditions = conditions | condition
        else:
            conditions = condition

    if show_visible:
        condition = Q(category__in=show_visible, is_hidden=False)

        if conditions:
            conditions = conditions | condition
        else:
            conditions = condition

    if show_owned:
        condition = Q(category__in=show_owned, starter_id=user_acl["user_id"])

        if conditions:
            conditions = conditions | condition
        else:
            conditions = condition

    if show_owned_visible:
        condition = Q(
            category__in=show_owned_visible,
            starter_id=user_acl["user_id"],
            is_hidden=False,
        )

        if conditions:
            conditions = conditions | condition
        else:
            conditions = condition

    if not conditions:
        return Thread.objects.none()

    return queryset.filter(conditions)


def exclude_invisible_posts(user_acl, categories, queryset):
    if hasattr(categories, "__iter__"):
        return exclude_invisible_posts_in_categories(user_acl, categories, queryset)
    return exclude_invisible_posts_in_category(user_acl, categories, queryset)


def exclude_invisible_posts_in_categories(
    user_acl, categories, queryset
):  # pylint: disable=too-many-branches
    show_all = []
    show_approved = []
    show_approved_owned = []

    hide_invisible_events = []

    for category in categories:
        add_acl_to_obj(user_acl, category)

        if category.acl["can_approve_content"]:
            show_all.append(category.pk)
        else:
            if user_acl["is_authenticated"]:
                show_approved_owned.append(category.pk)
            else:
                show_approved.append(category.pk)

        if not category.acl["can_hide_events"]:
            hide_invisible_events.append(category.pk)

    conditions = None
    if show_all:
        conditions = Q(category__in=show_all)

    if show_approved:
        condition = Q(category__in=show_approved, is_unapproved=False)

        if conditions:
            conditions = conditions | condition
        else:
            conditions = condition

    if show_approved_owned:
        condition = Q(
            Q(poster_id=user_acl["user_id"]) | Q(is_unapproved=False),
            category__in=show_approved_owned,
        )

        if conditions:
            conditions = conditions | condition
        else:
            conditions = condition

    if hide_invisible_events:
        queryset = queryset.exclude(
            category__in=hide_invisible_events, is_event=True, is_hidden=True
        )

    if not conditions:
        return Post.objects.none()

    return queryset.filter(conditions)


def exclude_invisible_posts_in_category(user_acl, category, queryset):
    add_acl_to_obj(user_acl, category)

    if not category.acl["can_approve_content"]:
        if user_acl["is_authenticated"]:
            queryset = queryset.filter(
                Q(is_unapproved=False) | Q(poster_id=user_acl["user_id"])
            )
        else:
            queryset = queryset.exclude(is_unapproved=True)

    if not category.acl["can_hide_events"]:
        queryset = queryset.exclude(is_event=True, is_hidden=True)

    return queryset
