"""Typed OPC relationship model.

Provides :class:`Relationship` and :class:`RelationshipCollection` for
reading and querying ``.rels`` files from OOXML packages.

See ADR 020, item 6.
"""

from __future__ import annotations

import posixpath
from dataclasses import dataclass
from typing import Iterator

from lxml import etree

from tokenmoulds.ooxml.namespaces import RELATIONSHIPS

_RELS_NS = RELATIONSHIPS


@dataclass(frozen=True)
class Relationship:
    """A single OPC relationship."""

    id: str
    type: str
    target: str
    target_mode: str = "Internal"

    @property
    def is_external(self) -> bool:
        return self.target_mode == "External"


class RelationshipCollection:
    """Parsed collection of OPC relationships from a ``.rels`` file."""

    def __init__(self, source_uri: str = "/") -> None:
        self._source_uri = source_uri
        self._rels: list[Relationship] = []
        self._by_id: dict[str, Relationship] = {}

    def add(self, rel: Relationship) -> None:
        self._rels.append(rel)
        self._by_id[rel.id] = rel

    def get_by_id(self, rel_id: str) -> Relationship | None:
        return self._by_id.get(rel_id)

    def get_by_type(self, rel_type: str) -> Iterator[Relationship]:
        return (r for r in self._rels if r.type == rel_type)

    def get_first_by_type(self, rel_type: str) -> Relationship | None:
        return next(self.get_by_type(rel_type), None)

    def resolve_target(self, rel_id: str) -> str | None:
        """Resolve target path relative to the source part."""
        rel = self.get_by_id(rel_id)
        if rel is None:
            return None
        if rel.is_external:
            return rel.target
        # Resolve relative to the source part's directory
        source_dir = posixpath.dirname(self._source_uri)
        return posixpath.normpath(posixpath.join(source_dir, rel.target))

    @classmethod
    def from_xml(
        cls, xml_content: bytes, source_uri: str = "/"
    ) -> RelationshipCollection:
        """Parse a ``.rels`` XML file into a collection."""
        coll = cls(source_uri)
        root = etree.fromstring(xml_content)

        for el in root:
            tag = el.tag
            # Strip namespace
            if "}" in tag:
                tag = tag.split("}", 1)[1]
            if tag != "Relationship":
                continue
            rel = Relationship(
                id=el.get("Id", ""),
                type=el.get("Type", ""),
                target=el.get("Target", ""),
                target_mode=el.get("TargetMode", "Internal"),
            )
            coll.add(rel)

        return coll

    def __iter__(self) -> Iterator[Relationship]:
        return iter(self._rels)

    def __len__(self) -> int:
        return len(self._rels)

    def __contains__(self, rel_id: str) -> bool:
        return rel_id in self._by_id


def get_rels_path(part_uri: str) -> str:
    """Compute the ``.rels`` file path for a given part URI.

    >>> get_rels_path("/")
    "/_rels/.rels"
    >>> get_rels_path("/ppt/presentation.xml")
    "/ppt/_rels/presentation.xml.rels"
    """
    if part_uri == "/":
        return "/_rels/.rels"
    directory = posixpath.dirname(part_uri)
    filename = posixpath.basename(part_uri)
    return f"{directory}/_rels/{filename}.rels"
