"""
PPTX parser — uses python-pptx for slide-by-slide extraction.

Each slide becomes a section boundary (slide title pushed as heading).
Text shapes, tables, images, and speaker notes are extracted.
"""

from __future__ import annotations

from collections.abc import Iterator

from .base import BaseParser, FileRecord, RawElement


def _table_from_pptx(table) -> RawElement:  # type: ignore[no-untyped-def]
    """Convert a python-pptx Table to a RawElement, detecting merge topology."""
    merge_map: list[dict] = []
    for row_i in range(len(table.rows)):
        for col_i in range(len(table.columns)):
            cell = table.cell(row_i, col_i)
            if cell.is_spanned:
                continue
            merge_map.append(
                {
                    "row": row_i,
                    "col": col_i,
                    "rowspan": cell.span_height if cell.is_merge_origin else 1,
                    "colspan": cell.span_width if cell.is_merge_origin else 1,
                    "value": cell.text_frame.text.strip(),
                }
            )

    if not merge_map:
        return RawElement(
            element_type="table:simple",
            text="",
            extra={"extraction_method": "python-pptx:table", "confidence": 0.90},
        )

    is_complex = any(c["rowspan"] > 1 or c["colspan"] > 1 for c in merge_map)
    element_type = "table:complex" if is_complex else "table:simple"

    # Build flat headers and rows for markdown
    row_groups: dict[int, list[str]] = {}
    for cell in merge_map:
        row_groups.setdefault(cell["row"], []).append(cell["value"])

    sorted_rows = [row_groups[i] for i in sorted(row_groups)]
    headers = sorted_rows[0] if sorted_rows else []
    data_rows = sorted_rows[1:] if len(sorted_rows) > 1 else []
    col_count = len(headers) if headers else (len(data_rows[0]) if data_rows else 0)

    # Markdown (flat)
    md_lines = []
    if headers:
        md_lines.append("| " + " | ".join(headers) + " |")
        md_lines.append("|" + "|".join(" --- " for _ in headers) + "|")
    for row in data_rows:
        padded = list(row) + [""] * max(0, len(headers) - len(row))
        md_lines.append("| " + " | ".join(padded) + " |")
    markdown = "\n".join(md_lines)

    # HTML (only for complex)
    html = None
    if is_complex:
        rows_by_idx: dict[int, list[dict]] = {}
        for cell in merge_map:
            rows_by_idx.setdefault(cell["row"], []).append(cell)
        html_lines = ["<table>"]
        for row_i in sorted(rows_by_idx):
            html_lines.append("  <tr>")
            for cell in sorted(rows_by_idx[row_i], key=lambda c: c["col"]):
                attrs = ""
                if cell["rowspan"] > 1:
                    attrs += f' rowspan="{cell["rowspan"]}"'
                if cell["colspan"] > 1:
                    attrs += f' colspan="{cell["colspan"]}"'
                html_lines.append(f"    <td{attrs}>{cell['value']}</td>")
            html_lines.append("  </tr>")
        html_lines.append("</table>")
        html = "\n".join(html_lines)

    return RawElement(
        element_type=element_type,
        text=markdown or (merge_map[0]["value"] if merge_map else ""),
        table_data={
            "markdown": markdown,
            "structured": {"headers": headers, "rows": data_rows},
            "has_header_row": bool(headers),
            "row_count": len(data_rows),
            "header_row_count": 1 if headers else 0,
            "col_count": col_count,
            "merge_map": merge_map if is_complex else [],
            "html": html,
            "table_extraction_method": "pptx-native",
        },
        extra={"extraction_method": "python-pptx:table", "confidence": 0.90},
    )


