#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Build the FFG Businessplan & Finanzplan Word document from the final markdown."""
import re
import os
from docx import Document
from docx.shared import Pt, Cm, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_BREAK
from docx.enum.section import WD_SECTION
from docx.enum.table import WD_TABLE_ALIGNMENT
from docx.oxml.ns import qn
from docx.oxml import OxmlElement

BASE = "/home/nk/hobo-godmode/otto/projekte/plappi/ffg-antrag"
SRC = os.path.join(BASE, "BUSINESSPLAN_FINANZPLAN_FINAL.md")
OUT = os.path.join(BASE, "BUSINESSPLAN_Plappi_FFG_final.docx")
ASSETS = os.path.join(BASE, "_assets")

# Abbildungs-Schlüssel -> Dateiname (relativ zu _assets)
ABB = {
    "GERAET": "A2_lifestyle.jpg",
    "GERAET2": "Langpal1.jpeg",
    "MONTESSORI": "B1_montessori.jpg",
    "DASHBOARD": "Dashboard.png",
    "POSITION": "3_tonies-vergleich.jpg",
    "SPRACHEN": "4_27-sprachen.jpg",
    "CHART_UMSATZ": "chart_umsatz.png",
    "CHART_GUV": "chart_guv.png",
    "CHART_CASHFLOW": "chart_cashflow.png",
    "CHART_FINANZIERUNG": "chart_finanzierung.png",
    "LOGO": "Logo.png",
}
# Default Bildbreite in cm; Dashboard/Charts breiter
ABB_WIDTH_CM = {
    "GERAET": 13.0,
    "GERAET2": 10.0,
    "MONTESSORI": 13.0,
    "POSITION": 13.0,
    "SPRACHEN": 13.0,
    "DASHBOARD": 16.0,
    "CHART_UMSATZ": 16.0,
    "CHART_GUV": 16.0,
    "CHART_CASHFLOW": 16.0,
    "CHART_FINANZIERUNG": 16.0,
}

ACCENT = RGBColor(0x2E, 0x5B, 0x9E)   # blau für Überschriften
GREY = RGBColor(0x59, 0x59, 0x59)     # dezentes Grau für Zitate
HDR_FILL = "D5E2F0"                    # Kopfzeilen-Hintergrund der Tabellen

notes_log = []

# ------------------------------------------------------------------ helpers


def set_cell_bg(cell, fill):
    tcPr = cell._tc.get_or_add_tcPr()
    shd = OxmlElement("w:shd")
    shd.set(qn("w:val"), "clear")
    shd.set(qn("w:color"), "auto")
    shd.set(qn("w:fill"), fill)
    tcPr.append(shd)


def add_runs_with_bold(paragraph, text, base_size=None, italic=False, color=None):
    """Parse **bold** markers and emit runs."""
    parts = re.split(r"(\*\*.+?\*\*)", text)
    for part in parts:
        if not part:
            continue
        if part.startswith("**") and part.endswith("**"):
            r = paragraph.add_run(part[2:-2])
            r.bold = True
        else:
            r = paragraph.add_run(part)
        if base_size is not None:
            r.font.size = base_size
        if italic:
            r.italic = True
        if color is not None:
            r.font.color.rgb = color


def add_image_centered(doc, key, caption, fig_no):
    fname = ABB.get(key)
    if not fname:
        notes_log.append(f"Abbildungsschlüssel '{key}' unbekannt — übersprungen.")
        return fig_no
    path = os.path.join(ASSETS, fname)
    if not os.path.exists(path):
        notes_log.append(f"Bilddatei fehlt für {key} ({fname}) — Marker übersprungen.")
        return fig_no
    width_cm = ABB_WIDTH_CM.get(key, 14.0)
    p = doc.add_paragraph()
    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    p.paragraph_format.space_before = Pt(6)
    p.paragraph_format.space_after = Pt(2)
    run = p.add_run()
    run.add_picture(path, width=Cm(width_cm))
    fig_no += 1
    cap = doc.add_paragraph()
    cap.alignment = WD_ALIGN_PARAGRAPH.CENTER
    cap.paragraph_format.space_after = Pt(10)
    cr = cap.add_run(f"Abbildung {fig_no}: {caption}")
    cr.italic = True
    cr.font.size = Pt(9)
    cr.font.color.rgb = GREY
    return fig_no


def add_table_caption(doc, tbl_no, title):
    cap = doc.add_paragraph()
    cap.paragraph_format.space_before = Pt(8)
    cap.paragraph_format.space_after = Pt(2)
    r = cap.add_run(f"Tabelle {tbl_no}: {title}")
    r.bold = True
    r.italic = True
    r.font.size = Pt(10)
    r.font.color.rgb = ACCENT


