"""Connect comments."""

import logging

from gaphor.diagram.connectors import BaseConnector, Connector
from gaphor.diagram.presentation import ElementPresentation, LinePresentation
from gaphor.UML import Comment, Element
from gaphor.UML.general.comment import CommentItem, CommentLineItem

logger = logging.getLogger(__name__)


@Connector.register(CommentItem, CommentLineItem)
@Connector.register(ElementPresentation, CommentLineItem)
class CommentLineElementConnect(BaseConnector):
    """Connect a comment line to any element item."""

    element: CommentItem | ElementPresentation
    line: CommentLineItem

    def allow(self, handle, port):
        """In addition to the normal check, both line ends may not be connected
        to the same element.

        Same goes for subjects. One of the ends should be connected to a
        Comment element.
        """
        opposite = self.line.opposite(handle)
        connected_to = self.get_connected(opposite)
        element = self.element

        if connected_to is element:
            return None

        # Same goes for subjects:
        if (
            connected_to
            and not connected_to.subject
            and not element.subject
            and connected_to.subject is element.subject
        ):
            return None

        # One end should be connected to a CommentItem:
        cls = CommentItem
        glue_ok = isinstance(connected_to, cls) ^ isinstance(self.element, cls)
        if connected_to and not glue_ok:
            return None

        # Do not allow links between the comment and the element
        if (
            connected_to
            and element
            and (
                (
                    isinstance(connected_to.subject, Comment)
                    and self.element.subject in connected_to.subject.annotatedElement
                )
                or (
                    isinstance(self.element.subject, Comment)
                    and connected_to.subject in self.element.subject.annotatedElement
                )
            )
        ):
            return None

        return super().allow(handle, port)

    def connect(self, handle, port):
        if super().connect(handle, port):
            opposite = self.line.opposite(handle)
            connected_to = self.get_connected(opposite)
            if connected_to and connected_to.subject:
                if isinstance(connected_to.subject, Comment):
                    connected_to.subject.annotatedElement = self.element.subject
                else:
                    assert isinstance(self.element.subject, Comment)
                    assert isinstance(connected_to.subject, Element)
                    self.element.subject.annotatedElement = connected_to.subject

    def disconnect(self, handle):
        opposite = self.line.opposite(handle)
        oct = self.get_connected(opposite)
        hct = self.get_connected(handle)

        if hct and oct:
            logger.debug(f"Disconnecting {hct} and {oct}")
            try:
                if hct.subject:
                    if isinstance(oct.subject, Comment):
                        assert isinstance(hct.subject, Element)
                        del oct.subject.annotatedElement[hct.subject]
                    elif oct.subject:
                        assert isinstance(hct.subject, Comment)
                        assert isinstance(oct.subject, Element)
                        del hct.subject.annotatedElement[oct.subject]
            except ValueError:
                logger.debug(
                    "Invoked CommentLineElementConnect.disconnect() for nonexistent relationship"
                )

        super().disconnect(handle)


@Connector.register(LinePresentation, CommentLineItem)
class CommentLineLineConnect(BaseConnector):
    """Connect a comment line to any diagram line."""

    element: LinePresentation
    line: CommentLineItem

    def allow(self, handle, port):
        """In addition to the normal check, both line ends may not be connected
        to the same element.

        Same goes for subjects. One of the ends should be connected to a
        Comment element.
        """
        opposite = self.line.opposite(handle)
        element = self.element
        connected_to = self.get_connected(opposite)

        # do not connect to the same item nor connect to other comment line
        if connected_to is element or isinstance(element, CommentLineItem):
            return None

        # One end should be connected to a CommentItem:
        cls = CommentItem
        glue_ok = isinstance(connected_to, cls) ^ isinstance(self.element, cls)
        return None if connected_to and not glue_ok else super().allow(handle, port)

    def connect(self, handle, port):
        if super().connect(handle, port):
            opposite = self.line.opposite(handle)
            c = self.get_connected(opposite)
            if c and self.element.subject:
                if isinstance(c.subject, Comment):
                    c.subject.annotatedElement = self.element.subject
                else:
                    assert isinstance(self.element.subject, Comment)
                    assert isinstance(c.subject, Element)
                    self.element.subject.annotatedElement = c.subject

    def disconnect(self, handle):
        c1 = self.get_connected(handle)
        opposite = self.line.opposite(handle)
        c2 = self.get_connected(opposite)
        if c1 and c2:
            if (
                isinstance(c1.subject, Comment)
                and isinstance(c2.subject, Element)
                and c2.subject in c1.subject.annotatedElement
            ):
                del c1.subject.annotatedElement[c2.subject]
            elif (
                isinstance(c1.subject, Element)
                and isinstance(c2.subject, Comment)
                and c1.subject in c2.subject.annotatedElement
            ):
                del c2.subject.annotatedElement[c1.subject]
        super().disconnect(handle)
