from collections.abc import Callable, Sequence
from typing import Any
import datetime

from django.contrib.auth.models import AbstractBaseUser, Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.core.checks import CheckMessage
from django.db import models
from django.http import HttpRequest, HttpResponse
from django.utils.functional import cached_property
from modelcluster.fields import ParentalKey
from modelcluster.models import ClusterableModel
from treebeard.mp_tree import MP_Node
from wagtail.admin.panels.base import Panel
from wagtail.compat import HTTPMethod
from wagtail.models.audit_log import (
    BaseLogEntry,
    BaseLogEntryManager,
    LogEntryQuerySet,
)
from wagtail.models.draft_state import DraftStateMixin
from wagtail.models.i18n import Locale, TranslatableMixin
from wagtail.models.locking import LockableMixin
from wagtail.models.panels import PanelPlaceholder
from wagtail.models.preview import PreviewableMixin
from wagtail.models.revisions import Revision, RevisionMixin
from wagtail.models.sites import Site
from wagtail.models.specific import SpecificMixin
from wagtail.models.view_restrictions import BaseViewRestriction
from wagtail.models.workflows import Workflow, WorkflowMixin
from wagtail.query import PageQuerySet
from wagtail.search import index
from wagtail.url_routing import RouteResult

PAGE_TEMPLATE_VAR: str
COMMENTS_RELATION_NAME: str
PAGE_MODEL_CLASSES: list[type[Page]]
PAGE_PERMISSION_TYPES: list[tuple[str, str, str]]
PAGE_PERMISSION_TYPE_CHOICES: list[tuple[str, str]]
PAGE_PERMISSION_CODENAMES: list[str]

def get_page_models() -> list[type[Page]]: ...
def get_page_content_types(
    include_base_page_type: bool = True,
) -> models.QuerySet[ContentType]: ...
def get_streamfield_names(model_class: type[Page]) -> tuple[str, ...]: ...
def reassign_root_page_locale_on_delete(sender: type, instance: Locale, **kwargs: Any) -> None: ...

class BasePageManager(models.Manager["Page"]):
    def get_queryset(self) -> PageQuerySet: ...
    def first_common_ancestor_of(
        self,
        pages: Sequence[Page],
        include_self: bool = False,
        strict: bool = False,
    ) -> Page: ...
    def annotate_parent_page(self, pages: Sequence[Page]) -> None: ...

class PageManager(BasePageManager, PageQuerySet): ...  # type: ignore[misc]

class PageBase(models.base.ModelBase):
    def __init__(cls, name: str, bases: tuple[type, ...], dct: dict[str, Any]) -> None: ...

class AbstractPage(
    WorkflowMixin,
    PreviewableMixin,
    DraftStateMixin,
    LockableMixin,
    RevisionMixin,
    TranslatableMixin,
    SpecificMixin,
    MP_Node,
):
    objects: PageManager  # type: ignore[assignment]
    class Meta:
        abstract: bool

