# -*- coding: utf-8 -*-
# Authors : Alix Chagué <alix.chague@inria.fr>
#           Lucas Terriel <lucas.terriel@inria.fr>
# Licence : MIT
"""
    The ``XML Parser`` module parses XML PAGE / XML ALTO
    ====================================================

    Supports ALTO v2–v4 and PAGE-XML (PcGts) without any third-party dependency.
"""

import xml.etree.ElementTree as ET
from os.path import basename, isfile
from typing import List

from cthulhu.utils._utils import _report_log

__all__ = [
    "_XMLParser"
]


def _strip_namespace(tag: str) -> str:
    """Return the local name of an element tag, stripping any namespace URI."""
    if "}" in tag:
        return tag.split("}", 1)[1]
    return tag


def _detect_format(root: ET.Element) -> str:
    """Detect whether the XML is ALTO or PAGE based on the root element tag.

    Returns 'alto' or 'page'. Raises ValueError for unknown formats.
    """
    local = _strip_namespace(root.tag).lower()
    if local == "alto":
        return "alto"
    if local == "pcgts":
        return "page"
    raise ValueError(
        f"Unknown XML format — root element is <{root.tag}>. "
        "Expected ALTO (<alto>) or PAGE-XML (<PcGts>)."
    )


class _XMLParser:
    """A XML Parser for Cthulhu — supports ALTO (v2–v4) and PAGE-XML (PcGts).

    Parses ground-truth text from structured XML documents used in HTR/OCR
    workflows. No third-party library required.

    Parameters
    ----------
    xml_path : str
        Path to the ALTO or PAGE-XML source file.

    Attributes
    ----------
    file_path : str
        Absolute path to the source file.
    filename : str
        Base filename of the source file.
    fmt : str
        Detected XML format: ``'alto'`` or ``'page'``.
    sentences : list[str]
        List of text lines extracted from the document.
    content : str
        Full text content, one line per ``\\n``.
    """

    def __init__(self, xml_path: str) -> None:
        self.file_path = xml_path
        self.filename = basename(xml_path) if isfile(xml_path) else ""

        try:
            tree = ET.parse(xml_path)
        except ET.ParseError as exc:
            _report_log(
                f"Failed to parse XML file '{xml_path}': {exc}", "E"
            )
            self.fmt = ""
            self.sentences: List[str] = []
            self.content = ""
            return

        root = tree.getroot()

        try:
            self.fmt = _detect_format(root)
        except ValueError as exc:
            _report_log(str(exc), "E")
            self.fmt = ""
            self.sentences = []
            self.content = ""
            return

        # Extract the namespace URI (common to all child elements)
        ns_uri = ""
        if root.tag.startswith("{"):
            ns_uri = root.tag[1:root.tag.index("}")]
        self._ns = {"ns": ns_uri} if ns_uri else {}

        self.sentences = self._extract_lines(root)
        self.content = "\n".join(self.sentences)

    # ------------------------------------------------------------------
    # Private helpers
    # ------------------------------------------------------------------

    def _find_all(self, element: ET.Element, xpath_local: str) -> List[ET.Element]:
        """Find all descendants matching *xpath_local* with or without namespace."""
        if self._ns:
            # Replace each bare tag name with the namespaced version
            ns_xpath = "/".join(
                f"ns:{part}" if not part.startswith("ns:") else part
                for part in xpath_local.split("/")
            )
            return element.findall(f".//{ns_xpath}", self._ns)
        return element.findall(f".//{xpath_local}")

    def _extract_lines(self, root: ET.Element) -> List[str]:
        if self.fmt == "alto":
            return self._extract_alto(root)
        return self._extract_page(root)

    def _extract_alto(self, root: ET.Element) -> List[str]:
        """Extract text from ALTO XML.

        Each ``<TextLine>`` may contain one or more ``<String CONTENT="…"/>``
        elements. Their CONTENT values are joined with a single space.
        """
        lines: List[str] = []
        for textline in self._find_all(root, "TextLine"):
            if self._ns:
                strings = textline.findall("ns:String", self._ns)
            else:
                strings = textline.findall("String")

            parts = [s.get("CONTENT", "") for s in strings if s.get("CONTENT")]
            text = " ".join(parts).strip()
            if text:
                lines.append(text)
        return lines

    def _extract_page(self, root: ET.Element) -> List[str]:
        """Extract text from PAGE-XML.

        Each ``<TextLine>`` contains a ``<TextEquiv><Unicode>…</Unicode></TextEquiv>``
        element whose text is the transcribed content.
        """
        lines: List[str] = []
        for textline in self._find_all(root, "TextLine"):
            if self._ns:
                unicode_elem = textline.find(
                    "ns:TextEquiv/ns:Unicode", self._ns
                )
            else:
                unicode_elem = textline.find("TextEquiv/Unicode")

            if unicode_elem is not None and unicode_elem.text:
                text = unicode_elem.text.strip()
                if text:
                    lines.append(text)
        return lines
