#!/usr/bin/env python3
"""
StudioSoft — Carousel Engine (dual theme: BG Putih / BG Biru)
5 slide per carousel: Cover → Isi ×3 → CTA
1080x1350 (4:5) • Margin 50px • Konten vertical-middle
"""
from PIL import Image, ImageDraw, ImageFont
import os

FD = os.path.expanduser("~/.fonts")
W, H = 1080, 1350
M = 50
CONTENT_W = W - 2 * M

THEMES = {
    "putih": {"bg": (255, 255, 255), "ink": (30, 64, 175), "soft": (100, 130, 200)},
    "biru":  {"bg": (30, 64, 175),  "ink": (255, 255, 255), "soft": (180, 200, 240)},
}

J100 = ImageFont.truetype(f"{FD}/Poppins-Bold.ttf", 100)
B36  = ImageFont.truetype(f"{FD}/Poppins-Bold.ttf", 36)
B44  = ImageFont.truetype(f"{FD}/Poppins-Bold.ttf", 44)
SB30 = ImageFont.truetype(f"{FD}/Poppins-SemiBold.ttf", 30)
R28  = ImageFont.truetype(f"{FD}/Poppins-Regular.ttf", 28)
R22  = ImageFont.truetype(f"{FD}/Poppins-Regular.ttf", 22)
R20  = ImageFont.truetype(f"{FD}/Poppins-Regular.ttf", 20)


def fw(d, text, font):
    return d.textbbox((0, 0), text, font=font)[2]


def fh(d, text, font):
    b = d.textbbox((0, 0), text, font=font)
    return b[3] - b[1]


def wrap(d, text, font, mx):
    lines = []
    for p in text.split("\n"):
        ws = p.split()
        cur = ""
        for w in ws:
            t = (cur + " " + w) if cur else w
            if fw(d, t, font) > mx and cur:
                lines.append(cur)
                cur = w
            else:
                cur = t
        if cur:
            lines.append(cur)
    return lines


def block_h(d, content, font, mx, ls=1.35):
    return sum(int(fh(d, ln, font) * ls) for ln in wrap(d, content, font, mx))


def draw_block(d, text, x, y, font, fill, mx, ls=1.35):
    for ln in wrap(d, text, font, mx):
        lh = fh(d, ln, font)
        d.text((x, y), ln, font=font, fill=fill)
        y += int(lh * ls)
    return y


def divider(d, x, y, fill, w=300):
    dx = x
    while dx < x + w:
        e = min(dx + 14, x + w)
        d.line([(dx, y), (e, y)], fill=fill, width=2)
        dx += 24


def clean_edges(im, bg):
    px = im.load()
    for y in range(H):
        for x in range(3):
            px[x, y] = bg
        for x in range(W - 3, W):
            px[x, y] = bg
    for y in range(H - 5, H):
        for x in range(W):
            px[x, y] = bg
    for x in range(W):
        for y in range(3):
            px[x, y] = bg


def new_canvas(theme):
    t = THEMES[theme]
    im = Image.new("RGB", (W, H), t["bg"])
    d = ImageDraw.Draw(im)
    d.text((M, M), "STUDIOSOFT", font=B36, fill=t["ink"])
    return im, d, t


def render_slide(theme, page_label, title_lines, body_fn, footer_right=""):
    im, d, t = new_canvas(theme)
    d.text((W - M, M), page_label, font=R22, fill=t["ink"], anchor="rt")

    hdr_h = fh(d, "STUDIOSOFT", B36)
    total = 0
    for ln in title_lines:
        total += int(fh(d, ln, J100) * 1.12)
    total += 60 + 5 + 25
    total += body_fn(d, t, count_only=True)

    start_y = M + hdr_h + 20 + (H - 2 * M - hdr_h - 20 - total) // 2
    ty = start_y
    for ln in title_lines:
        d.text((M, ty), ln, font=J100, fill=t["ink"])
        ty += int(fh(d, ln, J100) * 1.12)
    ty += 30
    divider(d, M, ty, t["ink"], 300)
    ty += 25
    body_fn(d, t, ty)

    d.text((M, H - 130), "www.studiosoft.id", font=R20, fill=t["ink"])
    if footer_right:
        d.text((W - M, H - 130), footer_right, font=R20, fill=t["ink"], anchor="rt")
    clean_edges(im, t["bg"])
    return im


def rows_body(rows):
    """rows: list of (kind, text) — kind: 'chk' (checklist) / 'num' (bold title+desc)"""
    def fn(d, t, y=None, count_only=False):
        h = 0
        for kind, text in rows:
            if kind == "chk":
                h += int(fh(d, text, R28) * 1.3)
            else:  # num: text = (title, desc)
                title, desc = text
                h += int(fh(d, title, SB30) * 1.3) + int(fh(d, desc, R28) * 1.2) + 12
        if count_only:
            return h
        yy = y
        for i, (kind, text) in enumerate(rows):
            if kind == "chk":
                d.text((M, yy), text, font=R28, fill=t["ink"])
                yy += int(fh(d, text, R28) * 1.3)
            else:
                title, desc = text
                d.text((M, yy), f"{i+1}", font=B44, fill=t["ink"])
                yy += int(fh(d, f"{i+1}", B44) * 1.25)
                yy = draw_block(d, title, M, yy, SB30, t["ink"], CONTENT_W)
                yy = draw_block(d, desc, M, yy, R28, t["soft"], CONTENT_W)
                yy += 18
        return yy
    return fn


def text_block_body(text, font, fill_kind="ink", ls=1.35):
    def fn(d, t, y=None, count_only=False):
        h = block_h(d, text, font, CONTENT_W, ls)
        if count_only:
            return h
        return draw_block(d, text, M, y, font, t[fill_kind], CONTENT_W, ls)
    return fn


def render_carousel(theme, pages, out_dir, prefix="slide"):
    """pages: list of dicts {title:[lines], rows:[...], body:fn, footer_right:str}"""
    os.makedirs(out_dir, exist_ok=True)
    paths = []
    n = len(pages)
    for i, pg in enumerate(pages):
        im = render_slide(theme, f"{i+1:02d}/{n:02d}", pg["title"],
                          pg.get("body", rows_body(pg.get("rows", []))),
                          pg.get("footer_right", ""))
        p = os.path.join(out_dir, f"{prefix}_{i+1:02d}.png")
        im.save(p)
        paths.append(p)
    return paths
