"""Document content injection helpers for MCP document tools."""

from __future__ import annotations

import zipfile
from io import BytesIO
from typing import Any, Dict, List, Optional, Tuple

from lxml import etree

from tokenmoulds.emitters import TemplateResolver
from tokenmoulds.mcp.tools.token_utils import escape_xml as _escape_xml

WORD_NSMAP = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}


def _inject_word_content(
    template_bytes: bytes,
    content_blocks: List[Dict[str, Any]],
    style_context: Dict[str, Any],
    title: Optional[str] = None,
    metadata: Optional[Dict[str, Any]] = None,
) -> bytes:
    """Inject content blocks into a Word template.

    Follows the same pattern as the existing injector module.
    """
    global _hyperlink_tracker

    if not content_blocks:
        return _convert_word_template_to_document(template_bytes)

    # Initialize hyperlink tracker for this document
    _hyperlink_tracker = HyperlinkTracker()

    templates = TemplateResolver("word")

    input_zip = BytesIO(template_bytes)
    output_zip = BytesIO()

    with zipfile.ZipFile(input_zip, "r") as zin:
        with zipfile.ZipFile(
            output_zip, "w", zipfile.ZIP_DEFLATED, compresslevel=9
        ) as zout:
            for item in zin.namelist():
                data = zin.read(item)

                if item == "word/document.xml":
                    data = _inject_into_word_document(
                        data, content_blocks, templates, style_context
                    )
                elif item == "word/_rels/document.xml.rels":
                    # Add hyperlink and image relationships
                    data = _inject_relationships(
                        data, _hyperlink_tracker.links, _hyperlink_tracker.images
                    )
                elif item == "[Content_Types].xml":
                    data = _update_content_types(data, _hyperlink_tracker.images)
                elif item == "docProps/core.xml" and metadata:
                    data = _inject_word_metadata(data, title, metadata)

                zout.writestr(item, data)

            # Write images to word/media/
            for img in _hyperlink_tracker.images:
                img_path = f"word/media/{img['filename']}"
                zout.writestr(img_path, img["data"])

    # Reset tracker
    _hyperlink_tracker = None

    return output_zip.getvalue()


def _convert_word_template_to_document(template_bytes: bytes) -> bytes:
    """Convert Word template to document format without content changes."""
    input_zip = BytesIO(template_bytes)
    output_zip = BytesIO()

    with zipfile.ZipFile(input_zip, "r") as zin:
        with zipfile.ZipFile(
            output_zip, "w", zipfile.ZIP_DEFLATED, compresslevel=9
        ) as zout:
            for item in zin.namelist():
                data = zin.read(item)
                if item == "[Content_Types].xml":
                    data = _update_content_types(data, [])  # No images
                zout.writestr(item, data)

    return output_zip.getvalue()


def _inject_relationships(
    rels_xml: bytes,
    links: List[Dict[str, str]],
    images: List[Dict[str, Any]],
) -> bytes:
    """Add hyperlink and image relationships to document.xml.rels."""
    if not links and not images:
        return rels_xml

    RELS_NS = "http://schemas.openxmlformats.org/package/2006/relationships"
    root = etree.fromstring(rels_xml)

    # Add hyperlink relationships
    for link in links:
        rel = etree.SubElement(root, f"{{{RELS_NS}}}Relationship")
        rel.set("Id", link["id"])
        rel.set(
            "Type",
            "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
        )
        rel.set("Target", link["url"])
        rel.set("TargetMode", "External")

    # Add image relationships
    for img in images:
        rel = etree.SubElement(root, f"{{{RELS_NS}}}Relationship")
        rel.set("Id", img["id"])
        rel.set(
            "Type",
            "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
        )
        rel.set("Target", f"media/{img['filename']}")

    return etree.tostring(root, xml_declaration=True, encoding="UTF-8")


