#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
build_training_pptx.py - JvG Training Master generator (v1.0)

Rebrand + optimalisatie van TRAINING_PREMIUM_V1.pptx naar JvG Consultancy
huisstijl (MASTER_STYLEGUIDE v1.3), plus generatie van een generiek
Master Template met placeholder-slides.

Gebruik:
    python build_training_pptx.py [--src bron/TRAINING_PREMIUM_V1.pptx]
                                  [--out-dir deliverables]
                                  [--logo assets/jvg-logo-white-large.png]

Deliverables:
    deliverables/JvG_Training_Inhaleerbaar_Stof_v1.0.pptx  (32 slides, inhoud intact)
    deliverables/JvG_Training_Master_TEMPLATE_v1.0.pptx    (10 placeholder-slides)

Regels: content intact, hyperlinks (slide://N nav-menu) blijven werken,
kleuren conform JvG-palet, Arial, footer + logo op elke slide.
"""
import argparse
import os
import re
import zipfile

from pptx import Presentation
from pptx.dml.color import RGBColor
from pptx.enum.shapes import MSO_SHAPE
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.util import Inches, Pt

# ---------------- JvG MASTER_STYLEGUIDE v1.3 ----------------
PRIMARY = "003366"     # navy (vervangt 005493)
ACCENT = "FF6D00"      # accent/safety (vervangt FF9900)
BODY = "1F2937"        # body tekst (vervangt 333333)
INFO = "3B82F6"
SUCCESS = "00A859"
WARN = "F59E0B"
ERROR = "EF4444"
CARD_BG = "F8F9FA"
CARD_BORDER = "E5E7EB"
FOOTER_BG = "374151"

COLOR_MAP = {
    "005493": PRIMARY,
    "FF9900": ACCENT,
    "333333": BODY,
    "008080": INFO,      # subtitel-kleur bron (teal) -> info-blauw
    "F0F8FF": CARD_BG,   # lichte panelen -> neutrale card-kleur
    "28A745": SUCCESS,
    "FFC107": WARN,
    "DC3545": ERROR,
}

FONT = "Arial"
FOOTER_TEXT = "JvG Consultancy | Safety \u2022 Governance \u2022 Advisory"

SLIDE_W = Inches(10)
SLIDE_H = Inches(5.625)
FOOTER_TOP = Inches(4.955)   # 0.67" hoge footerbar
FOOTER_H = Inches(0.67)
LOGO_FOOTER_W = Inches(0.75)
LOGO_FOOTER_H = Inches(0.67)
LOGO_TITLE_W = Inches(1.5)
LOGO_TITLE_H = Inches(1.35)

HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(HERE)


# ---------------- helpers ----------------
def set_run_font(run, size=None, bold=None, color=None, italic=None):
    f = run.font
    f.name = FONT
    if size is not None:
        f.size = Pt(size)
    if bold is not None:
        f.bold = bold
    if italic is not None:
        f.italic = italic
    if color is not None:
        f.color.rgb = RGBColor.from_string(color)


def add_textbox(slide, x, y, w, h, text, size=14, bold=False, color=BODY,
                align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP, wrap=True):
    tb = slide.shapes.add_textbox(x, y, w, h)
    tf = tb.text_frame
    tf.word_wrap = wrap
    tf.vertical_anchor = anchor
    tf.margin_left = tf.margin_right = Inches(0.02)
    tf.margin_top = tf.margin_bottom = Inches(0.01)
    p = tf.paragraphs[0]
    p.alignment = align
    r = p.add_run()
    r.text = text
    set_run_font(r, size=size, bold=bold, color=color)
    return tb


def add_rect(slide, x, y, w, h, fill, line=None, line_w=0.75,
             shape=MSO_SHAPE.RECTANGLE):
    sh = slide.shapes.add_shape(shape, x, y, w, h)
    sh.fill.solid()
    sh.fill.fore_color.rgb = RGBColor.from_string(fill)
    if line:
        sh.line.color.rgb = RGBColor.from_string(line)
        sh.line.width = Pt(line_w)
    else:
        sh.line.fill.background()
    sh.shadow.inherit = False
    return sh


def add_footer(slide, slide_no, logo_path, total=None):
    """JvG footer: donkere balk (#374151) + logo links + tekst + paginanummer."""
    add_rect(slide, 0, FOOTER_TOP, SLIDE_W, FOOTER_H, FOOTER_BG)
    slide.shapes.add_picture(logo_path, Inches(0.10), FOOTER_TOP,
                             LOGO_FOOTER_W, LOGO_FOOTER_H)
    add_textbox(slide, Inches(0.95), FOOTER_TOP, Inches(6.5), FOOTER_H,
                FOOTER_TEXT, size=9, bold=True, color="FFFFFF",
                anchor=MSO_ANCHOR.MIDDLE)
    num = str(slide_no) if total is None else "%d / %d" % (slide_no, total)
    add_textbox(slide, Inches(8.7), FOOTER_TOP, Inches(1.2), FOOTER_H,
                num, size=9, color="FFFFFF",
                align=PP_ALIGN.RIGHT, anchor=MSO_ANCHOR.MIDDLE)


def force_arial(pres):
    """Zet Arial op alle bestaande runs in alle slides."""
    for slide in pres.slides:
        for sh in slide.shapes:
            if sh.has_text_frame:
                for p in sh.text_frame.paragraphs:
                    for r in p.runs:
                        r.font.name = FONT


def rebrand_zip(src_path, dst_path):
    """Herschrijf XML-parts in de pptx: kleuren + fonts conform JvG.
    Behoudt alles wat python-pptx niet aanraakt (o.a. hyperlink-rels)."""
    color_rx = re.compile('val="(' + "|".join(COLOR_MAP) + ')"')
    font_rx = re.compile('typeface="Times New Roman"')

    def patch(name, data):
        if not (name.startswith("ppt/") and name.endswith(".xml")):
            return data
        text = data.decode("utf-8")
        text = color_rx.sub(lambda m: 'val="%s"' % COLOR_MAP[m.group(1)], text)
        text = font_rx.sub('typeface="%s"' % FONT, text)
        return text.encode("utf-8")

    with zipfile.ZipFile(src_path, "r") as zin, \
         zipfile.ZipFile(dst_path, "w", zipfile.ZIP_DEFLATED) as zout:
        for item in zin.infolist():
            zout.writestr(item, patch(item.filename, zin.read(item.filename)))


# ---------------- training-versie (32 slides) ----------------
def build_training(src, out, logo):
    pres = Presentation(src)

    # slide 1: placeholder-titel -> echte titel + JvG-branding
    s1 = pres.slides[0]
    for sh in s1.shapes:
        if sh.has_text_frame and "Titel training" in sh.text_frame.text:
            for p in sh.text_frame.paragraphs:
                for r in p.runs:
                    r.text = "Veilig werken met inhaleerbaar stof"
                    set_run_font(r, color=PRIMARY)
    add_textbox(s1, Inches(0.38), Inches(4.30), Inches(7.0), Inches(0.4),
                "JvG Consultancy Master Training", size=16, bold=True,
                color=ACCENT)
    # logo rechtsboven - exact 1.5 x 1.35 inch
    s1.shapes.add_picture(logo, Inches(8.30), Inches(0.20),
                          LOGO_TITLE_W, LOGO_TITLE_H)

    # slide 12 (arbeidshygi\u00ebnische piramide): comprimeer licht zodat
    # de trapezoids boven de nieuwe footer blijven (inhoud ongewijzigd)
    for sh in pres.slides[11].shapes:
        if sh.name.startswith("Trapezoid"):
            idx = int(sh.name.split()[-1]) - 3          # 0..3 van boven
            sh.top = Inches(1.20 + 0.83 * idx)
            sh.height = Inches(0.78)

    # footers op alle slides
    total = len(pres.slides)
    for i, slide in enumerate(pres.slides, 1):
        add_footer(slide, i, logo, total)

    force_arial(pres)

    tmp = out + ".tmp"
    pres.save(tmp)
    rebrand_zip(tmp, out)      # kleuren/fonts op XML-niveau (theme incl.)
    os.remove(tmp)


# ---------------- master template (placeholders) ----------------
def _card(slide, x, y, w, h):
    return add_rect(slide, x, y, w, h, CARD_BG, line=CARD_BORDER)


def _title_block(slide, title, subtitle=None):
    add_textbox(slide, Inches(0.5), Inches(0.30), Inches(9.0), Inches(0.55),
                title, size=26, bold=True, color=PRIMARY)
    if subtitle:
        add_textbox(slide, Inches(0.5), Inches(0.85), Inches(9.0), Inches(0.35),
                    subtitle, size=15, color=INFO)
    add_rect(slide, Inches(0.5), Inches(1.25), Inches(1.6), Inches(0.045),
             ACCENT)


def _bullets(slide, lines, x=Inches(0.8), y=Inches(1.6), w=Inches(8.5),
             size=14, gap=0.42, color=BODY):
    for i, line in enumerate(lines):
        add_textbox(slide, x, y + Inches(gap * i), w, Inches(0.38),
                    "\u2022  " + line, size=size, color=color)


def build_template(src, out, logo):
    """Generiek JvG Master Template: 10 placeholder-slides op basis van de
    bron-master (1 master, 14 layouts) met JvG-branding en werkend menu."""
    pres = Presentation(src)

    # verwijder alle bron-slides, hergebruik master/layouts
    # (ook orphan slide-rels verwijderen: anders blijven oude parts behouden)
    from pptx.opc.constants import RELATIONSHIP_TYPE as RT
    xml_slides = pres.slides._sldIdLst
    for sldId in list(xml_slides):
        pres.part.drop_rel(sldId.rId)
        xml_slides.remove(sldId)
    for rId, rel in list(pres.part.rels.items()):
        if rel.reltype == RT.SLIDE:
            pres.part.drop_rel(rId)
    # ook refs vanuit andere parts (bijv. viewProps -> laatste slide)
    for part in list(pres.part.package.iter_parts()):
        if str(part.partname) == '/ppt/presentation.xml':
            continue
        try:
            rels = list(part.rels.items())
        except Exception:
            continue
        for rId, rel in rels:
            if not rel.is_external and rel.reltype == RT.SLIDE:
                try:
                    part.drop_rel(rId)
                except AttributeError:
                    # generic Part: rel-dict direct opruimen
                    part.rels._rels.pop(rId, None)

    blank = None
    for l in pres.slide_masters[0].slide_layouts:
        if not l.placeholders:      # lege layout zonder placeholders
            blank = l
            break
    if blank is None:
        names = {l.name for l in pres.slide_masters[0].slide_layouts}
        for cand in ("Nur Titel", "Blank", "Leeg"):
            if cand in names:
                blank = [l for l in pres.slide_masters[0].slide_layouts
                         if l.name == cand][0]
                break
    if blank is None:
        blank = pres.slide_masters[0].slide_layouts[0]

    def new_slide():
        return pres.slides.add_slide(blank)

    NAV = ["Welkom", "Leerdoelen", "Theorie", "Praktijk",
           "Diagram", "Tabellen", "Rollen", "Quiz", "Bronnen", "Kern"]
    # doelslide per menu-item (titel = slide 1; Praktijk deelt Theorie-slide 4)
    NAV_TARGET = [2, 3, 4, 4, 5, 6, 7, 8, 9, 10]

    def nav_menu(slide, active):
        """Verticaal menu links met hyperlinks (slide://N), zoals bron-deck."""
        add_rect(slide, 0, Inches(1.28), Inches(0.80), Inches(3.50), CARD_BG,
                 line=CARD_BORDER)
        for i, label in enumerate(NAV):
            y = Inches(1.38 + 0.38 * i)
            if label == active:
                add_rect(slide, 0, y, Inches(0.06), Inches(0.32), ACCENT)
            tb = add_textbox(slide, Inches(0.08), y, Inches(0.68),
                             Inches(0.32), label, size=7,
                             color=PRIMARY if label == active else BODY,
                             bold=(label == active))
            r = tb.text_frame.paragraphs[0].runs[0]
            r.hyperlink.address = "slide://%d" % NAV_TARGET[i]

    # 1 - Titel
    s = new_slide()
    add_rect(s, 0, 0, SLIDE_W, SLIDE_H, PRIMARY)
    add_textbox(s, Inches(0.6), Inches(1.9), Inches(7.2), Inches(1.2),
                "[TITEL TRAINING]", size=40, bold=True, color="FFFFFF")
    add_textbox(s, Inches(0.6), Inches(3.1), Inches(7.2), Inches(0.4),
                "[Ondertitel - opdrachtgever / onderwerp]", size=16,
                color="FFFFFF")
    add_rect(s, Inches(0.62), Inches(3.55), Inches(1.6), Inches(0.05), ACCENT)
    add_textbox(s, Inches(0.6), Inches(3.75), Inches(7.0), Inches(0.35),
                FOOTER_TEXT, size=12, color="FFFFFF")
    s.shapes.add_picture(logo, Inches(8.30), Inches(0.20),
                         LOGO_TITLE_W, LOGO_TITLE_H)

    # 2 - Welkom / menu
    s = new_slide()
    _title_block(s, "[Welkom bij de training]", "[Naam / doelgroep training]")
    _bullets(s, ["[Doel van de training in \u00e9\u00e9n zin]",
                 "[Voor wie is deze training bedoeld?]",
                 "[Welke managementstandard / wetgeving?]",
                 "[Door wie wordt de training gegeven?]"])
    nav_menu(s, "Welkom")

    # 3 - Leerdoelen
    s = new_slide()
    _title_block(s, "[Leerdoelen]", "Wat weet en kan de deelnemer na afloop?")
    _bullets(s, ["[Leerdoel 1 - bijv. risico's herkennen]",
                 "[Leerdoel 2 - bijv. maatregelen toepassen]",
                 "[Leerdoel 3 - bijv. verantwoordelijkheden kennen]",
                 "[Leerdoel 4 - bijv. juiste PBM's selecteren]"])
    nav_menu(s, "Leerdoelen")

    # 4 - Content 2-koloms
    s = new_slide()
    _title_block(s, "[Contenttitel]", "[Subtitel / kernvraag]")
    for x, kop in ((1.0, "[Kolom 1 - kop]"), (5.3, "[Kolom 2 - kop]")):
        _card(s, Inches(x), Inches(1.6), Inches(4.0), Inches(3.0))
        add_textbox(s, Inches(x + 0.15), Inches(1.7), Inches(3.7),
                    Inches(0.35), kop, size=14, bold=True, color=PRIMARY)
        for i in range(4):
            add_textbox(s, Inches(x + 0.15), Inches(2.2 + 0.55 * i),
                        Inches(3.7), Inches(0.5), "\u2022  [tekst regel]",
                        size=12)
    nav_menu(s, "Theorie")

    # 5 - Piramide / diagram
    s = new_slide()
    _title_block(s, "[Diagramtitel]", "[Bijv. arbeidshygi\u00ebnische strategie]")
    levels = [("[4. Toplaag]", 3.2), ("[3. Niveau]", 4.0),
              ("[2. Niveau]", 4.8), ("[1. Basis]", 5.6)]
    for i, (label, w) in enumerate(levels):
        y = Inches(1.55 + 0.6 * i)
        x = Inches(6.8 - w / 2)
        add_rect(s, x, y, Inches(w), Inches(0.5),
                 PRIMARY if i % 2 == 0 else INFO, shape=MSO_SHAPE.TRAPEZOID)
        add_textbox(s, x, y, Inches(w), Inches(0.5), label, size=11,
                    bold=True, color="FFFFFF", align=PP_ALIGN.CENTER,
                    anchor=MSO_ANCHOR.MIDDLE)
    add_textbox(s, Inches(1.0), Inches(1.7), Inches(2.4), Inches(0.35),
                "[Toelichting diagram]", size=13, bold=True, color=PRIMARY)
    _bullets(s, ["[punt 1]", "[punt 2]", "[punt 3]"],
             x=Inches(1.0), y=Inches(2.15), w=Inches(2.6), size=12, gap=0.45)
    nav_menu(s, "Diagram")

    # 6 - Tabel
    s = new_slide()
    _title_block(s, "[Tabeltitel]", "[Bijv. maatregelen per activiteit]")
    rows, cols = 5, 4
    tbl = s.shapes.add_table(rows, cols, Inches(0.8), Inches(1.55),
                             Inches(8.4), Inches(2.9)).table
    headers = ["[Kolom A]", "[Kolom B]", "[Kolom C]", "[Kolom D]"]
    for c in range(cols):
        cell = tbl.cell(0, c)
        cell.text = headers[c]
        set_run_font(cell.text_frame.paragraphs[0].runs[0],
                     size=12, bold=True, color="FFFFFF")
        cell.fill.solid()
        cell.fill.fore_color.rgb = RGBColor.from_string(PRIMARY)
    for r in range(1, rows):
        for c in range(cols):
            cell = tbl.cell(r, c)
            cell.text = "[%d.%d tekst]" % (r, c)
            set_run_font(cell.text_frame.paragraphs[0].runs[0],
                         size=11, color=BODY)
            cell.fill.solid()
            cell.fill.fore_color.rgb = RGBColor.from_string(
                CARD_BG if r % 2 else "FFFFFF")
    nav_menu(s, "Tabellen")

    # 7 - RASCI
    s = new_slide()
    _title_block(s, "[RASCI - rollen & verantwoordelijkheden]",
                 "[Bijv. per taak of beslissing]")
    rasci = [("R", "Responsible - uitvoering", PRIMARY),
             ("A", "Accountable - eindverantwoordelijk", ACCENT),
             ("S", "Supportive - ondersteunend", INFO),
             ("C", "Consulted - raadplegen", SUCCESS),
             ("I", "Informed - informeren", WARN)]
    for i, (letter, uitleg, col) in enumerate(rasci):
        x = Inches(0.9 + i * 1.72)
        _card(s, x, Inches(1.7), Inches(1.6), Inches(1.1))
        add_textbox(s, x, Inches(1.75), Inches(1.6), Inches(0.5), letter,
                    size=24, bold=True, color=col, align=PP_ALIGN.CENTER)
        add_textbox(s, x, Inches(2.25), Inches(1.6), Inches(0.5), uitleg.split(" - ")[1],
                    size=8, align=PP_ALIGN.CENTER)
    add_textbox(s, Inches(0.9), Inches(3.1), Inches(8.2), Inches(0.4),
                "[Wijs per taak R/A/S/C/I toe aan rollen - zie bijv. RASCI-tabel]",
                size=12, color=BODY)
    nav_menu(s, "Rollen")

    # 8 - Quiz-vraag
    s = new_slide()
    _title_block(s, "[Quiz]", "[Toets de kennis van de deelnemer]")
    q = add_rect(s, Inches(0.9), Inches(1.6), Inches(8.2), Inches(0.8),
                 CARD_BG, line=CARD_BORDER)
    add_textbox(s, Inches(1.05), Inches(1.6), Inches(7.9), Inches(0.8),
                "[Stel hier de vraag over de zojuist behandelde stof?]",
                size=14, bold=True, color=PRIMARY, anchor=MSO_ANCHOR.MIDDLE)
    for i, letter in enumerate("ABCD"):
        x = Inches(0.9 + (i % 2) * 4.3)
        y = Inches(2.6 + (i // 2) * 0.75)
        _card(s, x, y, Inches(3.9), Inches(0.6))
        add_textbox(s, x + Inches(0.12), y, Inches(0.4), Inches(0.6), letter,
                    size=13, bold=True, color=INFO, anchor=MSO_ANCHOR.MIDDLE)
        add_textbox(s, x + Inches(0.55), y, Inches(3.2), Inches(0.6),
                    "[Antwoordoptie]", size=12, anchor=MSO_ANCHOR.MIDDLE)
    nav_menu(s, "Quiz")

    # 9 - Bronnen
    s = new_slide()
    _title_block(s, "[Bronnen & referenties]", "[Gerelateerde documenten]")
    _bullets(s, ["[1. Documentnaam - documentnummer]",
                 "[2. Procedure - nummer / versie]",
                 "[3. Wet- of regelgeving]",
                 "[4. Handboek / richtlijn]"])
    nav_menu(s, "Bronnen")

    # 10 - Kernpunten
    s = new_slide()
    _title_block(s, "[Kernpunten - samenvatting]",
                 "[Wat moet de deelnemer onthouden?]")
    for i in range(6):
        y = Inches(1.55 + 0.5 * i)
        _card(s, Inches(0.9), y, Inches(8.2), Inches(0.42))
        add_textbox(s, Inches(1.05), y, Inches(0.4), Inches(0.42),
                    "\u2713", size=14, bold=True, color=SUCCESS,
                    anchor=MSO_ANCHOR.MIDDLE)
        add_textbox(s, Inches(1.5), y, Inches(7.5), Inches(0.42),
                    "[Kernpunt %d]" % (i + 1), size=12,
                    anchor=MSO_ANCHOR.MIDDLE)
    nav_menu(s, "Kern")

    # footers op alle template-slides (niet op titelslide: daar staat branding al)
    total = len(pres.slides)
    for i, slide in enumerate(pres.slides, 1):
        add_footer(slide, i, logo, total)

    tmp = out + ".tmp"
    pres.save(tmp)
    rebrand_zip(tmp, out)
    os.remove(tmp)


# ---------------- main ----------------
def main():
    ap = argparse.ArgumentParser(description="JvG Training Master generator")
    ap.add_argument("--src", default=os.path.join(ROOT, "bron",
                                                  "TRAINING_PREMIUM_V1.pptx"))
    ap.add_argument("--out-dir", default=os.path.join(ROOT, "deliverables"))
    ap.add_argument("--logo",
                    default=os.path.join(ROOT, "assets",
                                         "jvg-logo-white-large.png"))
    ap.add_argument("--skip-template", action="store_true")
    args = ap.parse_args()

    os.makedirs(args.out_dir, exist_ok=True)
    training_out = os.path.join(args.out_dir,
                                "JvG_Training_Inhaleerbaar_Stof_v1.0.pptx")
    build_training(args.src, training_out, args.logo)
    print("OK training:", training_out)

    if not args.skip_template:
        template_out = os.path.join(args.out_dir,
                                    "JvG_Training_Master_TEMPLATE_v1.0.pptx")
        build_template(args.src, template_out, args.logo)
        print("OK template:", template_out)


if __name__ == "__main__":
    main()