def add_md_table(doc, rows):
    """rows: list of list-of-cell-strings; first row is header."""
    ncols = max(len(r) for r in rows)
    table = doc.add_table(rows=0, cols=ncols)
    table.style = "Table Grid"
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = True
    for ri, row in enumerate(rows):
        cells = table.add_row().cells
        for ci in range(ncols):
            text = row[ci] if ci < len(row) else ""
            cell = cells[ci]
            cell.paragraphs[0].text = ""
            para = cell.paragraphs[0]
            para.paragraph_format.space_before = Pt(1)
            para.paragraph_format.space_after = Pt(1)
            # parse bold within cell
            add_runs_with_bold(para, text, base_size=Pt(9.5))
            if ri == 0:
                set_cell_bg(cell, HDR_FILL)
                for run in para.runs:
                    run.bold = True
    return table


def add_field(paragraph, instr):
    """Insert a Word field (e.g. TOC) into a paragraph."""
    run = paragraph.add_run()
    fldChar1 = OxmlElement("w:fldChar")
    fldChar1.set(qn("w:fldCharType"), "begin")
    instrText = OxmlElement("w:instrText")
    instrText.set(qn("xml:space"), "preserve")
    instrText.text = instr
    fldChar2 = OxmlElement("w:fldChar")
    fldChar2.set(qn("w:fldCharType"), "separate")
    t = OxmlElement("w:t")
    t.text = "Inhaltsverzeichnis wird beim Öffnen in Word aktualisiert (F9)."
    fldChar3 = OxmlElement("w:fldChar")
    fldChar3.set(qn("w:fldCharType"), "end")
    run._r.append(fldChar1)
    run._r.append(instrText)
    run._r.append(fldChar2)
    r2 = paragraph.add_run()
    r2._r.append(t)
    r3 = paragraph.add_run()
    r3._r.append(fldChar3)


def add_page_number_footer(section):
    footer = section.footer
    footer.is_linked_to_previous = False
    p = footer.paragraphs[0]
    p.text = ""
    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    # "Seite "
    r = p.add_run("Seite ")
    r.font.size = Pt(8)
    r.font.color.rgb = GREY
    # PAGE field
    fld1 = OxmlElement("w:fldChar"); fld1.set(qn("w:fldCharType"), "begin")
    instr = OxmlElement("w:instrText"); instr.set(qn("xml:space"), "preserve"); instr.text = "PAGE"
    fld2 = OxmlElement("w:fldChar"); fld2.set(qn("w:fldCharType"), "end")
    rp = p.add_run(); rp.font.size = Pt(8); rp.font.color.rgb = GREY
    rp._r.append(fld1); rp._r.append(instr); rp._r.append(fld2)
    r2 = p.add_run("  ·  Plappi · FFG Projektnummer 944442 · vertraulich")
    r2.font.size = Pt(8)
    r2.font.color.rgb = GREY


# ------------------------------------------------------------------ markdown parse

def clean_inline(text):
    # remove markdown inline code backticks but keep content
    return text.replace("`", "")


DRAFT_PATTERNS = [
    "Offen (Nemanja",
    "Iteration durch Nemanja",
    "[TODO]",
    "TODO:",
]


def is_draft_line(line):
    for pat in DRAFT_PATTERNS:
        if pat in line:
            return True
    return False


# ------------------------------------------------------------------ build