def _update_content_types(
    content_types_xml: bytes, images: List[Dict[str, Any]]
) -> bytes:
    """Update Content_Types.xml for template->document conversion and image types."""
    xml_str = content_types_xml.decode("utf-8")

    # Convert template to document
    xml_str = xml_str.replace(
        "wordprocessingml.template.main+xml", "wordprocessingml.document.main+xml"
    )

    # Add image content type extensions if we have images
    if images:
        CT_NS = "http://schemas.openxmlformats.org/package/2006/content-types"
        root = etree.fromstring(xml_str.encode("utf-8"))

        # Collect unique extensions
        extensions_needed = set()
        for img in images:
            ext = img["filename"].rsplit(".", 1)[-1].lower()
            extensions_needed.add(ext)

        # Check which extensions already exist
        existing_extensions = set()
        for default in root.findall(f"{{{CT_NS}}}Default"):
            ext = default.get("Extension", "").lower()
            existing_extensions.add(ext)

        # Add missing extension types
        mime_types = {
            "png": "image/png",
            "jpeg": "image/jpeg",
            "jpg": "image/jpeg",
            "gif": "image/gif",
            "bmp": "image/bmp",
            "tiff": "image/tiff",
        }

        for ext in extensions_needed:
            if ext not in existing_extensions:
                mime = mime_types.get(ext, "application/octet-stream")
                default = etree.SubElement(root, f"{{{CT_NS}}}Default")
                default.set("Extension", ext)
                default.set("ContentType", mime)

        return etree.tostring(root, xml_declaration=True, encoding="UTF-8")

    return xml_str.encode("utf-8")


def _inject_into_word_document(
    doc_xml: bytes,
    content_blocks: List[Dict[str, Any]],
    templates: TemplateResolver,
    style_context: Dict[str, Any],
) -> bytes:
    """Inject content blocks into document.xml."""
    root = etree.fromstring(doc_xml)
    body = root.find(".//w:body", WORD_NSMAP)

    if body is None:
        return doc_xml

    # Preserve sectPr
    sect_pr = body.find("w:sectPr", WORD_NSMAP)

    # Remove existing paragraphs
    for p in body.findall("w:p", WORD_NSMAP):
        body.remove(p)

    # Add content blocks
    for block in content_blocks:
        elements = _render_word_content_block(block, templates, style_context)
        for el in elements:
            body.append(el)

    # Ensure sectPr is at the end
    if sect_pr is not None:
        body.remove(sect_pr)
        body.append(sect_pr)

    return etree.tostring(root, xml_declaration=True, encoding="UTF-8")


def _render_word_content_block(
    block: Dict[str, Any],
    templates: TemplateResolver,
    style_context: Dict[str, Any],
) -> List[etree._Element]:
    """Render a content block to Word XML elements."""
    block_type = block.get("type", "paragraph")
    runs = _get_runs_from_block(block)
    text = _get_text_from_block(block)
    items = block.get("items", [])
    rows = block.get("rows", [])
    level = block.get("indent", block.get("level", 0))

    # Check if we have formatted runs
    has_formatting = "runs" in block and any(
        r.get("bold")
        or r.get("italic")
        or r.get("strikethrough")
        or r.get("code")
        or r.get("link")
        for r in block.get("runs", [])
    )

    if block_type == "title":
        if has_formatting:
            return [_render_word_paragraph_with_runs(templates, runs, "Title")]
        return [_render_word_heading(templates, "Title", text)]

    elif block_type in ("heading1", "heading2", "heading3", "heading4"):
        level_num = block_type[-1]
        if has_formatting:
            return [
                _render_word_paragraph_with_runs(templates, runs, f"Heading{level_num}")
            ]
        return [_render_word_heading(templates, f"Heading{level_num}", text)]

    elif block_type == "paragraph":
        if has_formatting:
            return [_render_word_paragraph_with_runs(templates, runs, "BodyText")]
        return [_render_word_paragraph(templates, text)]

    elif block_type in ("quote", "block_quote"):
        if has_formatting:
            return [_render_word_paragraph_with_runs(templates, runs, "Quote")]
        return [_render_word_heading(templates, "Quote", text)]

    elif block_type == "code_block":
        # Use a monospace style if available
        return [_render_word_heading(templates, "Code", text)]

    elif block_type == "bullet_list":
        # Handle new format with runs
        if runs:
            return [
                _render_word_list_item_with_runs(
                    templates, runs, numbered=False, level=level
                )
            ]
        return _render_word_list(templates, items, numbered=False, level=level)

    elif block_type == "numbered_list":
        if runs:
            return [
                _render_word_list_item_with_runs(
                    templates, runs, numbered=True, level=level
                )
            ]
        return _render_word_list(templates, items, numbered=True, level=level)

    elif block_type == "table":
        table_style = style_context.get("table_style", "LightGrid-Accent1")
        return [_render_word_table_with_runs(templates, rows, table_style)]

    elif block_type == "horizontal_rule":
        return [_render_word_horizontal_rule()]

    elif block_type == "page_break":
        return [_render_word_page_break()]

    return []


