#!/usr/bin/env python3
"""Workshop-Folien 'Vault – Technischer Impuls': ruhig, visuell, wenig Text."""

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

# ---- Palette ---------------------------------------------------------------
INK    = RGBColor(0x2A, 0x2F, 0x3A)
NAVY   = RGBColor(0x1F, 0x3A, 0x5F)
ACCENT = RGBColor(0xC0, 0x49, 0x2F)
MUTE   = RGBColor(0x8A, 0x93, 0xA0)
SOFT   = RGBColor(0xE9, 0xEC, 0xEF)   # heller Kasten
SOFT2  = RGBColor(0xDD, 0xE5, 0xEC)   # heller Kasten blau
LINEC  = RGBColor(0xC3, 0xC9, 0xD1)
BG     = RGBColor(0xFF, 0xFF, 0xFF)
WHITE  = RGBColor(0xFF, 0xFF, 0xFF)
F = "Calibri"

prs = Presentation()
prs.slide_width  = Inches(13.333)
prs.slide_height = Inches(7.5)
SW, SH = prs.slide_width, prs.slide_height
BLANK = prs.slide_layouts[6]
MX = Inches(0.95)


def slide(bg=BG):
    s = prs.slides.add_slide(BLANK)
    r = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, SW, SH)
    r.fill.solid(); r.fill.fore_color.rgb = bg; r.line.fill.background()
    r.shadow.inherit = False
    s.shapes._spTree.remove(r._element); s.shapes._spTree.insert(2, r._element)
    return s


def shape(s, kind, x, y, w, h, fill=None, line=None, lw=Pt(1)):
    sh = s.shapes.add_shape(kind, x, y, w, h)
    if fill is None: sh.fill.background()
    else: sh.fill.solid(); sh.fill.fore_color.rgb = fill
    if line is None: sh.line.fill.background()
    else: sh.line.color.rgb = line; sh.line.width = lw
    sh.shadow.inherit = False
    return sh


def box(s, x, y, w, h, text="", fill=SOFT, tc=INK, size=15, bold=False,
        rounded=True, line=None, sub=None, sub_c=None):
    kind = MSO_SHAPE.ROUNDED_RECTANGLE if rounded else MSO_SHAPE.RECTANGLE
    sh = shape(s, kind, x, y, w, h, fill=fill, line=line)
    tf = sh.text_frame; tf.word_wrap = True
    tf.vertical_anchor = MSO_ANCHOR.MIDDLE
    tf.margin_top = tf.margin_bottom = Pt(2)
    p = tf.paragraphs[0]; p.alignment = PP_ALIGN.CENTER
    r = p.add_run(); r.text = text
    r.font.size = Pt(size); r.font.bold = bold; r.font.name = F; r.font.color.rgb = tc
    if sub:
        p2 = tf.add_paragraph(); p2.alignment = PP_ALIGN.CENTER; p2.space_before = Pt(2)
        r2 = p2.add_run(); r2.text = sub
        r2.font.size = Pt(11); r2.font.name = F; r2.font.color.rgb = sub_c or MUTE
    return sh


def text(s, x, y, w, h, runs, align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP,
         leading=1.1, space=0):
    tb = s.shapes.add_textbox(x, y, w, h); tf = tb.text_frame
    tf.word_wrap = True; tf.vertical_anchor = anchor
    tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0)
    p = tf.paragraphs[0]; p.alignment = align; p.line_spacing = leading; p.space_after = Pt(space)
    for t, kw in runs:
        r = p.add_run(); r.text = t
        r.font.size = Pt(kw.get("size", 16)); r.font.bold = kw.get("bold", False)
        r.font.italic = kw.get("italic", False); r.font.name = F
        r.font.color.rgb = kw.get("color", INK)
    return tb


def kicker(s, t):
    tb = text(s, MX, Inches(0.62), Inches(11), Inches(0.4),
              [(t.upper(), {"size": 12.5, "color": ACCENT, "bold": True})])
    tb.text_frame.paragraphs[0].runs[0].font._rPr.set('spc', '240')


def head(s, t):
    text(s, MX, Inches(1.0), Inches(11.4), Inches(0.9),
         [(t, {"size": 32, "color": NAVY, "bold": True})])
    shape(s, MSO_SHAPE.RECTANGLE, MX, Inches(1.78), Inches(0.6), Pt(4), fill=ACCENT)