def build():
    with open(SRC, "r", encoding="utf-8") as f:
        raw = f.read()
    lines = raw.split("\n")

    doc = Document()

    # ---- base styles
    normal = doc.styles["Normal"]
    normal.font.name = "Calibri"
    normal.font.size = Pt(11)
    normal.paragraph_format.space_after = Pt(6)
    normal.paragraph_format.line_spacing = 1.15

    for hi, (sz, sp_b, sp_a) in [
        ("Heading 1", (18, 16, 8)),
        ("Heading 2", (14, 12, 6)),
        ("Heading 3", (12, 10, 4)),
    ]:
        st = doc.styles[hi]
        st.font.name = "Calibri"
        st.font.size = Pt(sz)
        st.font.bold = True
        st.font.color.rgb = ACCENT
        st.paragraph_format.space_before = Pt(sp_b)
        st.paragraph_format.space_after = Pt(sp_a)
        st.paragraph_format.keep_with_next = True

    # ---- section/page geometry (A4)
    sec = doc.sections[0]
    sec.top_margin = Cm(2.2)
    sec.bottom_margin = Cm(2.0)
    sec.left_margin = Cm(2.2)
    sec.right_margin = Cm(2.2)

    # =================== TITELSEITE ===================
    for _ in range(2):
        doc.add_paragraph()
    logo_path = os.path.join(ASSETS, ABB["LOGO"])
    has_titlepage = False
    if os.path.exists(logo_path):
        p = doc.add_paragraph()
        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
        p.add_run().add_picture(logo_path, width=Cm(7))
        has_titlepage = True
    else:
        notes_log.append("Logo.png fehlt — Titelseite ohne Logo.")

    doc.add_paragraph()
    pt = doc.add_paragraph(); pt.alignment = WD_ALIGN_PARAGRAPH.CENTER
    rt = pt.add_run("Businessplan & Finanzplan — Plappi")
    rt.bold = True; rt.font.size = Pt(26); rt.font.color.rgb = ACCENT

    ps = doc.add_paragraph(); ps.alignment = WD_ALIGN_PARAGRAPH.CENTER
    rs = ps.add_run("Displayloser, kameraloser KI-Sprachlernbegleiter für Kinder")
    rs.font.size = Pt(14); rs.italic = True; rs.font.color.rgb = GREY

    doc.add_paragraph(); doc.add_paragraph()

    for txt, sz, bold in [
        ("FFG Basisprogramm · Projektnummer 944442 (eCall-Antrag 71545616)", 12, True),
        ("Antragsteller: Plappi FlexCo (i.G.), Wien", 12, False),
        ("Juni 2026", 12, False),
    ]:
        p = doc.add_paragraph(); p.alignment = WD_ALIGN_PARAGRAPH.CENTER
        r = p.add_run(txt); r.font.size = Pt(sz); r.bold = bold

    # Seitenumbruch nach Titelseite
    doc.add_paragraph().add_run().add_break(WD_BREAK.PAGE)

    # =================== INHALTSVERZEICHNIS ===================
    hv = doc.add_paragraph()
    rv = hv.add_run("Inhaltsverzeichnis")
    rv.bold = True; rv.font.size = Pt(18); rv.font.color.rgb = ACCENT
    hv.paragraph_format.space_after = Pt(8)

    hint = doc.add_paragraph()
    hr = hint.add_run("Hinweis: Bitte das Inhaltsverzeichnis nach dem Öffnen in Word "
                      "mit Rechtsklick → „Feld aktualisieren“ (oder F9) aktualisieren.")
    hr.italic = True; hr.font.size = Pt(9); hr.font.color.rgb = GREY

    toc_p = doc.add_paragraph()
    add_field(toc_p, 'TOC \\o "1-3" \\h \\z \\u')

    doc.add_paragraph().add_run().add_break(WD_BREAK.PAGE)

    # =================== BODY ===================
    table_no = 0
    fig_no = 0
    i = 0
    n = len(lines)
    # bullet/number list reference state not needed; use built-in styles

    while i < n:
        line = lines[i]
        stripped = line.strip()

        # skip draft notes
        if is_draft_line(line):
            i += 1
            continue

        # horizontal rule
        if stripped == "---":
            i += 1
            continue

        # blank line
        if stripped == "":
            i += 1
            continue

        # headings
        m = re.match(r"^(#{1,3})\s+(.*)$", stripped)
        if m:
            level = len(m.group(1))
            text = clean_inline(m.group(2))
            doc.add_heading(text, level=level)
            i += 1
            continue

        # image marker on its own line
        mimg = re.match(r"^\[\[ABB:([A-Z0-9_]+)\|(.*?)\]\]$", stripped)
        if mimg:
            key = mimg.group(1)
            caption = clean_inline(mimg.group(2))
            fig_no = add_image_centered(doc, key, caption, fig_no)
            i += 1
            continue

        # table block
        if stripped.startswith("|"):
            # gather table lines
            tbl_lines = []
            while i < n and lines[i].strip().startswith("|"):
                tbl_lines.append(lines[i].strip())
                i += 1
            # parse: drop separator row (---|---)
            parsed = []
            for tl in tbl_lines:
                # split, drop leading/trailing empty from pipes
                cells = [c.strip() for c in tl.strip().strip("|").split("|")]
                # detect separator row
                if all(re.match(r"^:?-{2,}:?$", c) for c in cells if c != ""):
                    continue
                parsed.append([clean_inline(c) for c in cells])
            if parsed:
                table_no += 1
                # title: use preceding heading context heuristically -> generic from first header cells
                # Build a short title from the table's header row
                header = parsed[0]
                title = derive_table_title(header, table_no)
                add_table_caption(doc, table_no, title)
                add_md_table(doc, parsed)
                # spacing after table
                sp = doc.add_paragraph()
                sp.paragraph_format.space_after = Pt(4)
            continue

        # blockquote
        if stripped.startswith(">"):
            # gather consecutive quote lines
            qlines = []
            while i < n and lines[i].strip().startswith(">"):
                qtext = lines[i].strip()[1:].strip()
                if not is_draft_line(qtext):
                    qlines.append(qtext)
                i += 1
            qtext = " ".join([q for q in qlines if q])
            if qtext:
                p = doc.add_paragraph()
                p.paragraph_format.left_indent = Cm(0.6)
                p.paragraph_format.space_before = Pt(4)
                p.paragraph_format.space_after = Pt(6)
                # left accent border
                add_left_border(p)
                add_runs_with_bold(p, clean_inline(qtext), base_size=Pt(10), italic=True, color=GREY)
            continue

        # ordered list item
        mol = re.match(r"^(\d+)\.\s+(.*)$", stripped)
        if mol:
            text = clean_inline(mol.group(2))
            p = doc.add_paragraph(style="List Number")
            add_runs_with_bold(p, text)
            i += 1
            continue

        # unordered list item
        mul = re.match(r"^[-*]\s+(.*)$", stripped)
        if mul:
            text = clean_inline(mul.group(1))
            p = doc.add_paragraph(style="List Bullet")
            add_runs_with_bold(p, text)
            i += 1
            continue

        # plain paragraph (may contain inline image markers mid-text -> handle)
        text = clean_inline(stripped)
        p = doc.add_paragraph()
        add_runs_with_bold(p, text)
        i += 1

    # =================== FOOTER (apply to all sections) ===================
    for s in doc.sections:
        add_page_number_footer(s)

    # ---- ensure w:zoom has a valid percent (schema requires it); keep it
    #      in its existing (schema-valid) position rather than re-inserting.
    settings = doc.settings.element
    zoom = settings.find(qn("w:zoom"))
    if zoom is None:
        zoom = OxmlElement("w:zoom")
        # zoom belongs near the very top of CT_Settings
        settings.insert(0, zoom)
    if zoom.get(qn("w:percent")) is None:
        zoom.set(qn("w:percent"), "100")

    # ---- TOC auto-update on open: intentionally NOT setting <w:updateFields>.
    #      The strict OOXML schema gives it no clean slot in python-docx's
    #      default settings.xml without cascading order errors, and Word
    #      already offers to refresh fields on open / via F9. The visible hint
    #      line under the TOC heading tells the user to press F9.

    doc.save(OUT)
    return table_no, fig_no, has_titlepage


