import sys
import uuid
from abc import ABC
from typing import Any, Literal, Optional, Union

from ..constants import AnnotationFlag
from ..generic import ArrayObject, DictionaryObject
from ..generic._base import (
    BooleanObject,
    FloatObject,
    IndirectObject,
    NameObject,
    NumberObject,
    TextStringObject,
)
from ..generic._rectangle import RectangleObject
from ..generic._utils import hex_to_rgb
from ._base import NO_FLAGS, AnnotationDictionary

if sys.version_info[:2] >= (3, 10):
    from typing import TypeAlias
else:
    # PEP 613 introduced typing.TypeAlias with Python 3.10
    # For older Python versions, the backport typing_extensions is necessary:
    from typing_extensions import TypeAlias


Vertex: TypeAlias = tuple[float, float]


def _get_bounding_rectangle(vertices: list[Vertex]) -> RectangleObject:
    x_min, y_min = vertices[0][0], vertices[0][1]
    x_max, y_max = vertices[0][0], vertices[0][1]
    for x, y in vertices:
        x_min = min(x_min, x)
        y_min = min(y_min, y)
        x_max = max(x_max, x)
        y_max = max(y_max, y)
    return RectangleObject((x_min, y_min, x_max, y_max))


class MarkupAnnotation(AnnotationDictionary, ABC):
    """
    Base class for all markup annotations.

    Args:
        title_bar: Text to be displayed in the title bar of the annotation;
            by convention this is the name of the author
        in_reply_to: The annotation that this annotation is "in reply to"
            (PDF 1.5). Can be either an annotation (previously added using
            :meth:`~pypdf.PdfWriter.add_annotation`) or a reference to the
            target annotation.
        reply_type: The relationship between this annotation and the one
            specified by ``in_reply_to``. Either ``"R"`` (a reply, default)
            or ``"Group"`` (grouped with the parent annotation). Raises
            ``ValueError`` if a non-default value is provided without
            ``in_reply_to``.
        annotation_name: A text string uniquely identifying this annotation
            among all annotations on its page. Automatically generated when
            ``in_reply_to`` is set and no name is provided. Raises
            ``ValueError`` if provided without ``in_reply_to``.

    """

    def __init__(
        self,
        *,
        title_bar: Optional[str] = None,
        in_reply_to: Optional[Union[DictionaryObject, IndirectObject]] = None,
        reply_type: Literal["R", "Group"] = "R",
        annotation_name: Optional[str] = None,
    ) -> None:
        if title_bar is not None:
            self[NameObject("/T")] = TextStringObject(title_bar)
        if annotation_name is not None and in_reply_to is None:
            raise ValueError(
                "annotation_name is only supported when in_reply_to is set"
            )
        if reply_type != "R" and in_reply_to is None:
            raise ValueError(
                "reply_type is only meaningful when in_reply_to is set"
            )
        if in_reply_to is not None:
            if isinstance(in_reply_to, IndirectObject):
                ref: IndirectObject = in_reply_to
            else:
                indirect_ref = getattr(in_reply_to, "indirect_reference", None)
                if not isinstance(indirect_ref, IndirectObject):
                    raise ValueError(
                        "in_reply_to must be a registered annotation "
                        "(added via writer.add_annotation() first)"
                    )
                ref = indirect_ref
            self[NameObject("/IRT")] = ref
            self[NameObject("/RT")] = NameObject(f"/{reply_type}")
            if annotation_name is None:
                annotation_name = str(uuid.uuid4())
            self[NameObject("/NM")] = TextStringObject(annotation_name)


class Text(MarkupAnnotation):
    """
    A text annotation.

    Args:
        rect: array of four integers ``[xLL, yLL, xUR, yUR]``
            specifying the clickable rectangular area
        text: The text that is added to the document
        open:
        flags:

    """

    def __init__(
        self,
        *,
        rect: Union[RectangleObject, tuple[float, float, float, float]],
        text: str,
        open: bool = False,
        flags: int = NO_FLAGS,
        **kwargs: Any,
    ) -> None:
        super().__init__(**kwargs)
        self[NameObject("/Subtype")] = NameObject("/Text")
        self[NameObject("/Rect")] = RectangleObject(rect)
        self[NameObject("/Contents")] = TextStringObject(text)
        self[NameObject("/Open")] = BooleanObject(open)
        self[NameObject("/Flags")] = NumberObject(flags)


class FreeText(MarkupAnnotation):
    """A FreeText annotation"""

    def __init__(
        self,
        *,
        text: str,
        rect: Union[RectangleObject, tuple[float, float, float, float]],
        font: str = "Helvetica",
        bold: bool = False,
        italic: bool = False,
        font_size: str = "14pt",
        font_color: str = "000000",
        border_color: Optional[str] = "000000",
        background_color: Optional[str] = "ffffff",
        **kwargs: Any,
    ) -> None:
        super().__init__(**kwargs)
        self[NameObject("/Subtype")] = NameObject("/FreeText")
        self[NameObject("/Rect")] = RectangleObject(rect)

        # Table 225 of the 1.7 reference ("CSS2 style attributes used in rich text strings")
        font_str = "font: "
        if italic:
            font_str = f"{font_str}italic "
        else:
            font_str = f"{font_str}normal "
        if bold:
            font_str = f"{font_str}bold "
        else:
            font_str = f"{font_str}normal "
        font_str = f"{font_str}{font_size} {font}"
        font_str = f"{font_str};text-align:left;color:#{font_color}"

        default_appearance_string = ""
        if border_color:
            for st in hex_to_rgb(border_color):
                default_appearance_string = f"{default_appearance_string}{st} "
            default_appearance_string = f"{default_appearance_string}rg"

        self.update(
            {
                NameObject("/Subtype"): NameObject("/FreeText"),
                NameObject("/Rect"): RectangleObject(rect),
                NameObject("/Contents"): TextStringObject(text),
                # font size color
                NameObject("/DS"): TextStringObject(font_str),
                NameObject("/DA"): TextStringObject(default_appearance_string),
            }
        )
        if border_color is None:
            # Border Style
            self[NameObject("/BS")] = DictionaryObject(
                {
                    # width of 0 means no border
                    NameObject("/W"): NumberObject(0)
                }
            )
        if background_color is not None:
            self[NameObject("/C")] = ArrayObject(
                [FloatObject(n) for n in hex_to_rgb(background_color)]
            )


