#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Generator carousel DAS — gaya template Bos "Black White Red Modern Microblog".

Aturan desain (dari template kiriman Bos 6 Okt 2026, sudah disetujui komposisinya):
  - kanvas 1080x1080, latar #0D0E0E + halftone #313232, aksen MERAH #ED2733
    (Bos: "tidak ada warna orange .. hapus itu warna orange .. pakai yang merah")
  - headline KAPITAL putih SF Pro Display Bold, label kiri atas, nomor slide kanan atas,
    footer kiri www.dasindonesia.com, footer kanan @das_indonesia
  - panah merah selalu DI BAWAH blok teks (jangan sebaris dengan headline)

Layout yang tersedia (kunci `jenis`):
  cover    : judul 2-3 baris + panah
  isi      : judul + paragraf + panah
  mitos    : label kiri-atas (default "SALAH PAHAM") + pernyataan + garis merah + label (default
             "YANG SEBENARNYA") + paragraf. ⚠️ Seri "Mitos atau Fakta" adalah milik klien TS LAW
             (Bos 6 Okt: "itu Mitos n fakta untuk klien Tslaw") — JANGAN dipakai di akun lain.
  angka    : angka raksasa merah + keterangan + paragraf
  check    : judul + 4-5 poin bercentang merah
  tutup    : judul + bookmark merah + satu baris ajakan

Pakai:
  python3 gen_das_carousel.py                 # seri "kampas rem" -> /tmp/das_carousel
  python3 gen_das_carousel.py --seri mitos    # seri "mitos servis" -> /tmp/das_car_mitos
