"""
QTI XML Parsing Utilities

Lightweight, dependency-free utilities for extracting structured data from
QTI 3.0 XML assessment items. Works in any Python runtime.

Example:
    ```python
    from timeback_qti.parse import parse_qti_xml

    item = parse_qti_xml(raw_xml)
    print(item.prompt, item.choices, item.correct_answer)
    ```
"""

from __future__ import annotations

import re

from .parse_types import (
    ParsedQtiItem,
    QtiChoice,
    QtiInlineFeedback,
    QtiInteractionAttributes,
    QtiModalFeedback,
    QtiResponseDeclaration,
)

__all__ = [
    "ParsedQtiItem",
    "QtiChoice",
    "QtiInlineFeedback",
    "QtiInteractionAttributes",
    "QtiModalFeedback",
    "QtiResponseDeclaration",
    "extract_choices",
    "extract_correct_response",
    "extract_feedback",
    "extract_interaction_attributes",
    "extract_modal_feedback",
    "extract_prompt",
    "extract_response_declaration",
    "parse_qti_xml",
]

# ═══════════════════════════════════════════════════════════════════════════════
# INDIVIDUAL EXTRACTORS
# ═══════════════════════════════════════════════════════════════════════════════


def extract_prompt(xml: str) -> str:
    """Extract the prompt text from a ``<qti-prompt>`` element.

    Args:
        xml: Raw QTI XML string.

    Returns:
        The inner HTML of the prompt, or an empty string if not found.
    """
    if not xml:
        return ""

    match = re.search(r"<qti-prompt[^>]*>([\s\S]*?)</qti-prompt>", xml, re.IGNORECASE)
    return match.group(1).strip() if match else ""


def extract_choices(xml: str) -> list[QtiChoice]:
    """Extract simple choices from ``<qti-simple-choice>`` elements.

    Inline feedback elements within each choice are stripped from the returned
    text. Use :func:`extract_feedback` to retrieve them separately.

    Args:
        xml: Raw QTI XML string.

    Returns:
        List of choices with identifier and cleaned text.
    """
    if not xml:
        return []

    choices: list[QtiChoice] = []
    pattern = re.compile(
        r'<qti-simple-choice[^>]*(?:^|\s)identifier="([^"]+)"[^>]*>([\s\S]*?)</qti-simple-choice>',
        re.IGNORECASE,
    )

    for match in pattern.finditer(xml):
        text = match.group(2) or ""
        text = re.sub(
            r"<qti-feedback-inline[\s\S]*?</qti-feedback-inline>", "", text, flags=re.IGNORECASE
        )
        text = text.strip()
        choices.append(QtiChoice(identifier=match.group(1) or "", text=text))

    return choices


def extract_correct_response(xml: str) -> str | None:
    """Extract the correct answer identifier from ``<qti-correct-response>``.

    Args:
        xml: Raw QTI XML string.

    Returns:
        The correct answer identifier, or ``None`` if not found.
    """
    if not xml:
        return None

    match = re.search(
        r"<qti-correct-response>\s*<qti-value>([^<]+)</qti-value>", xml, re.IGNORECASE
    )
    return match.group(1).strip() if match else None


def extract_feedback(xml: str) -> list[QtiInlineFeedback]:
    """Extract inline feedback from ``<qti-feedback-inline>`` elements.

    Args:
        xml: Raw QTI XML string.

    Returns:
        List of inline feedback entries.
    """
    if not xml:
        return []

    feedback: list[QtiInlineFeedback] = []
    pattern = re.compile(
        r"<qti-feedback-inline([^>]*)>([\s\S]*?)</qti-feedback-inline>",
        re.IGNORECASE,
    )

    for match in pattern.finditer(xml):
        attrs = match.group(1) or ""
        outcome_id = re.search(r'outcome-identifier="([^"]*)"', attrs)
        identifier = re.search(r'(?:^|\s)identifier="([^"]*)"', attrs)
        show_hide = re.search(r'show-hide="([^"]*)"', attrs)

        feedback.append(
            QtiInlineFeedback(
                outcome_identifier=outcome_id.group(1) if outcome_id else "",
                identifier=identifier.group(1) if identifier else "",
                show_hide=show_hide.group(1) if show_hide else "show",
                content=(match.group(2) or "").strip(),
            )
        )

    return feedback