def add_left_border(paragraph):
    pPr = paragraph._p.get_or_add_pPr()
    pbdr = OxmlElement("w:pBdr")
    left = OxmlElement("w:left")
    left.set(qn("w:val"), "single")
    left.set(qn("w:sz"), "18")
    left.set(qn("w:space"), "8")
    left.set(qn("w:color"), "9DB7D6")
    pbdr.append(left)
    # pBdr must precede spacing/ind/jc/rPr per the OOXML schema order.
    insert_before = None
    for tag in ("w:spacing", "w:ind", "w:jc", "w:rPr"):
        el = pPr.find(qn(tag))
        if el is not None:
            insert_before = el
            break
    if insert_before is not None:
        insert_before.addprevious(pbdr)
    else:
        pPr.append(pbdr)


def derive_table_title(header, num):
    """Build a short, human title for a table based on its header row + running map."""
    titles = {
        1: "Gründer & Schlüsselpersonen",
        2: "Org-Plan über 24 Monate",
        3: "Wettbewerbsübersicht (Lücke = Plappi-USP)",
        4: "Volumen-Hochlauf Y1–Y5",
        5: "Umsatz-Hochlauf in EUR (Y1–Y5)",
        6: "Plan-GuV über drei Jahre",
        7: "Cashflow-Inflows (kumuliert über 36 Monate)",
        8: "Cashflow-Outflows (kumuliert)",
        9: "Kumulierter Cash-Saldo",
        10: "Eröffnungs-Plan-Bilanz (Mt 0)",
        11: "Plan-Bilanz Ende Jahr 1",
        12: "Folgekosten-Posten (ab Mt 25)",
        13: "Risiken & Maßnahmen",
        14: "Gesamt-Kapitalbedarf 24 Monate",
    }
    return titles.get(num, " / ".join(header[:3]))


if __name__ == "__main__":
    t, f, hp = build()
    print(f"TABLES={t} FIGURES={f} TITLEPAGE={hp}")
    for note in notes_log:
        print("NOTE:", note)