def _render_word_heading(
    templates: TemplateResolver, style_id: str, text: str
) -> etree._Element:
    """Render a heading using template."""
    xml = templates.read_text("paragraph_heading.xml")
    xml = xml.replace("${STYLE_ID}", style_id)
    xml = xml.replace("${TEXT}", text)
    return etree.fromstring(xml.encode("utf-8"))


class HyperlinkTracker:
    """Tracks hyperlinks for relationship generation."""

    def __init__(self):
        self.links: List[Dict[str, str]] = []
        self.images: List[Dict[str, Any]] = []
        self._next_id = 100  # Start at rId100 to avoid conflicts

    def add_link(self, url: str) -> str:
        """Add a hyperlink and return its relationship ID."""
        rid = f"rId{self._next_id}"
        self._next_id += 1
        self.links.append({"id": rid, "url": url})
        return rid

    def add_image(self, url: str, data: bytes, ext: str) -> str:
        """Add an image and return its relationship ID."""
        rid = f"rId{self._next_id}"
        self._next_id += 1
        filename = f"image{len(self.images) + 1}.{ext}"
        self.images.append(
            {
                "id": rid,
                "url": url,
                "data": data,
                "filename": filename,
            }
        )
        return rid


# Global tracker for current document (reset per document)
_hyperlink_tracker: Optional[HyperlinkTracker] = None


def _fetch_image(url: str) -> Optional[Tuple[bytes, str]]:
    """Fetch image from URL or file path.

    Returns (data, extension) or None if failed.
    """
    import urllib.request
    from pathlib import Path

    try:
        # Check if it's a local file
        if url.startswith("/") or url.startswith("file://"):
            path = Path(url.replace("file://", ""))
            if path.exists():
                ext = path.suffix.lstrip(".").lower()
                if ext in ("jpg", "jpeg"):
                    ext = "jpeg"
                elif ext == "png":
                    ext = "png"
                else:
                    ext = "png"  # Default
                return path.read_bytes(), ext

        # Fetch from URL
        req = urllib.request.Request(url, headers={"User-Agent": "TokenMoulds/1.0"})
        with urllib.request.urlopen(req, timeout=10) as response:
            data = response.read()
            content_type = response.headers.get("Content-Type", "")

            if "png" in content_type:
                ext = "png"
            elif "jpeg" in content_type or "jpg" in content_type:
                ext = "jpeg"
            elif "gif" in content_type:
                ext = "gif"
            else:
                # Guess from URL
                if ".png" in url.lower():
                    ext = "png"
                elif ".gif" in url.lower():
                    ext = "gif"
                else:
                    ext = "jpeg"

            return data, ext

    except Exception:
        return None


def _render_inline_image_xml(rid: str, alt: str, cx: int, cy: int) -> str:
    """Render an inline image as OOXML drawing element."""
    W_NS = WORD_NSMAP["w"]
    R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
    WP_NS = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
    A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main"
    PIC_NS = "http://schemas.openxmlformats.org/drawingml/2006/picture"

    # Generate unique IDs
    import random

    doc_id = random.randint(1, 999999)

    return f"""<w:r xmlns:w="{W_NS}">
        <w:drawing>
            <wp:inline xmlns:wp="{WP_NS}" distT="0" distB="0" distL="0" distR="0">
                <wp:extent cx="{cx}" cy="{cy}"/>
                <wp:docPr id="{doc_id}" name="{alt}"/>
                <a:graphic xmlns:a="{A_NS}">
                    <a:graphicData uri="{PIC_NS}">
                        <pic:pic xmlns:pic="{PIC_NS}">
                            <pic:nvPicPr>
                                <pic:cNvPr id="{doc_id}" name="{alt}"/>
                                <pic:cNvPicPr/>
                            </pic:nvPicPr>
                            <pic:blipFill>
                                <a:blip xmlns:r="{R_NS}" r:embed="{rid}"/>
                                <a:stretch><a:fillRect/></a:stretch>
                            </pic:blipFill>
                            <pic:spPr>
                                <a:xfrm>
                                    <a:off x="0" y="0"/>
                                    <a:ext cx="{cx}" cy="{cy}"/>
                                </a:xfrm>
                                <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
                            </pic:spPr>
                        </pic:pic>
                    </a:graphicData>
                </a:graphic>
            </wp:inline>
        </w:drawing>
    </w:r>"""