def caption(s, t):
    text(s, MX, Inches(6.62), Inches(11.4), Inches(0.6),
         [(t, {"size": 16, "color": MUTE})], leading=1.15)


def vline(s, x, y, h, color=LINEC):
    shape(s, MSO_SHAPE.RECTANGLE, x, y, Pt(1.6), h, fill=color)


# ===========================================================================
# TITEL
# ===========================================================================
s = slide(NAVY)
shape(s, MSO_SHAPE.RECTANGLE, 0, 0, Inches(0.24), SH, fill=ACCENT)
shape(s, MSO_SHAPE.RECTANGLE, MX, Inches(2.7), Inches(0.85), Pt(5), fill=ACCENT)
text(s, MX, Inches(1.7), Inches(10), Inches(0.5),
     [("TECHNISCHER IMPULS", {"size": 14, "color": RGBColor(0xC6,0xD1,0xDE), "bold": True})]
     ).text_frame.paragraphs[0].runs[0].font._rPr.set('spc', '280')
text(s, MX, Inches(3.05), Inches(11.4), Inches(2.0),
     [("LIS – wohin", {"size": 50, "color": WHITE, "bold": True})])
text(s, MX, Inches(4.05), Inches(11.4), Inches(2.0),
     [("technisch denken?", {"size": 50, "color": WHITE, "bold": True})])

# ===========================================================================
# FOLIE 1 – Produkt oder Projekt  (Balken-Bild)
# ===========================================================================
s = slide()
kicker(s, "Diagnose")
head(s, "Produkt oder Projekt?")

base = Inches(5.45)          # Grundlinie
bw, gap = Inches(0.52), Inches(0.16)


def bars(cx_start, heights, colors, label):
    n = len(heights)
    for i, (hh, col) in enumerate(zip(heights, colors)):
        x = cx_start + i * (bw + gap)
        h = Inches(hh)
        shape(s, MSO_SHAPE.ROUNDED_RECTANGLE, x, base - h, bw, h, fill=col)
    total = n * bw + (n - 1) * gap
    # Grundlinie
    shape(s, MSO_SHAPE.RECTANGLE, cx_start, base, total, Pt(2), fill=LINEC)
    text(s, cx_start, base + Inches(0.18), total, Inches(0.4),
         [(label, {"size": 18, "color": NAVY, "bold": True})], align=PP_ALIGN.CENTER)


bars(Inches(1.5), [0.45, 0.5, 2.7, 0.5, 0.45],
     [MUTE, MUTE, ACCENT, MUTE, MUTE], "Projekt")
bars(Inches(8.0), [1.7, 2.0, 1.8, 2.1, 1.7],
     [NAVY, NAVY, NAVY, NAVY, NAVY], "Produkt")

# kleine neutrale Unterschriften
text(s, Inches(1.5), base + Inches(0.6), Inches(3.42), Inches(0.4),
     [("tief in einer Dimension", {"size": 13, "color": MUTE})], align=PP_ALIGN.CENTER)
text(s, Inches(8.0), base + Inches(0.6), Inches(3.42), Inches(0.4),
     [("breit über viele", {"size": 13, "color": MUTE})], align=PP_ALIGN.CENTER)

# ===========================================================================
# FOLIE 2 – Eine Schicht dazwischen  (Layer-Diagramm)
# ===========================================================================
s = slide()
kicker(s, "Architektur")
head(s, "Eine Schicht dazwischen")

cxL, cxR = MX, Inches(12.38)
full_w = cxR - cxL

# oben: Gruppe / Prozesse
top_y = Inches(2.2)
chips = ["Reporting", "DFÜ / Anlieferung", "Fusion"]
cw = Inches(3.2); cg = (full_w - 3 * cw) / 2
for i, c in enumerate(chips):
    box(s, cxL + i * (cw + cg), top_y, cw, Inches(0.72), c,
        fill=BG, tc=NAVY, size=15, bold=True, line=LINEC)

# Mitte: die Schicht (Hero)
mid_y = Inches(3.55)
box(s, cxL, mid_y, full_w, Inches(0.95),
    "Integrations- & Abstraktionsschicht",
    fill=NAVY, tc=WHITE, size=20, bold=True)

