"""
Data types to describe people's names.
"""

from __future__ import annotations

from typing import final, TYPE_CHECKING, Iterable

from typing_extensions import override

from betty.ancestry.has_citations import HasCitations
from betty.ancestry.locale import HasLocale
from betty.json.linked_data import dump_context, JsonLdObject
from betty.json.schema import String
from betty.locale import UNDETERMINED_LOCALE
from betty.locale.localizable import _, Localizable
from betty.model import Entity
from betty.model.association import BidirectionalToOne, ToOneResolver, ToManyResolver
from betty.plugin import ShorthandPluginBase
from betty.privacy import HasPrivacy, Privacy, merge_privacies
from betty.repr import repr_instance

if TYPE_CHECKING:
    from betty.ancestry.citation import Citation
    from betty.project import Project
    from betty.serde.dump import DumpMapping, Dump
    from betty.ancestry.person import Person


@final
class PersonName(ShorthandPluginBase, HasLocale, HasCitations, HasPrivacy, Entity):
    """
    A name for a :py:class:`betty.ancestry.person.Person`.
    """

    _plugin_id = "person-name"
    _plugin_label = _("Person name")

    #: The person whose name this is.
    person = BidirectionalToOne["PersonName", "Person"](
        "betty.ancestry.person_name:PersonName",
        "person",
        "betty.ancestry.person:Person",
        "names",
        title="Person",
    )

    def __init__(
        self,
        *,
        person: Person | ToOneResolver[Person],
        id: str | None = None,  # noqa A002
        individual: str | None = None,
        affiliation: str | None = None,
        privacy: Privacy | None = None,
        public: bool | None = None,
        private: bool | None = None,
        locale: str = UNDETERMINED_LOCALE,
        citations: Iterable[Citation] | ToManyResolver[Citation] | None = None,
    ):
        if not individual and not affiliation:
            raise ValueError(
                "The individual and affiliation names must not both be empty."
            )
        super().__init__(
            id,
            privacy=privacy,
            public=public,
            private=private,
            locale=locale,
            citations=citations,
        )
        self._individual = individual
        self._affiliation = affiliation
        # Set the person association last, because the association requires comparisons, and self.__eq__() uses the
        # individual and affiliation names.
        self.person = person

    @override
    def _get_effective_privacy(self) -> Privacy:
        privacy = super()._get_effective_privacy()
        return merge_privacies(privacy, self.person.privacy)

    @override
    def __repr__(self) -> str:
        return repr_instance(
            self, id=self.id, individual=self.individual, affiliation=self.affiliation
        )

    @override
    @classmethod
    def plugin_label_plural(cls) -> Localizable:
        return _("Person names")

    @property
    def individual(self) -> str | None:
        """
        The name's individual component.

        Also known as:

        - first name
        - given name
        """
        return self._individual

    @property
    def affiliation(self) -> str | None:
        """
        The name's affiliation, or family component.

        Also known as:

        - last name
        - surname
        """
        return self._affiliation

    @override
    @property
    def label(self) -> Localizable:
        return _("{individual_name} {affiliation_name}").format(
            individual_name="…" if not self.individual else self.individual,
            affiliation_name="…" if not self.affiliation else self.affiliation,
        )

    @override
    async def dump_linked_data(self, project: Project) -> DumpMapping[Dump]:
        dump = await super().dump_linked_data(project)
        if self.public:
            if self.individual is not None:
                dump_context(dump, individual="https://schema.org/givenName")
                dump["individual"] = self.individual
            if self.affiliation is not None:
                dump_context(dump, affiliation="https://schema.org/familyName")
                dump["affiliation"] = self.affiliation
        return dump

    @override
    @classmethod
    async def linked_data_schema(cls, project: Project) -> JsonLdObject:
        schema = await super().linked_data_schema(project)
        schema.add_property(
            "individual",
            String(
                title="Individual name",
                description="The part of the name unique to this individual, such as a first name.",
            ),
            False,
        )
        schema.add_property(
            "affiliation",
            String(
                title="Affiliation name",
                description="The part of the name shared with others, such as a surname.",
            ),
            False,
        )
        return schema