def _render_runs_xml(runs: List[Dict[str, Any]]) -> str:
    """Render a list of text runs with formatting to OOXML.

    Each run can have: text, bold, italic, strikethrough, code, link
    """
    global _hyperlink_tracker
    W_NS = WORD_NSMAP["w"]
    R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
    parts = []

    for run in runs:
        text = _escape_xml(run.get("text", ""))
        if not text:
            continue

        # Build run properties
        rpr_parts = []
        if run.get("bold"):
            rpr_parts.append(f'<w:b xmlns:w="{W_NS}"/>')
        if run.get("italic"):
            rpr_parts.append(f'<w:i xmlns:w="{W_NS}"/>')
        if run.get("strikethrough"):
            rpr_parts.append(f'<w:strike xmlns:w="{W_NS}"/>')
        if run.get("code"):
            rpr_parts.append(
                f'<w:rFonts xmlns:w="{W_NS}" w:ascii="Consolas" w:hAnsi="Consolas"/>'
            )
            rpr_parts.append(f'<w:shd xmlns:w="{W_NS}" w:val="clear" w:fill="E8E8E8"/>')

        # Links get blue underline style
        if run.get("link"):
            rpr_parts.append(f'<w:color xmlns:w="{W_NS}" w:val="0563C1"/>')
            rpr_parts.append(f'<w:u xmlns:w="{W_NS}" w:val="single"/>')

        rpr_xml = ""
        if rpr_parts:
            rpr_xml = f'<w:rPr xmlns:w="{W_NS}">' + "".join(rpr_parts) + "</w:rPr>"

        # Handle line breaks
        if run.get("linebreak"):
            parts.append(f'<w:r xmlns:w="{W_NS}">{rpr_xml}<w:br/></w:r>')
        elif run.get("image") and _hyperlink_tracker:
            # Render as inline image
            img_url = run.get("url", "")
            alt = _escape_xml(run.get("alt", "Image"))
            img_result = _fetch_image(img_url) if img_url else None

            if img_result:
                img_data, img_ext = img_result
                rid = _hyperlink_tracker.add_image(img_url, img_data, img_ext)
                # Default size: 4 inches wide, 4:3 aspect
                from tokenmoulds.design.layout_defaults import inline_image_emu

                cx, cy = inline_image_emu()
                parts.append(_render_inline_image_xml(rid, alt, cx, cy))
            else:
                # Fallback to placeholder text
                text_xml = f'<w:t xmlns:w="{W_NS}" xml:space="preserve">{text}</w:t>'
                parts.append(f'<w:r xmlns:w="{W_NS}">{rpr_xml}{text_xml}</w:r>')
        elif run.get("link") and _hyperlink_tracker:
            # Render as hyperlink
            url = run["link"]
            rid = _hyperlink_tracker.add_link(url)
            text_xml = f'<w:t xmlns:w="{W_NS}" xml:space="preserve">{text}</w:t>'
            run_xml = f'<w:r xmlns:w="{W_NS}">{rpr_xml}{text_xml}</w:r>'
            parts.append(
                f'<w:hyperlink xmlns:w="{W_NS}" xmlns:r="{R_NS}" r:id="{rid}">{run_xml}</w:hyperlink>'
            )
        else:
            text_xml = f'<w:t xmlns:w="{W_NS}" xml:space="preserve">{text}</w:t>'
            parts.append(f'<w:r xmlns:w="{W_NS}">{rpr_xml}{text_xml}</w:r>')

    return "".join(parts)


def _get_text_from_block(block: Dict[str, Any]) -> str:
    """Get plain text from block, handling both 'text' and 'runs' formats."""
    if "text" in block:
        return _escape_xml(block["text"])
    if "runs" in block:
        return "".join(_escape_xml(r.get("text", "")) for r in block["runs"])
    return ""


def _get_runs_from_block(block: Dict[str, Any]) -> List[Dict[str, Any]]:
    """Get runs from block, converting 'text' to runs if needed."""
    if "runs" in block:
        return block["runs"]
    if "text" in block:
        return [{"text": block["text"]}]
    return []


def _render_word_paragraph(templates: TemplateResolver, text: str) -> etree._Element:
    """Render a body paragraph using template."""
    xml = templates.read_text("paragraph_body.xml")
    xml = xml.replace("${TEXT}", text)
    return etree.fromstring(xml.encode("utf-8"))