class PPTXParser(BaseParser):
    handles = [
        ("application/vnd.openxmlformats-officedocument.presentationml.presentation", None),
    ]

    def parse(self, file_record: FileRecord) -> Iterator[RawElement]:
        try:
            from pptx import Presentation  # type: ignore[import-untyped]
        except ImportError as e:
            raise ImportError("python-pptx required: pip install python-pptx") from e

        prs = Presentation(str(file_record.path))

        for slide_num, slide in enumerate(prs.slides, start=1):
            # Extract slide title
            slide_title = f"Slide {slide_num}"
            for shape in slide.shapes:
                if shape.has_text_frame and shape.shape_type == 13:  # MSO_SHAPE_TYPE.PLACEHOLDER
                    pass
                try:
                    ph_fmt = (
                        shape.placeholder_format if hasattr(shape, "placeholder_format") else None
                    )
                except ValueError:
                    ph_fmt = None
                if ph_fmt is not None:
                    ph_idx = ph_fmt.idx
                    if ph_idx == 0 and shape.has_text_frame:  # title placeholder
                        title_text = shape.text_frame.text.strip()
                        if title_text:
                            slide_title = title_text
                        break

            # Emit slide as section heading
            yield RawElement(
                element_type="structural:section_header",
                text=slide_title,
                heading_level=1,
                detection_tier=1,
                page=slide_num,
                extra={
                    "extraction_method": "python-pptx:slide-title",
                    "confidence": 0.98,
                },
            )

            # Process all shapes on this slide
            for shape in slide.shapes:
                # Skip title (already emitted as heading)
                try:
                    ph_fmt2 = (
                        shape.placeholder_format if hasattr(shape, "placeholder_format") else None
                    )
                except ValueError:
                    ph_fmt2 = None
                is_title = ph_fmt2 is not None and ph_fmt2.idx == 0
                if is_title:
                    continue

                # Table shape
                if shape.has_table:
                    yield _table_from_pptx(shape.table)
                    continue

                # Text frame — emit bullet paragraphs as list:item, others as narrative
                if shape.has_text_frame:
                    narrative_lines: list[str] = []

                    def _flush_narrative(lines: list[str], slide_num: int) -> Iterator[RawElement]:
                        combined = "\n".join(lines).strip()
                        if combined:
                            yield RawElement(
                                element_type="text:narrative",
                                text=combined,
                                page=slide_num,
                                extra={
                                    "extraction_method": "python-pptx:text-frame",
                                    "confidence": 0.90,
                                },
                            )

                    for para in shape.text_frame.paragraphs:
                        para_text = para.text.strip()
                        if not para_text:
                            continue
                        # Detect bullet: explicit bullet format OR leading bullet characters
                        is_bullet = False
                        try:
                            pPr = para._p.get_or_add_pPr()
                            pPr.find(
                                "{http://schemas.openxmlformats.org/drawingml/2006/main}buNone"
                            )
                            buChar = pPr.find(
                                "{http://schemas.openxmlformats.org/drawingml/2006/main}buChar"
                            )
                            buAutoNum = pPr.find(
                                "{http://schemas.openxmlformats.org/drawingml/2006/main}buAutoNum"
                            )
                            if buChar is not None or buAutoNum is not None:
                                is_bullet = True
                        except Exception:
                            pass
                        # Also detect leading bullet characters
                        if not is_bullet and para_text and para_text[0] in "•·‣▸▶-":
                            is_bullet = True
                            para_text = para_text.lstrip("•·‣▸▶- ").strip()

                        if is_bullet:
                            yield from _flush_narrative(narrative_lines, slide_num)
                            narrative_lines = []
                            if para_text:
                                yield RawElement(
                                    element_type="list:item",
                                    text=para_text,
                                    page=slide_num,
                                    extra={
                                        "extraction_method": "python-pptx:bullet",
                                        "confidence": 0.92,
                                    },
                                )
                        else:
                            narrative_lines.append(para_text)

                    yield from _flush_narrative(narrative_lines, slide_num)

                # Image shape
                if shape.shape_type == 13:  # MSO_SHAPE_TYPE.PICTURE
                    yield RawElement(
                        element_type="media:image",
                        text="",
                        page=slide_num,
                        image_data={
                            "image_ref": None,
                            "image_sha256": None,
                            "format": None,
                            "width_px": int(shape.width.pt) if shape.width else None,
                            "height_px": int(shape.height.pt) if shape.height else None,
                            "alt_text": shape.name,
                            "ocr_text": None,
                            "storage": "skip",
                        },
                        extra={
                            "extraction_method": "python-pptx:image",
                            "confidence": 0.85,
                        },
                    )

            # Speaker notes
            if slide.has_notes_slide:
                notes_text = slide.notes_slide.notes_text_frame.text.strip()
                if notes_text:
                    yield RawElement(
                        element_type="text:narrative",
                        text=notes_text,
                        page=slide_num,
                        extra={
                            "extraction_method": "python-pptx:speaker-notes",
                            "confidence": 1.0,
                            "is_speaker_note": True,
                        },
                    )
