"""docx.py — the .docx surface projector: an OWN minimal OOXML emitter.

Own-the-core: a .docx is just a ZIP of a few XML parts (WordprocessingML). We
emit it with the standard library only — ``zipfile`` + string XML — no
python-docx, no external dependency. This proves a "richer structured format"
surface without importing anyone else's toolkit.

Parts emitted (the minimal valid set + a styles part for real headings):
    [Content_Types].xml
    _rels/.rels
    word/_rels/document.xml.rels
    word/styles.xml          (Title / Heading1 / Heading2 / Normal)
    word/document.xml        (the content)

Like the markdown projector it reads only the IR's realized sentences; it
invents nothing. The surface differs, the faithful content does not.
"""
from __future__ import annotations

import io
import os
import sys
import zipfile
from xml.sax.saxutils import escape

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from document_ir import DocumentIR  # noqa: E402
from projectors.base import register  # noqa: E402

_CONTENT_TYPES = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
  <Default Extension="xml" ContentType="application/xml"/>
  <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
  <Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>
</Types>"""

_RELS = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>"""

_DOC_RELS = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
</Relationships>"""

_W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"

_STYLES = f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:styles xmlns:w="{_W}">
  <w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/>
    <w:rPr><w:sz w:val="22"/></w:rPr></w:style>
  <w:style w:type="paragraph" w:styleId="Title"><w:name w:val="Title"/>
    <w:pPr><w:spacing w:after="240"/></w:pPr>
    <w:rPr><w:b/><w:sz w:val="52"/></w:rPr></w:style>
  <w:style w:type="paragraph" w:styleId="Subtitle"><w:name w:val="Subtitle"/>
    <w:rPr><w:i/><w:sz w:val="28"/><w:color w:val="555555"/></w:rPr></w:style>
  <w:style w:type="paragraph" w:styleId="Heading1"><w:name w:val="heading 1"/>
    <w:pPr><w:spacing w:before="240" w:after="120"/><w:outlineLvl w:val="0"/></w:pPr>
    <w:rPr><w:b/><w:sz w:val="34"/></w:rPr></w:style>
  <w:style w:type="paragraph" w:styleId="Heading2"><w:name w:val="heading 2"/>
    <w:pPr><w:spacing w:before="200" w:after="100"/><w:outlineLvl w:val="1"/></w:pPr>
    <w:rPr><w:b/><w:sz w:val="28"/></w:rPr></w:style>
</w:styles>"""


def _para(text: str, style: str | None = None) -> str:
    ppr = f"<w:pPr><w:pStyle w:val=\"{style}\"/></w:pPr>" if style else ""
    return (f"<w:p>{ppr}<w:r><w:t xml:space=\"preserve\">"
            f"{escape(text)}</w:t></w:r></w:p>")


class DocxProjector:
    surface = "docx"
    media_type = ("application/vnd.openxmlformats-officedocument."
                  "wordprocessingml.document")
    ext = "docx"
    modality = "text"

    def _document_xml(self, doc: DocumentIR) -> str:
        body: list[str] = [_para(doc.title, "Title")]
        if doc.subtitle:
            body.append(_para(doc.subtitle, "Subtitle"))
        abstract = doc.meta.get("abstract")
        if abstract is not None and abstract.sentences:
            body.append(_para(abstract.text()))
        for sec in doc.sections:
            style = "Heading1" if sec.level <= 1 else "Heading2"
            body.append(_para(sec.heading, style))
            for block in sec.blocks:
                t = block.text()
                if t:
                    body.append(_para(t))
        return (f"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
                f"<w:document xmlns:w=\"{_W}\"><w:body>"
                + "".join(body)
                + "<w:sectPr><w:pgSz w:w=\"12240\" w:h=\"15840\"/>"
                  "<w:pgMar w:top=\"1440\" w:right=\"1440\" w:bottom=\"1440\" "
                  "w:left=\"1440\"/></w:sectPr></w:body></w:document>")

    def project(self, doc: DocumentIR) -> bytes:
        buf = io.BytesIO()
        with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z:
            z.writestr("[Content_Types].xml", _CONTENT_TYPES)
            z.writestr("_rels/.rels", _RELS)
            z.writestr("word/_rels/document.xml.rels", _DOC_RELS)
            z.writestr("word/styles.xml", _STYLES)
            z.writestr("word/document.xml", self._document_xml(doc))
        return buf.getvalue()


register(DocxProjector())