def _render_word_paragraph_with_runs(
    templates: TemplateResolver, runs: List[Dict[str, Any]], style: str = "BodyText"
) -> etree._Element:
    """Render a paragraph with formatted runs."""
    W_NS = WORD_NSMAP["w"]
    runs_xml = _render_runs_xml(runs)

    xml = f"""<w:p xmlns:w="{W_NS}">
        <w:pPr><w:pStyle w:val="{style}"/></w:pPr>
        {runs_xml}
    </w:p>"""
    return etree.fromstring(xml.encode("utf-8"))


def _render_word_list(
    templates: TemplateResolver,
    items: List[str],
    numbered: bool = False,
    level: int = 0,
) -> List[etree._Element]:
    """Render list items using template."""
    elements = []
    num_id = "2" if numbered else "1"

    for item in items:
        xml = templates.read_text("paragraph_list_item.xml")
        xml = xml.replace("${LEVEL}", str(level))
        xml = xml.replace("${NUM_ID}", num_id)
        xml = xml.replace("${TEXT}", _escape_xml(item))
        elements.append(etree.fromstring(xml.encode("utf-8")))

    return elements


def _render_word_list_item_with_runs(
    templates: TemplateResolver,
    runs: List[Dict[str, Any]],
    numbered: bool = False,
    level: int = 0,
) -> etree._Element:
    """Render a single list item with formatted runs."""
    W_NS = WORD_NSMAP["w"]
    num_id = "2" if numbered else "1"
    runs_xml = _render_runs_xml(runs)

    xml = f"""<w:p xmlns:w="{W_NS}">
        <w:pPr>
            <w:pStyle w:val="ListParagraph"/>
            <w:numPr>
                <w:ilvl w:val="{level}"/>
                <w:numId w:val="{num_id}"/>
            </w:numPr>
        </w:pPr>
        {runs_xml}
    </w:p>"""
    return etree.fromstring(xml.encode("utf-8"))


def _render_word_horizontal_rule() -> etree._Element:
    """Render a horizontal rule as a paragraph with bottom border."""
    W_NS = WORD_NSMAP["w"]
    xml = f"""<w:p xmlns:w="{W_NS}">
        <w:pPr>
            <w:pBdr>
                <w:bottom w:val="single" w:sz="6" w:space="1" w:color="auto"/>
            </w:pBdr>
        </w:pPr>
    </w:p>"""
    return etree.fromstring(xml.encode("utf-8"))


def _render_word_table_with_runs(
    templates: TemplateResolver,
    rows: List[List[Dict[str, Any]]],
    table_style: str,
) -> etree._Element:
    """Render a table with formatted cell content.

    Each cell is a dict with 'runs' and optional 'header' flag.
    """
    W_NS = WORD_NSMAP["w"]

    if not rows:
        return _render_word_paragraph(templates, "")

    # Calculate column count
    col_count = max(len(row) for row in rows) if rows else 1

    # Build table grid
    grid_cols = "".join('<w:gridCol w:w="2500"/>' for _ in range(col_count))

    # Build rows
    rows_xml = []
    for row in rows:
        cells_xml = []
        for cell in row:
            cell_runs = cell.get("runs", [])
            is_header = cell.get("header", False)

            # Add bold to header cells
            if is_header:
                cell_runs = [{"bold": True, **r} for r in cell_runs]

            runs_xml = _render_runs_xml(cell_runs)

            cell_xml = f"""<w:tc>
                <w:tcPr><w:tcW w:w="2500" w:type="dxa"/></w:tcPr>
                <w:p>
                    <w:pPr><w:pStyle w:val="TableText"/></w:pPr>
                    {runs_xml}
                </w:p>
            </w:tc>"""
            cells_xml.append(cell_xml)

        row_xml = f"<w:tr>{''.join(cells_xml)}</w:tr>"
        rows_xml.append(row_xml)

    table_xml = f"""<w:tbl xmlns:w="{W_NS}">
        <w:tblPr>
            <w:tblStyle w:val="{table_style}"/>
            <w:tblW w:w="5000" w:type="pct"/>
            <w:tblLook w:val="04A0" w:firstRow="1" w:lastRow="0" w:firstColumn="1" w:lastColumn="0" w:noHBand="0" w:noVBand="1"/>
        </w:tblPr>
        <w:tblGrid>{grid_cols}</w:tblGrid>
        {"".join(rows_xml)}
    </w:tbl>"""

    return etree.fromstring(table_xml.encode("utf-8"))