"""
import os, sys, math
from PIL import Image, ImageDraw, ImageFont

W = H = 1080
BG = (13, 14, 14)
DOT = (49, 50, 50)
PUTIH = (255, 255, 255)
MERAH = (237, 39, 51)
M = 65
LABEL = "DAS BENGKEL INDONESIA"
FOOTER_KIRI = "www.dasindonesia.com"
FOOTER_KANAN = "@das_indonesia"

FD = "/home/mantapman/.fonts/SFProDisplay-Bold.otf"
FT_R = "/home/mantapman/.fonts/SFProText-Regular.otf"
FT_B = "/home/mantapman/.fonts/SFProText-Bold.otf"

# ── SERI 1: 6 tanda kampas rem menipis ──
DEK_KAMPAS = [
{"jenis": "cover", "index": "01", "judul": ["6 TANDA", "KAMPAS REM", "SUDAH MENIPIS"], "busur": ("kanan", 0)},
{"jenis": "isi", "index": "02", "judul": "BUNYI BERDECIT", "busur": ("kiri", 60),
 "body": "Pelat pengingat di kampas sudah menyentuh cakram. Bunyinya muncul tiap pedal rem diinjak, dan makin lama makin sering."},
{"jenis": "isi", "index": "03", "judul": "PEDAL MAKIN DALAM", "busur": ("kanan", 90),
 "body": "Butuh injakan lebih dalam untuk berhenti. Bisa kampas yang menipis, bisa juga masalah di minyak rem atau kaliper."},
{"jenis": "isi", "index": "04", "judul": "MOBIL MENEPI", "busur": ("kiri", 30),
 "body": "Satu sisi menahan lebih kuat dari sisi lain. Mobil terasa tertarik ke kiri atau ke kanan setiap rem dipakai."},
{"jenis": "isi", "index": "05", "judul": "SETIR BERGETAR", "busur": ("kanan", 40),
 "body": "Getaran di setir saat mengerem biasanya bukan soal balancing. Arahnya ke cakram yang permukaannya sudah tidak rata."},
{"jenis": "isi", "index": "06", "judul": "SERBUK HITAM", "busur": ("kiri", 80),
 "body": "Debu hitam tebal di pelek depan adalah sisa gesekan kampas. Kalau cepat menumpuk, artinya kampas bekerja lebih keras dari seharusnya."},
{"jenis": "isi", "index": "07", "judul": "LEWAT 40.000 KM", "busur": ("kanan", 20),
 "body": "Angka ini bukan patokan mati, tapi pengingat. Gaya mengemudi dan pemakaian kota seperti Surabaya bikin kampas lebih cepat habis."},
{"jenis": "isi", "index": "08", "judul": "BEDA TIPIS JADI MAHAL", "busur": ("kiri", 50),
 "body": "Dibiarkan sampai logamnya ikut menggerus cakram, yang tadinya cukup ganti kampas jadi ganti kampas dan cakram sekaligus."},
{"jenis": "check", "index": "09", "judul": "YANG KAMI PERIKSA", "busur": ("kanan", 70),
 "poin": ["Ketebalan kampas", "Kondisi permukaan cakram", "Level dan kondisi minyak rem", "Kemungkinan rembesan di kaliper"]},
{"jenis": "tutup", "index": "10", "judul": ["MAU DICEK", "REM MOBILMU?"], "busur": ("kiri", 0),
 "body": "Tulis di komentar: kapan terakhir kamu periksa rem?"},
]

# ── SERI 2: 6 salah paham soal servis mobil ──
# ⚠️ Judul seri SENGAJA bukan "Mitos atau Fakta" — itu seri milik klien TS LAW.
DEK_MITOS = [
{"jenis": "cover", "index": "01", "judul": ["6 SALAH PAHAM", "SERVIS MOBIL", "YANG SERING", "DIPERCAYA"], "busur": ("kanan", 0)},
{"jenis": "mitos", "index": "02", "busur": ("kiri", 60),
 "mitos": "\"Oli paling mahal pasti paling bagus.\"",
 "fakta": "Yang menentukan bukan harganya, tapi kesesuaian spesifikasi dengan mobilmu. Oli mahal dengan jenis yang salah justru bisa lebih merugikan."},
{"jenis": "mitos", "index": "03", "busur": ("kanan", 90),
 "mitos": "\"Mobil jarang dipakai, jadi nggak perlu servis.\"",
 "fakta": "Justru kebalikannya. Oli tetap menua, aki tekor, dan seal mengering. Untuk mobil yang jarang jalan, patokannya waktu, bukan jarak tempuh."},
{"jenis": "mitos", "index": "04", "busur": ("kiri", 30),
 "mitos": "\"Panaskan mesin lama-lama bikin mesin awet.\"",
 "fakta": "Mesin modern cukup 30-60 detik, lalu jalan pelan. Yang merawat mesin bukan diam di tempat, tapi oli yang sudah bersirkulasi sebelum beban berat."},
{"jenis": "mitos", "index": "05", "busur": ("kanan", 40),
 "mitos": "\"AC kurang dingin, tinggal isi freon.\"",
 "fakta": "Filter kabin tersumbat dan kondensor kotor adalah dua penyebab paling umum, dan dua-duanya murah. Freon baru dicari kalau memang ada kebocoran."},
{"jenis": "mitos", "index": "06", "busur": ("kiri", 80),
 "mitos": "\"Bunyi di kaki-kaki cuma baut kendor.\"",
 "fakta": "Urutan yang paling sering kami temui: link stabilizer, bushing, baru sokbreker. Yang paling murah justru paling sering jadi biangnya."},
{"jenis": "mitos", "index": "07", "busur": ("kanan", 20),
 "mitos": "\"Servis rutin itu cuma ganti oli.\"",
 "fakta": "Servis rutin juga memeriksa rem, ban, aki, cairan, dan kaki-kaki. Komponen yang tidak diperiksa itulah yang biasanya tumbuh jadi biaya besar."},
{"jenis": "angka", "index": "08", "busur": ("kiri", 50), "angka": "40.000", "satuan": "KM",
 "keterangan": "Patokan periksa kampas rem", 
 "body": "Bukan angka mati, tapi pengingat. Gaya mengemudi dan pemakaian kota bikin kampas lebih cepat habis."},
{"jenis": "check", "index": "09", "judul": "SEBELUM BILANG MAHAL", "busur": ("kanan", 70),
 "poin": ["Minta rincian sebelum dibongkar", "Tanya mana yang masih aman", "Tanya garansi pekerjaannya", "Simpan catatan servisnya"]},
{"jenis": "tutup", "index": "10", "judul": ["SALAH PAHAM APA", "YANG PERNAH", "KAMU DENGAR?"], "busur": ("kiri", 0),
 "body": "Tulis di komentar, biar yang lain ikut tahu."},
]
# ── SERI 3: 7 hal yang wajib dicek sebelum jalan jauh ──
DEK_JALAN = [
{"jenis": "cover", "index": "01", "judul": ["SEBELUM", "JALAN JAUH", "7 HAL YANG", "WAJIB DICEK"], "busur": ("kanan", 0)},
{"jenis": "isi", "index": "02", "judul": "BAN & TEKANAN ANGIN", "busur": ("kiri", 60),
 "body": "Cek saat ban dingin, dan baca angka di stiker pintu pengemudi. Perhatikan juga pola ausnya: aus sebelah berarti soal sudut roda, bukan angin."},
{"jenis": "isi", "index": "03", "judul": "OLI & CAIRAN", "busur": ("kanan", 90),
 "body": "Level oli mesin, air radiator, minyak rem, dan air pembersih kaca. Empat-empatnya murah dicek dan mahal kalau ketahuan di tengah jalan."},
{"jenis": "isi", "index": "04", "judul": "REM", "busur": ("kiri", 30),
 "body": "Bunyi berdecit, pedal yang terasa dalam, atau mobil yang menepi saat mengerem. Tiga-tiganya alasan untuk diperiksa sebelum berangkat."},
{"jenis": "isi", "index": "05", "judul": "AKI & KELISTRIKAN", "busur": ("kanan", 40),
 "body": "Starter yang terasa berat di pagi hari, lampu yang meredup, dan jam digital yang mengulang dari nol adalah tanda awal aki melemah."},
{"jenis": "isi", "index": "06", "judul": "LAMPU & WIPER", "busur": ("kiri", 80),
 "body": "Lampu rem belakang dan lampu kabut paling berguna saat hujan deras, dan paling sering kelewat saat diperiksa. Wiper yang meninggalkan garis berarti karetnya sudah keras."},
{"jenis": "isi", "index": "07", "judul": "KAKI-KAKI & KEMUDI", "busur": ("kanan", 20),
 "body": "Setir yang bergetar di kecepatan tinggi, atau rasa melayang saat jalan lurus. Dua keluhan ini paling sering baru terasa setelah berkendara jauh."},
{"jenis": "isi", "index": "08", "judul": "PERKAKAS & DOKUMEN", "busur": ("kiri", 50),
 "body": "Dongkrak dan kunci roda, pastikan ada dan bisa dipakai. Simpan juga STNK, surat kendaraan, dan nomor bengkel yang bisa dihubungi."},
{"jenis": "check", "index": "09", "judul": "URUTAN CEK CEPAT", "busur": ("kanan", 70),
 "poin": ["Ban dan tekanan anginnya", "Level oli dan cairan", "Rem dan lampu", "Aki, wiper, dan perkakas"]},
{"jenis": "tutup", "index": "10", "judul": ["RENCANA KE MANA", "ENDEK BULAN INI?"], "busur": ("kiri", 0),
 "body": "Tulis di komentar: ke mana rencana perjalananmu?"},
]

# ── SERI 4: matic & CVT ──
DEK_MATIC = [
{"jenis": "cover", "index": "01", "judul": ["MATIC & CVT", "6 KEBIASAAN", "YANG BIKIN", "CEPAT RUSAK"], "busur": ("kanan", 0)},
{"jenis": "isi", "index": "02", "judul": "OLI LEWAT JADWAL", "busur": ("kiri", 60),
 "body": "Oli transmisi bekerja di suhu dan tekanan tinggi. Lewat jadwal, pelumasan turun dan gesekan naik. Ini kebiasaan nomor satu yang kami temui."},
{"jenis": "isi", "index": "03", "judul": "GAS DALAM DARI BERHENTI", "busur": ("kanan", 90),
 "body": "Menginjak gas dalam-dalam setiap kali mulai jalan bikin komponen bekerja lebih keras dari yang seharusnya. Pelan tapi mulus jauh lebih baik."},
{"jenis": "isi", "index": "04", "judul": "MENAHAN DI TANJAKAN", "busur": ("kiri", 30),
 "body": "Menahan mobil di tanjakan hanya dengan posisi gigi membuat oli cepat panas. Pakai rem, itu memang tugasnya."},
{"jenis": "isi", "index": "05", "judul": "PINDAH GIGI BELUM BERHENTI", "busur": ("kanan", 40),
 "body": "Pindah dari maju ke mundur sebelum mobil benar-benar berhenti itu salah satu penyebab paling mahal. Biasakan berhenti penuh dulu."},
{"jenis": "isi", "index": "06", "judul": "MENUNGGU SAMPAI MENGELUH", "busur": ("kiri", 80),
 "body": "Jeda saat pindah gigi, tarikan menggerung, atau lampu indikator menyala. Tanda-tanda ini tidak membaik sendiri, hanya jadi lebih mahal."},
{"jenis": "isi", "index": "07", "judul": "SERVIS DI TANGAN YANG BELUM PAHAM", "busur": ("kanan", 20),
 "body": "Matic dan CVT punya prosedur sendiri, termasuk jenis oli dan cara pengukuran levelnya. Salah oli atau salah ukur bisa terasa berminggu-minggu kemudian."},
{"jenis": "check", "index": "08", "judul": "TANDA HARUS DIPERIKSA", "busur": ("kiri", 50),
 "poin": ["Jeda atau hentakan saat pindah gigi", "Suara menggerung saat menanjak", "Lampu indikator menyala", "Rembesan oli di bawah transmisi"]},
{"jenis": "check", "index": "09", "judul": "YANG KAMI CEK DULU", "busur": ("kanan", 70),
 "poin": ["Warna dan level oli transmisi", "Kebocoran seal dan selang", "Riwayat servis sebelumnya", "Uji jalan sebelum dibongkar"]},
{"jenis": "tutup", "index": "10", "judul": ["MATIC MOBILMU", "SEDANG APA?"], "busur": ("kiri", 0),
 "body": "Ceritakan gejalanya di komentar."},
]

SERI = {"kampas": (DEK_KAMPAS, "/tmp/das_carousel"), "paham": (DEK_MITOS, "/tmp/das_car_paham"),
        "jalan": (DEK_JALAN, "/tmp/das_car_jalan"), "matic": (DEK_MATIC, "/tmp/das_car_matic")}


def font(path, size):
    try:
        return ImageFont.truetype(path, size)
    except Exception:
        return ImageFont.load_default()


def fit(t, path, maxw, start, min_size=40):
    s = start
    while s > min_size:
        f = font(path, s)
        if f.getlength(t) <= maxw:
            return s
        s -= 2
    return min_size


def bungkus(t, f, maxw):
    kata, baris, kini = t.split(), [], ""
    for k in kata:
        coba = (kini + " " + k).strip()
        if f.getlength(coba) <= maxw or not kini:
            kini = coba
        else:
            baris.append(kini)
            kini = k
    if kini:
        baris.append(kini)
    return baris


def pola_titik(img, fokus=(1000, 880), min_r=0.7, max_r=3.4, jarak=13):
    d = ImageDraw.Draw(img)
    fx, fy = fokus
    dmax = math.hypot(W, H)
    for y in range(0, H + jarak, jarak):
        for x in range(0, W + jarak, jarak):
            dd = math.hypot(x - fx, y - fy) / dmax
            r = min_r + (max_r - min_r) * (1 - dd) ** 1.6
            if r < 1.05:
                continue
            d.ellipse([x - r, y - r, x + r, y + r], fill=DOT)


def busur(img, sisi, offset, tebal=3):
    d = ImageDraw.Draw(img)
    r = 700 + offset
    cx = 1080 + r * 0.42 if sisi == "kanan" else -r * 0.42
    d.ellipse([cx - r, 540 - r, cx + r, 540 + r], outline=MERAH, width=tebal)


def panah(d, x, y, s=1.0):
    lw = max(9, int(11 * s))
    x2 = x + int(96 * s)
    d.line([(x, y), (x2, y)], fill=MERAH, width=lw)
    d.line([(x2 - int(34 * s), y - int(30 * s)), (x2, y), (x2 - int(34 * s), y + int(30 * s))],
           fill=MERAH, width=lw, joint="curve")


def bookmark(d, x, y, s=1.0):
    w_, h_ = int(78 * s), int(96 * s)
    d.polygon([(x, y), (x + w_, y), (x + w_, y + h_), (x + w_ // 2, y + h_ - int(28 * s)), (x, y + h_)], fill=MERAH)


def centang(d, x, y, uk=30):
    d.line([(x, y + uk * 0.52), (x + uk * 0.32, y + uk * 0.92), (x + uk, y + uk * 0.08)],
           fill=MERAH, width=max(6, int(uk * 0.19)), joint="curve")


def render(dek, outdir):
    img = Image.new("RGB", (W, H), BG)
    pola_titik(img)
    sisi, off = dek.get("busur", ("kanan", 0))
    busur(img, sisi, off)
    d = ImageDraw.Draw(img)
    masalah = []
    MAXW = W - 2 * M

    d.text((M, 96), LABEL, font=font(FT_R, 32), fill=PUTIH)
    d.text((W - M, 96), dek["index"], font=font(FT_B, 32), fill=PUTIH, anchor="ra")

    j = dek["jenis"]

    if j in ("cover", "tutup"):
        baris = dek["judul"]
        s = min(fit(b, FD, MAXW, 118, 56) for b in baris)
        f = font(FD, s)
        lh = int(s * 1.0)
        y = 300 if j == "cover" else 300
        for b in baris:
            d.text((M, y), b, font=f, fill=PUTIH)
            y += lh
        if j == "cover":
            panah(d, W - M - 150, min(y + 74, H - 250), 1.0)
        else:
            bookmark(d, M, y + 24)
            fd_ = font(FT_R, 36)
            for i, b in enumerate(bungkus(dek.get("body", ""), fd_, MAXW)):
                yy = y + 24 + 130 + i * 52
                if yy > H - 130:
                    masalah.append("body tutup lewat footer")
                    break
                d.text((M, yy), b, font=fd_, fill=PUTIH)

    elif j == "isi":
        sj = fit(dek["judul"], FD, MAXW, 104, 56)
        d.text((M, 300), dek["judul"], font=font(FD, sj), fill=PUTIH)
        fb = font(FT_R, 40)
        yb = 300 + int(sj * 1.15) + 76
        n = 0
        for i, b in enumerate(bungkus(dek["body"], fb, MAXW)):
            n = i + 1
            if yb + i * 62 > H - 170:
                masalah.append(f"body {dek['index']} lewat footer")
            d.text((M, yb + i * 62), b, font=fb, fill=PUTIH)
        panah(d, W - M - 140, min(yb + n * 62 + 34, H - 240), 0.9)

    elif j in ("mitos", "paham"):
        label_atas = dek.get("label_atas", "SALAH PAHAM")
        label_bawah = dek.get("label_bawah", "YANG SEBENARNYA")
        d.text((M, 268), label_atas, font=font(FT_B, 30), fill=MERAH)
        sm = fit(dek["mitos"], FD, MAXW, 62, 40)
        fm = font(FD, sm)
        ym = 312
        for i, b in enumerate(bungkus(dek["mitos"], fm, MAXW)):
            d.text((M, ym + i * int(sm * 1.12)), b, font=fm, fill=PUTIH)
        y_garis = ym + len(bungkus(dek["mitos"], fm, MAXW)) * int(sm * 1.12) + 12
        d.line([(M, y_garis), (M + 170, y_garis)], fill=MERAH, width=7)
        fy = y_garis + 34
        d.text((M, fy), label_bawah, font=font(FT_B, 28), fill=MERAH)
        fb = font(FT_R, 36)
        for i, b in enumerate(bungkus(dek["fakta"], fb, MAXW)):
            yy = fy + 46 + i * 54
            if yy > H - 140:
                masalah.append(f"fakta {dek['index']} lewat footer")
                break
            d.text((M, yy), b, font=fb, fill=PUTIH)

    elif j == "angka":
        d.text((M, 250), dek.get("keterangan", "").upper(), font=font(FT_B, 28), fill=MERAH)
        fa = font(FD, 210)
        d.text((M - 6, 292), dek["angka"], font=fa, fill=MERAH)
        d.text((M + font(FD, 210).getlength(dek["angka"]) + 16, 292 + 118), dek.get("satuan", ""),
               font=font(FD, 60), fill=PUTIH)
        fb = font(FT_R, 38)
        yb = 292 + 232
        for i, b in enumerate(bungkus(dek["body"], fb, MAXW)):
            yy = yb + i * 58
            if yy > H - 140:
                masalah.append(f"body angka {dek['index']} lewat footer")
                break
            d.text((M, yy), b, font=fb, fill=PUTIH)

    elif j == "check":
        sj = fit(dek["judul"], FD, MAXW, 84, 48)
        d.text((M, 300), dek["judul"], font=font(FD, sj), fill=PUTIH)
        yc = 300 + int(sj * 1.2) + 60
        fp = font(FT_B if False else FT_R, 38)
        for i, p in enumerate(dek["poin"]):
            yy = yc + i * 84
            if yy > H - 150:
                masalah.append(f"poin {dek['index']} lewat footer")
                break
            centang(d, M + 2, yy + 4, 30)
            d.text((M + 56, yy), p, font=fp, fill=PUTIH)

    d.text((M, H - 96), FOOTER_KIRI, font=font(FT_R, 32), fill=PUTIH)
    d.text((W - M, H - 96), FOOTER_KANAN, font=font(FT_R, 32), fill=PUTIH, anchor="ra")

    os.makedirs(outdir, exist_ok=True)
    out = os.path.join(outdir, f"das_car_{dek['index']}.jpg")
    img.save(out, quality=92)
    return out, masalah


def main():
    seri = "kampas"
    if "--seri" in sys.argv:
        seri = sys.argv[sys.argv.index("--seri") + 1]
    dek, outdir = SERI[seri]
    total = 0
    for s in dek:
        out, masalah = render(s, outdir)
        total += len(masalah)
        print(f"{out} {'✅' if not masalah else '❌ ' + '; '.join(masalah)}")
    print(f"selesai seri={seri} | masalah: {total}")


if __name__ == "__main__":
    main()
