"""
Private helper functions and constants for docx processing.

This module is internal - do not import directly from external code.
"""

import itertools
import logging
import re
from typing import Generator, Iterable, Optional, Union

from docx.document import Document as DocumentType
from docx.oxml.table import CT_Tbl
from docx.oxml.text.paragraph import CT_P
from docx.table import Table, _Cell
from docx.text.paragraph import Paragraph

from .models import EbdChapterInformation

_logger = logging.getLogger(__name__)

# Regex patterns for EBD key detection
EBD_KEY_PATTERN = re.compile(r"^E_\d{4}$")
EBD_KEY_WITH_HEADING_PATTERN = re.compile(r"^(?P<key>E_\d{4})_?(?P<title>.*)\s*$")

EBD_CELL_PATTERN = re.compile(r"^(?:ja|nein)\s*(?:Ende|\d+)$")
"""
Any EBD table shall contain at least one cell that matches this pattern.
"""

DOCX_ARROW_CHAR = "\uf0e0"
"""
U+F0E0: Private Use Area character representing a right arrow in DOCX documents.
This character is used by MS Word to render arrows (e.g., "ja → 5") in EBD tables.
It appears in cells like "ja  5" to indicate the subsequent step number.
"""


def get_tables_and_paragraphs(document: DocumentType) -> Generator[Union[Table, Paragraph], None, None]:
    """
    Yields tables and paragraphs from the given document in the order in which they occur in the document.
    This is helpful because document.tables and document.paragraphs are de-coupled and give you no information which
    paragraph follows which table.
    """
    parent_elements = document.element.body
    for item in parent_elements.iterchildren():
        if isinstance(item, CT_P):
            yield Paragraph(item, document)
        elif isinstance(item, CT_Tbl):
            yield Table(item, document)
        else:
            _logger.debug("Item %s is neither Paragraph nor Table", str(item))


def cell_is_probably_from_an_ebd_cell(cell: _Cell) -> bool:
    """Check if a cell likely belongs to an EBD table based on its content."""
    if DOCX_ARROW_CHAR in cell.text:
        return True
    if cell.text in {"ja", "nein"}:
        return True
    if "à" in cell.text:
        # the rightarrow in wrong encoding
        return True
    if EBD_CELL_PATTERN.match(cell.text):
        return True
    if cell.text.strip().startswith("Cluster:") or cell.text.startswith("Hinweis:"):
        return True
    return False


def table_is_an_ebd_table(table: Table) -> bool:
    """
    Returns true iff the table "looks like" an EB-Table.
    This is to distinguish between tables that are inside the same subsection that describes an EBD but are not part
    of the decision tree at all (e.g. in E_0406 the tables about Artikel-IDs).
    """
    if table_is_first_ebd_table(table):
        return True
    for row in table.rows:
        try:
            for cell in row.cells:
                if cell_is_probably_from_an_ebd_cell(cell):
                    return True
        except IndexError:  # don't ask me why this happens; It's the internals of python-docx
            continue
    return False


def table_is_first_ebd_table(table: Table) -> bool:
    """
    Returns true if the first row of a table contains "Prüfende Rolle".
    We assume that each EBD table has a header row with
    "Prüfende Rolle" in the first column.
    """
    return "prüfende rolle" in table.rows[0].cells[0].text.lower()


def is_heading(paragraph: Paragraph) -> bool:
    """
    Returns True if the paragraph is a heading.
    """
    return paragraph.style is not None and paragraph.style.style_id in {
        "berschrift1",
        "berschrift2",
        "berschrift3",
    }


def enrich_paragraphs_with_sections(
    paragraphs: Iterable[Paragraph],
) -> Generator[tuple[Paragraph, EbdChapterInformation], None, None]:
    """
    Yield each paragraph + the "Kapitel" in which it is found.
    """
    chapter_counter = itertools.count(start=1)
    chapter = 1
    chapter_title: Optional[str] = None
    section_counter = itertools.count(start=1)
    section = 1
    section_title: Optional[str] = None
    subsection_counter = itertools.count(start=1)
    subsection = 1
    subsection_title: Optional[str] = None
    for paragraph in paragraphs:
        # since pyton-docx 1.1.2 there are type hints; seems like the style is not guaranteed to be not None
        match paragraph.style.style_id:  # type:ignore[union-attr]
            case "berschrift1":
                chapter = next(chapter_counter)
                chapter_title = paragraph.text.strip()
                section_counter = itertools.count(start=1)
                section_title = None
                subsection_counter = itertools.count(start=1)
                subsection_title = None
            case "berschrift2":
                section = next(section_counter)
                section_title = paragraph.text.strip()
                subsection_counter = itertools.count(start=1)
                subsection_title = None
            case "berschrift3":
                subsection = next(subsection_counter)
                subsection_title = paragraph.text.strip()
        location = EbdChapterInformation(
            chapter=chapter,
            section=section,
            subsection=subsection,
            chapter_title=chapter_title,
            section_title=section_title,
            subsection_title=subsection_title,
        )
        _logger.debug("Handling Paragraph %i.%i.%i", chapter, section, subsection)
        yield paragraph, location