class Line(MarkupAnnotation):
    def __init__(
        self,
        p1: Vertex,
        p2: Vertex,
        rect: Union[RectangleObject, tuple[float, float, float, float]],
        text: str = "",
        **kwargs: Any,
    ) -> None:
        super().__init__(**kwargs)
        self.update(
            {
                NameObject("/Subtype"): NameObject("/Line"),
                NameObject("/Rect"): RectangleObject(rect),
                NameObject("/L"): ArrayObject(
                    [
                        FloatObject(p1[0]),
                        FloatObject(p1[1]),
                        FloatObject(p2[0]),
                        FloatObject(p2[1]),
                    ]
                ),
                NameObject("/LE"): ArrayObject(
                    [
                        NameObject("/None"),
                        NameObject("/None"),
                    ]
                ),
                NameObject("/IC"): ArrayObject(
                    [
                        FloatObject(0.5),
                        FloatObject(0.5),
                        FloatObject(0.5),
                    ]
                ),
                NameObject("/Contents"): TextStringObject(text),
            }
        )


class PolyLine(MarkupAnnotation):
    def __init__(
        self,
        vertices: list[Vertex],
        **kwargs: Any,
    ) -> None:
        super().__init__(**kwargs)
        if len(vertices) == 0:
            raise ValueError("A polyline needs at least 1 vertex with two coordinates")
        coord_list = []
        for x, y in vertices:
            coord_list.append(NumberObject(x))
            coord_list.append(NumberObject(y))
        self.update(
            {
                NameObject("/Subtype"): NameObject("/PolyLine"),
                NameObject("/Vertices"): ArrayObject(coord_list),
                NameObject("/Rect"): RectangleObject(_get_bounding_rectangle(vertices)),
            }
        )


class Rectangle(MarkupAnnotation):
    def __init__(
        self,
        rect: Union[RectangleObject, tuple[float, float, float, float]],
        *,
        interior_color: Optional[str] = None,
        **kwargs: Any,
    ) -> None:
        super().__init__(**kwargs)
        self.update(
            {
                NameObject("/Type"): NameObject("/Annot"),
                NameObject("/Subtype"): NameObject("/Square"),
                NameObject("/Rect"): RectangleObject(rect),
            }
        )

        if interior_color:
            self[NameObject("/IC")] = ArrayObject(
                [FloatObject(n) for n in hex_to_rgb(interior_color)]
            )


class Highlight(MarkupAnnotation):
    def __init__(
        self,
        *,
        rect: Union[RectangleObject, tuple[float, float, float, float]],
        quad_points: ArrayObject,
        highlight_color: str = "ff0000",
        printing: bool = False,
        **kwargs: Any,
    ) -> None:
        super().__init__(**kwargs)
        self.update(
            {
                NameObject("/Subtype"): NameObject("/Highlight"),
                NameObject("/Rect"): RectangleObject(rect),
                NameObject("/QuadPoints"): quad_points,
                NameObject("/C"): ArrayObject(
                    [FloatObject(n) for n in hex_to_rgb(highlight_color)]
                ),
            }
        )
        if printing:
            self.flags = AnnotationFlag.PRINT


class Ellipse(MarkupAnnotation):
    def __init__(
        self,
        rect: Union[RectangleObject, tuple[float, float, float, float]],
        *,
        interior_color: Optional[str] = None,
        **kwargs: Any,
    ) -> None:
        super().__init__(**kwargs)

        self.update(
            {
                NameObject("/Type"): NameObject("/Annot"),
                NameObject("/Subtype"): NameObject("/Circle"),
                NameObject("/Rect"): RectangleObject(rect),
            }
        )

        if interior_color:
            self[NameObject("/IC")] = ArrayObject(
                [FloatObject(n) for n in hex_to_rgb(interior_color)]
            )


class Polygon(MarkupAnnotation):
    def __init__(
        self,
        vertices: list[tuple[float, float]],
        **kwargs: Any,
    ) -> None:
        super().__init__(**kwargs)
        if len(vertices) == 0:
            raise ValueError("A polygon needs at least 1 vertex with two coordinates")

        coord_list = []
        for x, y in vertices:
            coord_list.append(NumberObject(x))
            coord_list.append(NumberObject(y))
        self.update(
            {
                NameObject("/Type"): NameObject("/Annot"),
                NameObject("/Subtype"): NameObject("/Polygon"),
                NameObject("/Vertices"): ArrayObject(coord_list),
                NameObject("/IT"): NameObject("/PolygonCloud"),
                NameObject("/Rect"): RectangleObject(_get_bounding_rectangle(vertices)),
            }
        )