# unten: Systeme
bot_y = Inches(5.15)
labs = [("Labor A", "LIS"), ("Labor B", "LIS"), ("Vault", "LIS"), ("Labor C", "eigenes LIS")]
lw = Inches(2.65); lg = (full_w - 4 * lw) / 3
for i, (nm, sub) in enumerate(labs):
    x = cxL + i * (lw + lg)
    box(s, x, bot_y, lw, Inches(0.95), nm, fill=SOFT, tc=INK, size=16, bold=True,
        sub=sub, sub_c=MUTE)
    # Verbinder Schicht -> System
    vline(s, x + lw / 2, mid_y + Inches(0.95), bot_y - (mid_y + Inches(0.95)))

# Verbinder oben -> Schicht
for i in range(3):
    cx = cxL + i * (cw + cg) + cw / 2
    vline(s, cx, top_y + Inches(0.72), mid_y - (top_y + Inches(0.72)))

caption(s, "Labore docken an und behalten ihr LIS. Die Schicht verbindet.")

# ===========================================================================
# FOLIE 3 – Zustand oder Ereignis  (Event Sourcing, visuell)
# ===========================================================================
s = slide()
kicker(s, "Modell")
head(s, "Zustand oder Ereignis")

cxL, cxR = MX, Inches(12.38)
full_w = cxR - cxL

# --- obere Spur: Zustand ---
ay = Inches(2.35)
text(s, cxL, ay - Inches(0.05), Inches(3), Inches(0.4),
     [("Zustand", {"size": 15, "color": MUTE, "bold": True})])
zy = ay + Inches(0.42)
box(s, cxL, zy, Inches(2.4), Inches(0.95), "Probe", fill=SOFT, tc=INK, size=16, bold=True)
# Pfeil "überschreiben"
shape(s, MSO_SHAPE.RIGHT_ARROW, cxL + Inches(2.65), zy + Inches(0.27),
      Inches(1.5), Inches(0.42), fill=LINEC)
text(s, cxL + Inches(2.55), zy - Inches(0.18), Inches(1.7), Inches(0.3),
     [("überschreiben", {"size": 11, "color": MUTE, "italic": True})], align=PP_ALIGN.CENTER)
box(s, cxL + Inches(4.4), zy, Inches(2.7), Inches(0.95), "1 Abrechnung",
    fill=BG, tc=NAVY, size=16, bold=True, line=LINEC)

# Trennlinie
shape(s, MSO_SHAPE.RECTANGLE, cxL, Inches(4.25), full_w, Pt(1.2), fill=LINEC)

# --- untere Spur: Ereignisse ---
by = Inches(4.55)
text(s, cxL, by - Inches(0.05), Inches(3), Inches(0.4),
     [("Ereignisse", {"size": 15, "color": MUTE, "bold": True})])
ey = by + Inches(0.42)
ev = ["Eingang", "Messung", "Abrechnung", "Abrechnung"]
ew = Inches(1.85); eg = Inches(0.32)
for i, e in enumerate(ev):
    x = cxL + i * (ew + eg)
    col = ACCENT if e == "Abrechnung" else NAVY
    box(s, x, ey, ew, Inches(0.85), e, fill=col, tc=WHITE, size=14, bold=True)
    if i < len(ev) - 1:
        shape(s, MSO_SHAPE.CHEVRON, x + ew + Inches(0.02), ey + Inches(0.24),
              Inches(0.28), Inches(0.38), fill=LINEC)
# Pfeil zum abgeleiteten Zustand
last_x = cxL + 4 * (ew + eg)
shape(s, MSO_SHAPE.RIGHT_ARROW, last_x - Inches(0.05), ey + Inches(0.22),
      Inches(0.7), Inches(0.42), fill=LINEC)
box(s, last_x + Inches(0.75), ey - Inches(0.05), Inches(2.7), Inches(0.95),
    "Zustand", fill=SOFT2, tc=NAVY, size=15, bold=True,
    sub="abgeleitet", sub_c=MUTE)

caption(s, "Der Zustand entsteht aus den Ereignissen – Mehrfaches inklusive.")

out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Vault_Impuls_Workshop.pptx")
prs.save(out)
print("saved", out)