def _render_word_page_break() -> etree._Element:
    """Render a page break element."""
    xml = f"""<w:p xmlns:w="{WORD_NSMAP["w"]}">
        <w:r><w:br w:type="page"/></w:r>
    </w:p>"""
    return etree.fromstring(xml.encode("utf-8"))


def _inject_word_metadata(
    core_xml: bytes,
    title: Optional[str],
    metadata: Dict[str, Any],
) -> bytes:
    """Inject metadata into docProps/core.xml."""
    # Parse and modify core properties
    root = etree.fromstring(core_xml)

    # Namespace for Dublin Core elements
    DC_NS = "http://purl.org/dc/elements/1.1/"

    dc = f"{{{DC_NS}}}"

    # Update or add elements
    if title:
        title_el = root.find(f".//{dc}title")
        if title_el is not None:
            title_el.text = title

    if "author" in metadata:
        creator_el = root.find(f".//{dc}creator")
        if creator_el is not None:
            creator_el.text = metadata["author"]

    if "subject" in metadata:
        subject_el = root.find(f".//{dc}subject")
        if subject_el is not None:
            subject_el.text = metadata["subject"]

    return etree.tostring(root, xml_declaration=True, encoding="UTF-8")


# ============================================================================
# PowerPoint Content Injection
# ============================================================================


def _inject_powerpoint_content(
    template_bytes: bytes,
    slides: List[Dict[str, Any]],
) -> bytes:
    """Inject slide content into PowerPoint template.

    Note: Full slide content injection is complex and would require
    significant XML manipulation. This is a placeholder that converts
    the template to a presentation.
    """
    # For now, just convert template to presentation
    input_zip = BytesIO(template_bytes)
    output_zip = BytesIO()

    with zipfile.ZipFile(input_zip, "r") as zin:
        with zipfile.ZipFile(
            output_zip, "w", zipfile.ZIP_DEFLATED, compresslevel=9
        ) as zout:
            for item in zin.namelist():
                data = zin.read(item)
                if item == "[Content_Types].xml":
                    # Convert from template to presentation
                    data = data.replace(
                        b"presentationml.template.main+xml",
                        b"presentationml.presentation.main+xml",
                    )
                zout.writestr(item, data)

    return output_zip.getvalue()


# ============================================================================
# Excel Content Injection
# ============================================================================


def _inject_excel_content(
    template_bytes: bytes,
    sheets: List[Dict[str, Any]],
) -> bytes:
    """Inject sheet data into Excel template.

    Note: Full sheet data injection requires modifying sheet XML.
    This is a placeholder that converts template to workbook.
    """
    input_zip = BytesIO(template_bytes)
    output_zip = BytesIO()

    with zipfile.ZipFile(input_zip, "r") as zin:
        with zipfile.ZipFile(
            output_zip, "w", zipfile.ZIP_DEFLATED, compresslevel=9
        ) as zout:
            for item in zin.namelist():
                data = zin.read(item)
                if item == "[Content_Types].xml":
                    data = data.replace(
                        b"spreadsheetml.template.main+xml",
                        b"spreadsheetml.sheet.main+xml",
                    )
                zout.writestr(item, data)

    return output_zip.getvalue()


# ============================================================================
# ODF Content Injection (Writer/Impress)
# ============================================================================


def _inject_writer_content(
    template_bytes: bytes,
    content_blocks: List[Dict[str, Any]],
) -> bytes:
    """Inject content into ODF Writer template.

    ODF uses content.xml instead of word/document.xml.
    """
    # For now, just return the template as-is (converted to .odt)
    # Full ODF injection would modify content.xml
    return template_bytes


def _inject_impress_content(
    template_bytes: bytes,
    slides: List[Dict[str, Any]],
) -> bytes:
    """Inject slides into ODF Impress template."""
    # For now, just return the template as-is
    return template_bytes


# ============================================================================
# Utility Functions
# ============================================================================


def _parse_markdown_to_blocks(markdown: str) -> List[Dict[str, Any]]:
    """Parse markdown content into content blocks.

    Uses mistune for full GFM (GitHub Flavored Markdown) support:
    - Headings (# ## ### ####)
    - Paragraphs with inline formatting (bold, italic, strikethrough, code)
    - Bullet lists (- *)
    - Numbered lists (1. 2.)
    - Tables (GFM)
    - Code blocks (```)
    - Block quotes (>)
    - Horizontal rules (---)
    - Links [text](url)
    """
    from tokenmoulds.mcp.markdown import markdown_to_blocks

    return markdown_to_blocks(markdown)