class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
    # ---- model fields ----
    id: int
    title: models.CharField[str, str]
    draft_title: models.CharField[str, str]
    slug: models.SlugField[str, str]
    content_type: models.ForeignKey[ContentType, ContentType]
    url_path: models.TextField[str, str]
    owner: models.ForeignKey[AbstractBaseUser | None, AbstractBaseUser | None]
    seo_title: models.CharField[str, str]
    show_in_menus: models.BooleanField[bool | int, bool]
    search_description: models.TextField[str, str]
    latest_revision_created_at: models.DateTimeField[datetime.datetime | None, datetime.datetime | None]
    alias_of: models.ForeignKey[Page | None, Page | None]
    _default_manager: PageManager  # type: ignore[assignment]

    # ---- class-level configuration ----
    search_fields: list[index.BaseField]
    is_creatable: bool
    max_count: int | None
    max_count_per_parent: int | None
    admin_default_ordering: str
    exclude_fields_in_copy: list[str]
    default_exclude_fields_in_copy: list[str]
    content_panels: list[Panel | PanelPlaceholder]
    promote_panels: list[Panel | PanelPlaceholder]
    settings_panels: list[Panel | PanelPlaceholder]
    template: str
    ajax_template: str | None
    show_in_menus_default: bool
    private_page_options: list[str]
    context_object_name: str | None
    password_required_template: str | None
    subpage_types: list[str]
    parent_page_types: list[str]
    allowed_http_methods: list[HTTPMethod]

    objects: PageManager  # type: ignore[assignment]

    # ---- static / class methods ----
    @staticmethod
    def route_for_request(request: HttpRequest, path: str) -> RouteResult | None: ...
    @staticmethod
    def find_for_request(request: HttpRequest, path: str) -> Page | None: ...
    @classmethod
    def allowed_http_method_names(cls) -> list[str]: ...
    @classmethod
    def check(cls, **kwargs: Any) -> list[CheckMessage]: ...
    @classmethod
    def get_streamfield_names(cls) -> tuple[str, ...]: ...
    @classmethod
    def get_indexed_objects(cls) -> models.QuerySet[Page]: ...
    @classmethod
    def clean_subpage_models(cls) -> list[type[Page]]: ...
    @classmethod
    def clean_parent_page_models(cls) -> list[type[Page]]: ...
    @classmethod
    def allowed_parent_page_models(cls) -> list[type[Page]]: ...
    @classmethod
    def allowed_subpage_models(cls) -> list[type[Page]]: ...
    @classmethod
    def creatable_subpage_models(cls) -> list[type[Page]]: ...
    @classmethod
    def can_exist_under(cls, parent: Page) -> bool: ...
    @classmethod
    def can_create_at(cls, parent: Page) -> bool: ...
    @classmethod
    def get_verbose_name(cls) -> str: ...
    @classmethod
    def get_page_description(cls) -> str: ...

    # ---- instance methods ----
    def __init__(self, *args: Any, **kwargs: Any) -> None: ...
    @property
    def revisions(self) -> models.QuerySet[Revision]: ...
    def get_base_content_type(self) -> ContentType: ...
    def get_content_type(self) -> ContentType: ...
    def set_url_path(self, parent: Page | None) -> str: ...
    def full_clean(self, *args: Any, **kwargs: Any) -> None: ...
    def clean(self) -> None: ...
    def minimal_clean(self) -> None: ...
    def get_default_locale(self) -> Locale: ...
    def get_admin_default_ordering(self) -> str: ...
    def is_site_root(self) -> bool: ...
    def save(  # type: ignore[override]
        self,
        clean: bool = True,
        user: AbstractBaseUser | None = None,
        log_action: bool | str | None = False,
        **kwargs: Any,
    ) -> None: ...
    def delete(self, *args: Any, **kwargs: Any) -> tuple[int, dict[str, int]]: ...
    @property
    def page_type_display_name(self) -> str: ...
    def route(self, request: HttpRequest, path_components: list[str]) -> RouteResult: ...
    def get_admin_display_title(self) -> str: ...
    def save_revision(
        self,
        user: AbstractBaseUser | None = None,
        approved_go_live_at: datetime.datetime | None = None,
        changed: bool = True,
        log_action: bool | str = False,
        previous_revision: Revision | None = None,
        clean: bool = True,
    ) -> Revision: ...
    def get_latest_revision_as_object(self) -> Page: ...
    def update_aliases(
        self,
        *,
        revision: Revision | None = None,
        _content: dict[str, Any] | None = None,
        _updated_ids: list[int] | None = None,
    ) -> None: ...
    def publish(
        self,
        revision: Revision,
        user: AbstractBaseUser | None = None,
        changed: bool = True,
        log_action: bool | str = True,
        previous_revision: Revision | None = None,
        skip_permission_checks: bool = False,
    ) -> None: ...
    def unpublish(
        self,
        set_expired: bool = False,
        commit: bool = True,
        user: AbstractBaseUser | None = None,
        log_action: bool | str = True,
    ) -> None: ...
    def get_context(self, request: HttpRequest, *args: Any, **kwargs: Any) -> dict[str, Any]: ...
    def get_preview_context(self, request: HttpRequest, mode_name: str) -> dict[str, Any]: ...
    def get_template(self, request: HttpRequest, *args: Any, **kwargs: Any) -> str: ...
    def get_preview_template(self, request: HttpRequest, mode_name: str) -> str: ...
    def serve(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ...
    def check_request_method(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse | None: ...
    def handle_options_request(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ...
    def is_navigable(self) -> bool: ...
    def get_url_parts(
        self, request: HttpRequest | None = None
    ) -> tuple[int, str, str] | tuple[int, None, None] | None: ...
    def get_full_url(self, request: HttpRequest | None = None) -> str | None: ...
    full_url: str | None  # type: ignore[assignment]
    def get_url(
        self,
        request: HttpRequest | None = None,
        current_site: Site | None = None,
    ) -> str | None: ...
    url: str | None  # type: ignore[assignment]
    def relative_url(self, current_site: Site, request: HttpRequest | None = None) -> str | None: ...
    def get_site(self) -> Site | None: ...
    def get_indexed_instance(self) -> Page | None: ...
    def get_default_privacy_setting(self, request: HttpRequest) -> dict[str, str]: ...
    def can_move_to(self, parent: Page) -> bool: ...
    def permissions_for_user(self, user: AbstractBaseUser) -> PagePermissionTester: ...
    def is_previewable(self) -> bool: ...
    def get_route_paths(self) -> list[str]: ...
    def get_cached_paths(self) -> list[str]: ...
    def get_cache_key_components(self) -> list[Any]: ...
    @property
    def cache_key(self) -> str: ...
    def get_sitemap_urls(self, request: HttpRequest | None = None) -> list[dict[str, Any]]: ...
    def get_ancestors(self, inclusive: bool = False) -> PageQuerySet: ...
    def get_descendants(self, inclusive: bool = False) -> PageQuerySet: ...
    def get_siblings(self, inclusive: bool = True) -> PageQuerySet: ...
    def get_next_siblings(self, inclusive: bool = False) -> PageQuerySet: ...
    def get_prev_siblings(self, inclusive: bool = False) -> PageQuerySet: ...
    def get_children(self) -> PageQuerySet: ...
    def get_view_restrictions(
        self,
    ) -> models.QuerySet[PageViewRestriction]: ...
    def serve_password_required_response(self, request: HttpRequest, form: Any, action_url: str) -> HttpResponse: ...
    def with_content_json(self, content: dict[str, Any]) -> Page: ...
    @property
    def has_workflow(self) -> bool: ...
    def get_workflow(self) -> Workflow | None: ...
    @property
    def approved_schedule(self) -> bool: ...
    def has_unpublished_subtree(self) -> bool: ...
    def move(
        self,
        target: Page,
        pos: str | None = None,
        user: AbstractBaseUser | None = None,
    ) -> None: ...
    def copy(
        self,
        recursive: bool = False,
        to: Page | None = None,
        update_attrs: dict[str, Any] | None = None,
        copy_revisions: bool = True,
        keep_live: bool = True,
        user: AbstractBaseUser | None = None,
        process_child_object: Callable[..., None] | None = None,
        exclude_fields: list[str] | None = None,
        log_action: str | None = "wagtail.copy",
        reset_translation_key: bool = True,
    ) -> Page: ...
    def create_alias(
        self,
        *,
        recursive: bool = False,
        parent: Page | None = None,
        update_slug: str | None = None,
        update_locale: Locale | None = None,
        user: AbstractBaseUser | None = None,
        log_action: str | None = "wagtail.create_alias",
        reset_translation_key: bool = True,
        _mpnode_attrs: tuple[str, int, int] | None = None,
    ) -> Page: ...
    def copy_for_translation(
        self,
        locale: Locale,
        copy_parents: bool = False,
        alias: bool = False,
        exclude_fields: list[str] | None = None,
    ) -> Page: ...
    def get_parent(self, *, update: bool = False) -> Page | None: ...
    def get_translations(self, inclusive: bool = False) -> PageQuerySet: ...

    class Meta:
        verbose_name: str
        verbose_name_plural: str
        unique_together: list[tuple[str, str]]
        permissions: list[tuple[str, str]]

class GroupPagePermissionManager(models.Manager["GroupPagePermission"]):
    def create(self, **kwargs: Any) -> GroupPagePermission: ...  # type: ignore[override]

class GroupPagePermission(models.Model):
    group: models.ForeignKey[Group, Group]
    page: models.ForeignKey[Page, Page]
    permission: models.ForeignKey[Permission, Permission]

    objects: GroupPagePermissionManager  # type: ignore[assignment]

    class Meta:
        verbose_name: str
        verbose_name_plural: str

class PagePermissionTester:
    user: AbstractBaseUser
    permission_policy: Any
    page: Page
    page_is_root: bool
    permissions: set[str]

    def __init__(self, user: AbstractBaseUser, page: Page) -> None: ...
    def user_has_lock(self) -> bool: ...
    def page_locked(self) -> bool: ...
    def can_add_subpage(self) -> bool: ...
    def can_edit(self) -> bool: ...
    def can_delete(self, ignore_bulk: bool = False) -> bool: ...
    def can_unpublish(self) -> bool: ...
    def can_publish(self) -> bool: ...
    def can_submit_for_moderation(self) -> bool: ...
    def can_set_view_restrictions(self) -> bool: ...
    def can_unschedule(self) -> bool: ...
    def can_lock(self) -> bool: ...
    def can_unlock(self) -> bool: ...
    def can_publish_subpage(self) -> bool: ...
    def can_reorder_children(self) -> bool: ...
    def can_move(self) -> bool: ...
    def can_copy(self) -> bool: ...
    def can_move_to(self, destination: Page) -> bool: ...
    def can_copy_to(self, destination: Page, recursive: bool = False) -> bool: ...
    def can_view_revisions(self) -> bool: ...

class PageViewRestriction(BaseViewRestriction):
    page: models.ForeignKey[Page, Page]
    passed_view_restrictions_session_key: str

    def save(  # type: ignore[override]
        self, user: AbstractBaseUser | None = None, **kwargs: Any
    ) -> None: ...
    def delete(  # type: ignore[override]
        self, user: AbstractBaseUser | None = None, **kwargs: Any
    ) -> tuple[int, dict[str, int]]: ...

    class Meta:
        verbose_name: str
        verbose_name_plural: str

class WorkflowPage(models.Model):
    page: models.OneToOneField[Page, Page]
    workflow: models.ForeignKey[Workflow, Workflow]

    def get_pages(self) -> PageQuerySet: ...

    class Meta:
        verbose_name: str
        verbose_name_plural: str

class PageLogEntryQuerySet(LogEntryQuerySet):
    def get_content_type_ids(self) -> set[int]: ...
    def filter_on_content_type(self, content_type: ContentType) -> PageLogEntryQuerySet: ...

class PageLogEntryManager(BaseLogEntryManager):
    def get_queryset(self) -> PageLogEntryQuerySet: ...  # type: ignore[override]
    def get_instance_title(self, instance: Page) -> str: ...  # type: ignore[override]
    def log_action(  # type: ignore[override]
        self, instance: Page, action: str, **kwargs: Any
    ) -> PageLogEntry: ...
    def viewable_by_user(  # type: ignore[override]
        self, user: AbstractBaseUser
    ) -> models.QuerySet[PageLogEntry]: ...
    def for_instance(  # type: ignore[override]
        self, instance: Page
    ) -> models.QuerySet[PageLogEntry]: ...

class PageLogEntry(BaseLogEntry):
    page: models.ForeignKey[Page, Page]
    objects: PageLogEntryManager  # type: ignore[assignment]

    @cached_property
    def object_id(self) -> int: ...  # type: ignore[override]
    @cached_property
    def message(self) -> str: ...  # type: ignore[override]

    class Meta:
        ordering: list[str]
        verbose_name: str
        verbose_name_plural: str

class Comment(ClusterableModel):
    page: ParentalKey[Page, Page]
    user: models.ForeignKey[AbstractBaseUser, AbstractBaseUser]
    text: models.TextField[str, str]
    contentpath: models.TextField[str, str]
    position: models.TextField[str, str]
    created_at: models.DateTimeField[datetime.datetime, datetime.datetime]
    updated_at: models.DateTimeField[datetime.datetime, datetime.datetime]
    revision_created: models.ForeignKey[Revision | None, Revision | None]
    resolved_at: models.DateTimeField[datetime.datetime | None, datetime.datetime | None]
    resolved_by: models.ForeignKey[AbstractBaseUser | None, AbstractBaseUser | None]

    def save(  # type: ignore[override]
        self, update_position: bool = False, **kwargs: Any
    ) -> None: ...
    def log_create(
        self,
        page_revision: Revision | None = None,
        user: AbstractBaseUser | None = None,
    ) -> None: ...
    def log_edit(
        self,
        page_revision: Revision | None = None,
        user: AbstractBaseUser | None = None,
    ) -> None: ...
    def log_resolve(
        self,
        page_revision: Revision | None = None,
        user: AbstractBaseUser | None = None,
    ) -> None: ...
    def log_delete(
        self,
        page_revision: Revision | None = None,
        user: AbstractBaseUser | None = None,
    ) -> None: ...
    def has_valid_contentpath(self, page: Page) -> bool: ...

    class Meta:
        verbose_name: str
        verbose_name_plural: str

class CommentReply(models.Model):
    comment: ParentalKey[Comment, Comment]
    user: models.ForeignKey[AbstractBaseUser, AbstractBaseUser]
    text: models.TextField[str, str]
    created_at: models.DateTimeField[datetime.datetime, datetime.datetime]
    updated_at: models.DateTimeField[datetime.datetime, datetime.datetime]

    def log_create(
        self,
        page_revision: Revision | None = None,
        user: AbstractBaseUser | None = None,
    ) -> None: ...
    def log_edit(
        self,
        page_revision: Revision | None = None,
        user: AbstractBaseUser | None = None,
    ) -> None: ...
    def log_delete(
        self,
        page_revision: Revision | None = None,
        user: AbstractBaseUser | None = None,
    ) -> None: ...

    class Meta:
        verbose_name: str
        verbose_name_plural: str

class PageSubscription(models.Model):
    user: models.ForeignKey[AbstractBaseUser, AbstractBaseUser]
    page: models.ForeignKey[Page, Page]
    comment_notifications: models.BooleanField[bool | int, bool]
    wagtail_reference_index_ignore: bool

    class Meta:
        unique_together: list[tuple[str, str]]