def extract_modal_feedback(xml: str) -> list[QtiModalFeedback]:
    """Extract modal feedback from ``<qti-modal-feedback>`` elements.

    Args:
        xml: Raw QTI XML string.

    Returns:
        List of modal feedback entries.
    """
    if not xml:
        return []

    modal_feedback: list[QtiModalFeedback] = []
    pattern = re.compile(
        r"<qti-modal-feedback([^>]*)>([\s\S]*?)</qti-modal-feedback>",
        re.IGNORECASE,
    )

    for match in pattern.finditer(xml):
        attrs = match.group(1) or ""
        outcome_id = re.search(r'outcome-identifier="([^"]*)"', attrs)
        identifier = re.search(r'(?:^|\s)identifier="([^"]*)"', attrs)
        show_hide = re.search(r'show-hide="([^"]*)"', attrs)

        content = match.group(2) or ""
        body_match = re.search(
            r"<qti-content-body>([\s\S]*?)</qti-content-body>", content, re.IGNORECASE
        )
        if body_match:
            content = body_match.group(1) or ""

        modal_feedback.append(
            QtiModalFeedback(
                outcome_identifier=outcome_id.group(1) if outcome_id else "",
                identifier=identifier.group(1) if identifier else "",
                show_hide=show_hide.group(1) if show_hide else "show",
                content=content.strip(),
            )
        )

    return modal_feedback


def extract_interaction_attributes(xml: str) -> QtiInteractionAttributes | None:
    """Extract interaction attributes from ``<qti-choice-interaction>``.

    Args:
        xml: Raw QTI XML string.

    Returns:
        Interaction attributes, or ``None`` if no choice interaction found.
    """
    if not xml:
        return None

    match = re.search(r"<qti-choice-interaction([^>]*)>", xml, re.IGNORECASE)
    if not match:
        return None

    attrs = match.group(1) or ""

    response_id = re.search(r'response-identifier="([^"]*)"', attrs)
    shuffle = re.search(r'shuffle="([^"]*)"', attrs)
    max_choices = re.search(r'max-choices="([^"]*)"', attrs)

    return QtiInteractionAttributes(
        response_identifier=response_id.group(1) if response_id else "",
        shuffle=shuffle.group(1).lower() == "true" if shuffle else False,
        max_choices=int(max_choices.group(1)) if max_choices else 1,
    )


def extract_response_declaration(xml: str) -> QtiResponseDeclaration | None:
    """Extract the response declaration from ``<qti-response-declaration>``.

    Args:
        xml: Raw QTI XML string.

    Returns:
        Response declaration, or ``None`` if not found.
    """
    if not xml:
        return None

    match = re.search(r"<qti-response-declaration([^>]*)>", xml, re.IGNORECASE)
    if not match:
        return None

    attrs = match.group(1) or ""

    identifier = re.search(r'identifier="([^"]*)"', attrs)
    cardinality = re.search(r'cardinality="([^"]*)"', attrs)
    base_type = re.search(r'base-type="([^"]*)"', attrs)

    return QtiResponseDeclaration(
        identifier=identifier.group(1) if identifier else "",
        cardinality=cardinality.group(1) if cardinality else "",
        base_type=base_type.group(1) if base_type else None,
    )


# ═══════════════════════════════════════════════════════════════════════════════
# ALL-IN-ONE PARSER
# ═══════════════════════════════════════════════════════════════════════════════


def parse_qti_xml(xml: str) -> ParsedQtiItem:
    """Parse a QTI 3.0 assessment item XML string into structured data.

    Combines all individual extractors into a single result. For more control,
    use the individual ``extract_*`` functions directly.

    Args:
        xml: Raw QTI XML string.

    Returns:
        Complete parsed item data.

    Example:
        ```python
        from timeback_qti.parse import parse_qti_xml

        item = parse_qti_xml(xml)
        print(item.prompt)             # "What is 2 + 2?"
        print(item.choices)            # [QtiChoice(identifier="A", text="3"), ...]
        print(item.correct_answer)     # "B"
        print(item.feedback)           # [QtiInlineFeedback(...), ...]
        print(item.modal_feedback)     # [QtiModalFeedback(...), ...]
        ```
    """
    return ParsedQtiItem(
        prompt=extract_prompt(xml),
        choices=extract_choices(xml),
        correct_answer=extract_correct_response(xml),
        feedback=extract_feedback(xml),
        modal_feedback=extract_modal_feedback(xml),
        interaction=extract_interaction_attributes(xml),
        response_declaration=extract_response_declaration(xml),
    )
