from typing import Any

from django.db import models
from django.db.models.fields.reverse_related import ForeignObjectRel
from django.utils.safestring import SafeString
from wagtail import blocks as blocks
from wagtail.blocks.base import BoundBlock
from wagtail.fields import RichTextField as RichTextField
from wagtail.fields import StreamField as StreamField
from wagtail.utils.registry import ModelFieldRegistry as ModelFieldRegistry
from wagtail.utils.text import text_from_html as text_from_html

comparison_class_registry: ModelFieldRegistry

def register_comparison_class(
    field_class: type[models.Field],
    to: type | None = None,
    comparison_class: type[FieldComparison] | None = None,
    exact_class: bool = False,
) -> None: ...

class FieldComparison:
    is_field: bool
    is_child_relation: bool
    field: models.Field
    val_a: Any
    val_b: Any
    def __init__(self, field: models.Field, obj_a: models.Model, obj_b: models.Model) -> None: ...
    def field_label(self) -> str: ...
    def htmldiff(self) -> SafeString | str: ...
    def has_changed(self) -> bool: ...

class TextFieldComparison(FieldComparison):
    def htmldiff(self) -> SafeString: ...

class RichTextFieldComparison(TextFieldComparison):
    def htmldiff(self) -> SafeString: ...

def get_comparison_class_for_block(block: blocks.Block) -> type[BlockComparison]: ...

class BlockComparison:
    block: blocks.Block
    exists_a: bool
    exists_b: bool
    val_a: Any
    val_b: Any
    def __init__(self, block: blocks.Block, exists_a: bool, exists_b: bool, val_a: Any, val_b: Any) -> None: ...
    def is_new(self) -> bool: ...
    def is_deleted(self) -> bool: ...
    def has_changed(self) -> bool: ...
    def htmlvalue(self, val: Any) -> SafeString | str: ...
    def htmldiff(self) -> SafeString: ...

class CharBlockComparison(BlockComparison):
    def htmldiff(self) -> SafeString: ...
    def htmlvalue(self, val: Any) -> str: ...

class RichTextBlockComparison(BlockComparison): ...

class StructBlockComparison(BlockComparison):
    def htmlvalue(self, val: Any) -> SafeString: ...
    def htmldiff(self) -> SafeString: ...

class BaseSequenceBlockComparison(BlockComparison):
    @staticmethod
    def get_blocks_from_value(val: Any) -> None: ...
    @staticmethod
    def get_blocks_by_id(
        a_blocks: list[BoundBlock], b_blocks: list[BoundBlock]
    ) -> tuple[dict[str, BoundBlock], dict[str, BoundBlock]]: ...
    def get_block_comparisons_by_id(self) -> list[BlockComparison]: ...
    def htmldiff(self) -> SafeString: ...

class StreamBlockComparison(BaseSequenceBlockComparison):
    @staticmethod
    def get_blocks_from_value(val: Any) -> list[BoundBlock]: ...  # type: ignore[override]
    def get_block_comparisons(self) -> list[BlockComparison]: ...
    def htmlvalue(self, val: Any) -> SafeString | str: ...

class ListBlockComparison(BaseSequenceBlockComparison):
    @staticmethod
    def get_blocks_from_value(val: Any) -> list[BoundBlock]: ...  # type: ignore[override]
    def get_block_comparisons(self) -> list[BlockComparison]: ...

class StreamFieldComparison(FieldComparison):
    def has_block_ids(self, val: Any) -> bool: ...
    def htmldiff(self) -> SafeString: ...

class ChoiceFieldComparison(FieldComparison):
    def htmldiff(self) -> SafeString | str: ...

class M2MFieldComparison(FieldComparison):
    def get_items(self) -> tuple[list[models.Model], list[models.Model]]: ...
    def get_item_display(self, item: models.Model) -> str: ...
    def htmldiff(self) -> SafeString: ...
    def has_changed(self) -> bool: ...

class TagsFieldComparison(M2MFieldComparison):
    def get_item_display(self, tag: models.Model) -> str: ...

class ForeignObjectComparison(FieldComparison):
    def get_objects(self) -> tuple[models.Model | None, models.Model | None]: ...
    def htmldiff(self) -> SafeString | str: ...

class ChildRelationComparison:
    is_field: bool
    is_child_relation: bool
    field: ForeignObjectRel
    field_comparisons: list[type[FieldComparison]]
    val_a: Any
    val_b: Any
    label: str
    def __init__(
        self,
        field: ForeignObjectRel,
        field_comparisons: list[type[FieldComparison]],
        obj_a: models.Model,
        obj_b: models.Model,
        label: str = "",
    ) -> None: ...
    def field_label(self) -> str: ...
    def get_mapping(
        self, objs_a: list[models.Model], objs_b: list[models.Model]
    ) -> tuple[dict[int, int], dict[int, int], list[int], list[int]]: ...
    def get_child_comparison(self, obj_a: models.Model | None, obj_b: models.Model | None) -> ChildObjectComparison: ...
    def get_child_comparisons(self) -> list[ChildObjectComparison]: ...
    def has_changed(self) -> bool: ...

class ChildObjectComparison:
    model: type[models.Model]
    field_comparisons: list[type[FieldComparison]]
    obj_a: models.Model | None
    obj_b: models.Model | None
    def __init__(
        self,
        model: type[models.Model],
        field_comparisons: list[type[FieldComparison]],
        obj_a: models.Model | None,
        obj_b: models.Model | None,
    ) -> None: ...
    def is_addition(self) -> bool: ...
    def is_deletion(self) -> bool: ...
    def get_position_change(self) -> int | None: ...
    def get_field_comparisons(self) -> list[FieldComparison]: ...
    def has_changed(self) -> bool: ...
    def get_num_differences(self) -> int: ...

class TextDiff:
    changes: list[tuple[str, str]]
    separator: str
    def __init__(self, changes: list[tuple[str, str]], separator: str = "") -> None: ...
    def to_html(
        self, tag: str = "span", addition_class: str = "addition", deletion_class: str = "deletion"
    ) -> SafeString: ...

def diff_text(a: str | None, b: str | None) -> TextDiff: ...
