#!/usr/bin/env python3
"""
Fibo – Logistics Deck Builder
Creates a 12-slide Google Slides presentation in Hebrew
Style B: Clean + modern, light background, black type, minimal color accents
"""

import json
import time
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build

# ── Auth ──────────────────────────────────────────────────────────────────────
with open('/root/.config/gws/credentials.json') as f:
    creds_data = json.load(f)
if 'installed' in creds_data:
    creds_data = creds_data['installed']

creds = Credentials(
    token=None,
    refresh_token=creds_data['refresh_token'],
    token_uri='https://oauth2.googleapis.com/token',
    client_id=creds_data['client_id'],
    client_secret=creds_data['client_secret'],
    scopes=['https://www.googleapis.com/auth/presentations', 'https://www.googleapis.com/auth/drive']
)

service = build('slides', 'v1', credentials=creds)

# ── Palette (Style B – clean modern) ────────────────────────────────────────
WHITE      = {'red': 1.0,   'green': 1.0,   'blue': 1.0}
OFF_WHITE  = {'red': 0.976, 'green': 0.976, 'blue': 0.976}
BLACK      = {'red': 0.08,  'green': 0.08,  'blue': 0.08}
DARK_GRAY  = {'red': 0.25,  'green': 0.25,  'blue': 0.25}
MID_GRAY   = {'red': 0.55,  'green': 0.55,  'blue': 0.55}
LIGHT_GRAY = {'red': 0.88,  'green': 0.88,  'blue': 0.88}
ACCENT     = {'red': 0.0,   'green': 0.36,  'blue': 0.8}   # Fibo blue
ACCENT_LIGHT = {'red': 0.88, 'green': 0.93, 'blue': 1.0}   # light blue bg

# ── Helpers ───────────────────────────────────────────────────────────────────
def emu(pt):
    """Convert points to EMU."""
    return int(pt * 12700)

def pt_to_emu(pt):
    return int(pt * 12700)

def cm(c):
    return int(c * 360000)

# Slide dimensions (16:9)
W = 9144000  # EMU
H = 5143500  # EMU

MARGIN = 457200  # 0.5 inch

def make_size(w_emu, h_emu):
    return {'width': {'magnitude': w_emu, 'unit': 'EMU'},
            'height': {'magnitude': h_emu, 'unit': 'EMU'}}

def make_transform(x, y, sx=1.0, sy=1.0):
    return {'scaleX': sx, 'scaleY': sy,
            'translateX': x, 'translateY': y, 'unit': 'EMU'}

def rgb(color):
    """For solidFill.color (backgrounds, shapes)."""
    return {'rgbColor': color}

def rgb_fg(color):
    """For text foregroundColor (requires opaqueColor wrapper)."""
    return {'opaqueColor': {'rgbColor': color}}

def add_text_box(requests, slide_id, obj_id, text, x, y, w, h,
                 font_size=18, bold=False, color=None, align='END',
                 font='IBM Plex Sans', italic=False):
    """Add a text box with Hebrew-compatible RTL alignment."""
    if color is None:
        color = BLACK
    requests.append({
        'createShape': {
            'objectId': obj_id,
            'shapeType': 'TEXT_BOX',
            'elementProperties': {
                'pageObjectId': slide_id,
                'size': make_size(w, h),
                'transform': make_transform(x, y)
            }
        }
    })
    requests.append({
        'insertText': {
            'objectId': obj_id,
            'text': text,
            'insertionIndex': 0
        }
    })
    requests.append({
        'updateTextStyle': {
            'objectId': obj_id,
            'style': {
                'bold': bold,
                'italic': italic,
                'fontSize': {'magnitude': font_size, 'unit': 'PT'},
                'foregroundColor': rgb_fg(color),
                'fontFamily': font,
            },
            'fields': 'bold,italic,fontSize,foregroundColor,fontFamily',
            'textRange': {'type': 'ALL'}
        }
    })
    requests.append({
        'updateParagraphStyle': {
            'objectId': obj_id,
            'style': {
                'alignment': align,  # END = right (RTL Hebrew)
                'direction': 'RIGHT_TO_LEFT',
            },
            'fields': 'alignment,direction',
            'textRange': {'type': 'ALL'}
        }
    })

def add_shape(requests, slide_id, obj_id, shape_type, x, y, w, h, fill_color, border_color=None):
    req = {
        'createShape': {
            'objectId': obj_id,
            'shapeType': shape_type,
            'elementProperties': {
                'pageObjectId': slide_id,
                'size': make_size(w, h),
                'transform': make_transform(x, y)
            }
        }
    }
    requests.append(req)
    fill_props = {
        'shapeBackgroundFill': {
            'solidFill': {'color': rgb(fill_color)}
        },
    }
    fill_fields = 'shapeBackgroundFill'
    if border_color:
        fill_props['outline'] = {
            'outlineFill': {'solidFill': {'color': rgb(border_color)}},
            'weight': {'magnitude': 1, 'unit': 'PT'}
        }
        fill_fields += ',outline'
    else:
        # No border — set outline to transparent by using dashStyle NONE
        fill_props['outline'] = {
            'dashStyle': 'SOLID',
            'outlineFill': {'solidFill': {'color': rgb(fill_color)}},
            'weight': {'magnitude': 1, 'unit': 'PT'}
        }
        fill_fields += ',outline'
    fill_req = {
        'updateShapeProperties': {
            'objectId': obj_id,
            'shapeProperties': fill_props,
            'fields': fill_fields
        }
    }
    requests.append(fill_req)

def set_slide_bg(requests, slide_id, color):
    requests.append({
        'updatePageProperties': {
            'objectId': slide_id,
            'pageProperties': {
                'pageBackgroundFill': {
                    'solidFill': {'color': rgb(color)}
                }
            },
            'fields': 'pageBackgroundFill'
        }
    })

def add_line(requests, slide_id, obj_id, x, y, w, h=2, color=None):
    """Add a horizontal rule / divider line."""
    if color is None:
        color = ACCENT
    requests.append({
        'createShape': {
            'objectId': obj_id,
            'shapeType': 'RECTANGLE',
            'elementProperties': {
                'pageObjectId': slide_id,
                'size': make_size(w, pt_to_emu(h)),
                'transform': make_transform(x, y)
            }
        }
    })
    requests.append({
        'updateShapeProperties': {
            'objectId': obj_id,
            'shapeProperties': {
                'shapeBackgroundFill': {'solidFill': {'color': rgb(color)}},
                'outline': {'outlineFill': {'solidFill': {'color': rgb(color)}},
                            'weight': {'magnitude': 1, 'unit': 'PT'}}
            },
            'fields': 'shapeBackgroundFill,outline'
        }
    })

# ── Slide content ─────────────────────────────────────────────────────────────
SLIDES = [
    # 1 – Cover
    {
        'id': 'slide_01',
        'type': 'cover',
        'title': 'החבילה יודעת איפה היא.',
        'subtitle': 'תמיד.',
        'tagline': 'FiBo — מעקב לוגיסטי פסיבי בזמן אמת',
        'date': 'מרץ 2026',
    },
    # 2 – Problem
    {
        'id': 'slide_02',
        'type': 'problem',
        'title': 'הלוגיסטיקה פועלת על סריקות ידניות',
        'points': [
            '📦  כל חבילה שלא נסרקת — היא כתם עיוור במערכת',
            '❌  אחוז אחד עד שלושה מהחבילות אובדות או מתעכבות מדי שנה',
            '💸  המייל האחרון הוא היקר ביותר — וגם הכי פחות נראה',
            '🔄  תורות, טעויות אנוש, אחריות שנעלמת בין ידיים',
        ],
        'kicker': 'כל סריקה שלא בוצעה — זה פרצה במידע, עיכוב בלקוח, ועלות שמישהו משלם.'
    },
    # 3 – Market
    {
        'id': 'slide_03',
        'type': 'stats',
        'title': 'שוק שמחכה לפתרון',
        'stats': [
            ('$500B+', 'שוק הלוגיסטיקה העולמי'),
            ('1–3%', 'חבילות אבודות / מאוחרות בשנה'),
            ('$700M+', 'הפסד שנתי בניו יורק לבד'),
            ('€700M', 'הפסד שנתי בפריז'),
        ],
        'body': 'מרבית מערכות המעקב הקיימות מבוססות על ברקוד או NFC קצר טווח — כלומר, אדם צריך לבצע פעולה. כל פעולה שלא בוצעה — נתון שאבד.'
    },
    # 4 – Solution
    {
        'id': 'slide_04',
        'type': 'solution',
        'title': 'מדבקת FiBo על כל חבילה.',
        'points': [
            '📡  פרוטוקול MFC (Medium Field Communication) — טווח 50 ס״מ',
            '🔕  פסיבי לחלוטין — אין צורך בסריקה, בלחיצה, בפעולה כלשהי',
            '🔐  מוצפן ברמת מערכת — פועל גם ללא אינטרנט',
            '🔗  API פתוח — משתלב ישירות עם WMS / ERP קיים',
        ],
        'kicker': 'החבילה נכנסת לטווח האנטנה → המערכת מזהה, מתעדת, מעדכנת. הכל ברקע. אפס חיכוך.'
    },
    # 5 – How it works
    {
        'id': 'slide_05',
        'type': 'how',
        'title': 'ככה זה עובד',
        'steps': [
            ('01', 'מדבקת FiBo', 'מודבקת על החבילה — פסיבית, קטנה, ללא סוללה'),
            ('02', 'אנטנת זיהוי', 'מותקנת בנקודת ביקורת — מחסן, רציף, רכב, מייל אחרון'),
            ('03', 'זיהוי אוטומטי', 'החבילה עוברת בטווח 50 ס״מ → אירוע נרשם אוטומטית'),
            ('04', 'שרת מקומי', 'מעבד את הנתונים locally, ללא תלות ברשת חיצונית'),
            ('05', 'דשבורד + API', 'נתוני מיקום בזמן אמת → ל-WMS, ERP, ולכל מערכת קיימת'),
        ]
    },
    # 6 – Dashboard / What you see
    {
        'id': 'slide_06',
        'type': 'dashboard',
        'title': 'מה אתם רואים',
        'points': [
            '🗂️  כל חבילה, כל מיקום, היסטוריית מסלול מלאה',
            '🔔  התראות חריגה — עיכוב, סטייה ממסלול, חבילה שנעצרה',
            '📊  ניתוח דפוסי תנועה לשיפור תהליכים',
            '🔌  אינטגרציה מלאה עם מערכות קיימות דרך API פתוח',
        ],
        'kicker': 'ממשק אחד. מידע שלם. בזמן אמת.'
    },
    # 7 – Competitive edge
    {
        'id': 'slide_07',
        'type': 'comparison',
        'title': 'FiBo מול החלופות',
        'rows': [
            ('קריטריון', 'ברקוד', 'GPS', 'RFID שער', 'FiBo'),
            ('פסיבי — ללא פעולה', '✗', '✗', '✗', '✓'),
            ('ללא סוללה בתג', '✓', '✗', '✓', '✓'),
            ('ללא שערים / תשתית יקרה', '✓', '✓', '✗', '✓'),
            ('טווח 50 ס״מ בתנועה', '✗', '✗', '✓', '✓'),
            ('עובד offline', '✓', '✗', '✗', '✓'),
            ('אינטגרציה API פתוח', 'חלקי', 'חלקי', 'חלקי', '✓'),
        ]
    },
    # 8 – Use cases
    {
        'id': 'slide_08',
        'type': 'usecases',
        'title': 'איפה FiBo עובד',
        'cases': [
            ('🏭', 'מחסן', 'כניסה / יציאה אוטומטית, ספירת מלאי בזמן אמת'),
            ('🚚', 'הובלה', 'עדכוני טעינה / פריקה ללא נהג, ניטור רכב'),
            ('📦', 'מייל אחרון', 'אישור מסירה אוטומטי, ללא צורך בחתימה דיגיטלית'),
            ('❄️', 'שרשרת קרה', 'תיעוד כל נקודת מעבר לצרכי תאימות'),
            ('🔁', 'החזרות', 'קבלה ומיון אוטומטיים — בלי לייצר צוואר בקבוק'),
            ('💎', 'סחורה בעלת ערך', 'ניטור צמוד, התראות חריגה בזמן אמת'),
        ]
    },
    # 9 – Proof / Pilots
    {
        'id': 'slide_09',
        'type': 'proof',
        'title': 'הטכנולוגיה כבר בשטח',
        'points': [
            '🚌  פיילוט פעיל עם חברת דן — תיקוף אוטומטי של נסיעות בתחבורה ציבורית',
            '🪖  פריסה מבצעית עם צבא ההגנה לישראל וחיל הרפואה',
            '🏗️  בפיתוח מתקדם עם שותפים בתשתיות חכמות ומתקני תעשייה',
            '📡  הפרוטוקול פותח מתוך דרישה מבצעית אמיתית — לא POC מעבדתי',
        ],
        'kicker': 'הטכנולוגיה שעובדת בתוך אוטובוס צפוף — תעבוד גם בתוך מחסן שלכם.'
    },
    # 10 – Business model
    {
        'id': 'slide_10',
        'type': 'model',
        'title': 'מודל עסקי',
        'rows': [
            ('רכיב', 'תמחור', 'הערות'),
            ('מערכת FiBo — הטמעה + שנה ראשונה', '₪300K + ₪500K/שנה', 'כולל תמיכה ורישיון'),
            ('אנטנת FiBo', '₪2,500/שנה השכרה + ₪1,000 התקנה', 'תחזוקה כלולה'),
            ('מערכת הדפסת תגים', '₪10,000/שנה', 'עצמאית, ללא אינטרנט'),
        ],
        'kicker': 'מחיר קבוע לפי נפח — ללא עמלה לסריקה, ללא הפתעות.'
    },
    # 11 – Team
    {
        'id': 'slide_11',
        'type': 'team',
        'title': 'הצוות',
        'members': [
            ('אור מרכוס', 'מנכ״ל ומייסד משותף', 'קצין פעיל לשעבר, מנהל מוצר ביחידות 8200 וממר״ם. הוביל פרויקטים טכנולוגיים בקנה מידה ארצי.'),
            ('ניב בייביץ', 'סמנכ״ל טכנולוגיות ומייסד משותף', 'מומחה ארכיטקטורת סייבר, יחידה 108. הקים בית תוכנה וניהל מאות עובדים.'),
        ],
        'note': 'צוות מפתחים — שיתוף פעולה עם תוכנית המצוינות "מפתחים", המאפשרת כוח אדם איכותי וגמיש.'
    },
    # 12 – Next step
    {
        'id': 'slide_12',
        'type': 'cta',
        'title': 'בואו נתחיל בפיילוט',
        'offer': [
            '📍  הגדרת נקודות ביקורת יחד — מחסן, רציף, רכב',
            '📡  התקנת אנטנות ב-X מיקומים',
            '⏱️  90 יום — נמדוד ביחד: כיסוי, דיוק, זמן תגובה',
            '📈  לאחר הפיילוט: מסלול פריסה מלא',
        ],
        'kicker': 'הטכנולוגיה מוכנה. השוק מוכן. השאלה היחידה: כמה חבילות אבדו לכם החודש?',
        'company': 'fibonacci',
        'contact': 'fibonacci-systems.com'
    },
]

# ── Build the presentation ────────────────────────────────────────────────────
def build_presentation():
    # Create blank presentation
    pres = service.presentations().create(body={
        'title': 'Fibo – Logistics Deck | מעקב חבילות'
    }).execute()
    pres_id = pres['presentationId']
    print(f"Created: https://docs.google.com/presentation/d/{pres_id}/edit")

    # Get the default empty slide ID
    default_slide_id = pres['slides'][0]['objectId']

    # Delete the default slide first
    service.presentations().batchUpdate(
        presentationId=pres_id,
        body={'requests': [{'deleteObject': {'objectId': default_slide_id}}]}
    ).execute()
    time.sleep(0.3)

    # We'll build slides one by one
    for i, slide_data in enumerate(SLIDES):
        slide_id = slide_data['id']
        requests = []

        # Add the slide
        requests.append({
            'createSlide': {
                'objectId': slide_id,
                'insertionIndex': i,
                'slideLayoutReference': {'predefinedLayout': 'BLANK'}
            }
        })

        service.presentations().batchUpdate(
            presentationId=pres_id,
            body={'requests': requests}
        ).execute()
        time.sleep(0.3)

        # Now build slide content
        content_requests = []
        build_slide(content_requests, slide_data)

        if content_requests:
            service.presentations().batchUpdate(
                presentationId=pres_id,
                body={'requests': content_requests}
            ).execute()
            time.sleep(0.3)

        print(f"  ✓ Slide {i+1}: {slide_data.get('title', slide_data['id'])}")

    return pres_id


def build_slide(requests, data):
    slide_id = data['id']
    stype = data['type']

    if stype == 'cover':
        build_cover(requests, slide_id, data)
    elif stype == 'problem':
        build_bullets(requests, slide_id, data, accent=True)
    elif stype == 'stats':
        build_stats(requests, slide_id, data)
    elif stype == 'solution':
        build_bullets(requests, slide_id, data, accent=False)
    elif stype == 'how':
        build_steps(requests, slide_id, data)
    elif stype == 'dashboard':
        build_bullets(requests, slide_id, data, accent=False)
    elif stype == 'comparison':
        build_comparison(requests, slide_id, data)
    elif stype == 'usecases':
        build_usecases(requests, slide_id, data)
    elif stype == 'proof':
        build_bullets(requests, slide_id, data, accent=False)
    elif stype == 'model':
        build_model(requests, slide_id, data)
    elif stype == 'team':
        build_team(requests, slide_id, data)
    elif stype == 'cta':
        build_cta(requests, slide_id, data)


def build_cover(requests, slide_id, data):
    set_slide_bg(requests, slide_id, WHITE)
    # Left accent bar
    add_shape(requests, slide_id, f'{slide_id}_bar', 'RECTANGLE',
              0, 0, pt_to_emu(6), H, ACCENT)
    # Slide number subtle
    add_text_box(requests, slide_id, f'{slide_id}_num', '01',
                 W - cm(3), H - cm(1.5), cm(2.5), cm(1),
                 font_size=11, color=MID_GRAY, align='END')
    # Main title
    add_text_box(requests, slide_id, f'{slide_id}_title', data['title'],
                 cm(1.5), cm(3.5), W - cm(3), cm(3),
                 font_size=52, bold=True, color=BLACK, align='END')
    # Subtitle (accent colored)
    add_text_box(requests, slide_id, f'{slide_id}_sub', data['subtitle'],
                 cm(1.5), cm(6.8), W - cm(3), cm(1.8),
                 font_size=52, bold=True, color=ACCENT, align='END')
    # Tagline
    add_line(requests, slide_id, f'{slide_id}_rule', cm(1.5), cm(9.2), cm(8), 2, LIGHT_GRAY)
    add_text_box(requests, slide_id, f'{slide_id}_tag', data['tagline'],
                 cm(1.5), cm(9.5), W - cm(3), cm(1.2),
                 font_size=18, color=DARK_GRAY, align='END')
    # Date
    add_text_box(requests, slide_id, f'{slide_id}_date', data['date'],
                 cm(1.5), cm(11.2), W - cm(3), cm(0.8),
                 font_size=13, color=MID_GRAY, align='END')
    # Bottom accent line
    add_shape(requests, slide_id, f'{slide_id}_bot', 'RECTANGLE',
              0, H - pt_to_emu(4), W, pt_to_emu(4), ACCENT)


def build_bullets(requests, slide_id, data, accent=False):
    set_slide_bg(requests, slide_id, WHITE)
    n = SLIDES.index(data) + 1
    # Left bar
    add_shape(requests, slide_id, f'{slide_id}_bar', 'RECTANGLE',
              0, 0, pt_to_emu(6), H, ACCENT)
    # Slide number
    add_text_box(requests, slide_id, f'{slide_id}_num', f'{n:02d}',
                 W - cm(3), H - cm(1.5), cm(2.5), cm(1),
                 font_size=11, color=MID_GRAY, align='END')
    # Title
    add_text_box(requests, slide_id, f'{slide_id}_title', data['title'],
                 cm(1.5), cm(1.2), W - cm(2.5), cm(1.8),
                 font_size=30, bold=True, color=BLACK, align='END')
    add_line(requests, slide_id, f'{slide_id}_rule', cm(1.5), cm(3.3), cm(12), 1.5, ACCENT)

    # Bullets
    y = cm(3.8)
    for j, point in enumerate(data.get('points', [])):
        add_text_box(requests, slide_id, f'{slide_id}_p{j}', point,
                     cm(1.5), y, W - cm(2.5), cm(1.1),
                     font_size=17, color=BLACK, align='END')
        y += cm(1.35)

    # Kicker
    if 'kicker' in data:
        add_shape(requests, slide_id, f'{slide_id}_kbg', 'RECTANGLE',
                  cm(1.5), H - cm(3), W - cm(2.5), cm(2.2), ACCENT_LIGHT)
        add_text_box(requests, slide_id, f'{slide_id}_kicker', data['kicker'],
                     cm(2), H - cm(2.9), W - cm(3.5), cm(2),
                     font_size=15, color=ACCENT, italic=True, align='END')

    add_shape(requests, slide_id, f'{slide_id}_bot', 'RECTANGLE',
              0, H - pt_to_emu(4), W, pt_to_emu(4), ACCENT)


def build_stats(requests, slide_id, data):
    set_slide_bg(requests, slide_id, WHITE)
    n = SLIDES.index(data) + 1
    add_shape(requests, slide_id, f'{slide_id}_bar', 'RECTANGLE',
              0, 0, pt_to_emu(6), H, ACCENT)
    add_text_box(requests, slide_id, f'{slide_id}_num', f'{n:02d}',
                 W - cm(3), H - cm(1.5), cm(2.5), cm(1),
                 font_size=11, color=MID_GRAY, align='END')
    add_text_box(requests, slide_id, f'{slide_id}_title', data['title'],
                 cm(1.5), cm(1.2), W - cm(2.5), cm(1.8),
                 font_size=30, bold=True, color=BLACK, align='END')
    add_line(requests, slide_id, f'{slide_id}_rule', cm(1.5), cm(3.3), cm(12), 1.5, ACCENT)

    # Stats in a 2x2 grid
    stats = data.get('stats', [])
    positions = [
        (cm(1.5), cm(4.0)),   (cm(13.5), cm(4.0)),
        (cm(1.5), cm(7.5)),   (cm(13.5), cm(7.5)),
    ]
    box_w = cm(10)
    for j, ((num, label), (sx, sy)) in enumerate(zip(stats, positions)):
        oid = f'{slide_id}_stat{j}'
        # Number
        add_text_box(requests, slide_id, f'{oid}_n', num,
                     sx, sy, box_w, cm(1.8),
                     font_size=38, bold=True, color=ACCENT, align='END')
        add_text_box(requests, slide_id, f'{oid}_l', label,
                     sx, sy + cm(1.9), box_w, cm(0.9),
                     font_size=15, color=DARK_GRAY, align='END')

    # Body
    if 'body' in data:
        add_shape(requests, slide_id, f'{slide_id}_bbg', 'RECTANGLE',
                  cm(1.5), H - cm(3.2), W - cm(2.5), cm(2.5), OFF_WHITE)
        add_text_box(requests, slide_id, f'{slide_id}_body', data['body'],
                     cm(2), H - cm(3.0), W - cm(3.5), cm(2.2),
                     font_size=14, color=DARK_GRAY, align='END')

    add_shape(requests, slide_id, f'{slide_id}_bot', 'RECTANGLE',
              0, H - pt_to_emu(4), W, pt_to_emu(4), ACCENT)


def build_steps(requests, slide_id, data):
    set_slide_bg(requests, slide_id, WHITE)
    n = SLIDES.index(data) + 1
    add_shape(requests, slide_id, f'{slide_id}_bar', 'RECTANGLE',
              0, 0, pt_to_emu(6), H, ACCENT)
    add_text_box(requests, slide_id, f'{slide_id}_num', f'{n:02d}',
                 W - cm(3), H - cm(1.5), cm(2.5), cm(1),
                 font_size=11, color=MID_GRAY, align='END')
    add_text_box(requests, slide_id, f'{slide_id}_title', data['title'],
                 cm(1.5), cm(1.2), W - cm(2.5), cm(1.8),
                 font_size=30, bold=True, color=BLACK, align='END')
    add_line(requests, slide_id, f'{slide_id}_rule', cm(1.5), cm(3.3), cm(12), 1.5, ACCENT)

    steps = data.get('steps', [])
    col_w = (W - cm(3)) // max(len(steps), 1)
    for j, (num, name, desc) in enumerate(steps):
        x = cm(1.5) + j * col_w
        # Number badge
        add_shape(requests, slide_id, f'{slide_id}_sbg{j}', 'ELLIPSE',
                  x + col_w // 2 - cm(0.7), cm(4.0), cm(1.4), cm(1.4), ACCENT)
        add_text_box(requests, slide_id, f'{slide_id}_sn{j}', num,
                     x + col_w // 2 - cm(0.7), cm(4.05), cm(1.4), cm(1.3),
                     font_size=14, bold=True, color=WHITE, align='CENTER')
        # Name
        add_text_box(requests, slide_id, f'{slide_id}_snm{j}', name,
                     x, cm(5.8), col_w - cm(0.2), cm(1.0),
                     font_size=15, bold=True, color=BLACK, align='CENTER')
        # Desc
        add_text_box(requests, slide_id, f'{slide_id}_sd{j}', desc,
                     x, cm(6.9), col_w - cm(0.2), cm(2.5),
                     font_size=12, color=DARK_GRAY, align='CENTER')
        # Connector arrow (not last)
        if j < len(steps) - 1:
            mid_x = x + col_w - cm(0.3)
            add_text_box(requests, slide_id, f'{slide_id}_arr{j}', '→',
                         mid_x, cm(4.4), cm(0.8), cm(0.8),
                         font_size=16, color=ACCENT, align='CENTER')

    add_shape(requests, slide_id, f'{slide_id}_bot', 'RECTANGLE',
              0, H - pt_to_emu(4), W, pt_to_emu(4), ACCENT)


def build_comparison(requests, slide_id, data):
    set_slide_bg(requests, slide_id, WHITE)
    n = SLIDES.index(data) + 1
    add_shape(requests, slide_id, f'{slide_id}_bar', 'RECTANGLE',
              0, 0, pt_to_emu(6), H, ACCENT)
    add_text_box(requests, slide_id, f'{slide_id}_num', f'{n:02d}',
                 W - cm(3), H - cm(1.5), cm(2.5), cm(1),
                 font_size=11, color=MID_GRAY, align='END')
    add_text_box(requests, slide_id, f'{slide_id}_title', data['title'],
                 cm(1.5), cm(1.2), W - cm(2.5), cm(1.8),
                 font_size=30, bold=True, color=BLACK, align='END')
    add_line(requests, slide_id, f'{slide_id}_rule', cm(1.5), cm(3.3), cm(12), 1.5, ACCENT)

    rows = data.get('rows', [])
    row_h = cm(1.1)
    col_widths = [cm(5.5), cm(2.8), cm(2.8), cm(3.2), cm(3.5)]
    col_x = [cm(1.5), cm(7.2), cm(10.1), cm(13.0), cm(16.3)]

    for ri, row in enumerate(rows):
        y = cm(3.8) + ri * row_h
        is_header = (ri == 0)
        bg_col = ACCENT if is_header else (OFF_WHITE if ri % 2 == 0 else WHITE)
        txt_col = WHITE if is_header else (BLACK if ri < len(rows) - 1 else ACCENT)

        if is_header or ri % 2 == 0:
            add_shape(requests, slide_id, f'{slide_id}_rbg{ri}', 'RECTANGLE',
                      cm(1.5), y, W - cm(2.5), row_h, bg_col)

        for ci, (cell, cx, cw) in enumerate(zip(row, col_x, col_widths)):
            is_fibo_col = (ci == 4)
            tc = WHITE if is_header else (ACCENT if is_fibo_col and cell == '✓' else BLACK)
            fw = is_fibo_col or is_header
            add_text_box(requests, slide_id, f'{slide_id}_r{ri}c{ci}', cell,
                         cx, y + pt_to_emu(2), cw, row_h - pt_to_emu(2),
                         font_size=13, bold=fw, color=tc, align='CENTER')

    add_shape(requests, slide_id, f'{slide_id}_bot', 'RECTANGLE',
              0, H - pt_to_emu(4), W, pt_to_emu(4), ACCENT)


def build_usecases(requests, slide_id, data):
    set_slide_bg(requests, slide_id, WHITE)
    n = SLIDES.index(data) + 1
    add_shape(requests, slide_id, f'{slide_id}_bar', 'RECTANGLE',
              0, 0, pt_to_emu(6), H, ACCENT)
    add_text_box(requests, slide_id, f'{slide_id}_num', f'{n:02d}',
                 W - cm(3), H - cm(1.5), cm(2.5), cm(1),
                 font_size=11, color=MID_GRAY, align='END')
    add_text_box(requests, slide_id, f'{slide_id}_title', data['title'],
                 cm(1.5), cm(1.2), W - cm(2.5), cm(1.8),
                 font_size=30, bold=True, color=BLACK, align='END')
    add_line(requests, slide_id, f'{slide_id}_rule', cm(1.5), cm(3.3), cm(12), 1.5, ACCENT)

    cases = data.get('cases', [])
    card_w = cm(7.8)
    card_h = cm(3.2)
    cols = 3
    pad_x = cm(0.5)

    for j, (icon, name, desc) in enumerate(cases):
        col = j % cols
        row = j // cols
        x = cm(1.5) + col * (card_w + pad_x)
        y = cm(4.0) + row * (card_h + cm(0.4))
        add_shape(requests, slide_id, f'{slide_id}_cbg{j}', 'RECTANGLE',
                  x, y, card_w, card_h, OFF_WHITE)
        add_text_box(requests, slide_id, f'{slide_id}_ci{j}', icon,
                     x + cm(0.3), y + cm(0.3), cm(1.2), cm(1.1),
                     font_size=22, align='CENTER')
        add_text_box(requests, slide_id, f'{slide_id}_cn{j}', name,
                     x + cm(1.6), y + cm(0.3), card_w - cm(2), cm(0.9),
                     font_size=15, bold=True, color=BLACK, align='END')
        add_text_box(requests, slide_id, f'{slide_id}_cd{j}', desc,
                     x + cm(0.3), y + cm(1.4), card_w - cm(0.6), cm(1.6),
                     font_size=12, color=DARK_GRAY, align='END')

    add_shape(requests, slide_id, f'{slide_id}_bot', 'RECTANGLE',
              0, H - pt_to_emu(4), W, pt_to_emu(4), ACCENT)


def build_model(requests, slide_id, data):
    set_slide_bg(requests, slide_id, WHITE)
    n = SLIDES.index(data) + 1
    add_shape(requests, slide_id, f'{slide_id}_bar', 'RECTANGLE',
              0, 0, pt_to_emu(6), H, ACCENT)
    add_text_box(requests, slide_id, f'{slide_id}_num', f'{n:02d}',
                 W - cm(3), H - cm(1.5), cm(2.5), cm(1),
                 font_size=11, color=MID_GRAY, align='END')
    add_text_box(requests, slide_id, f'{slide_id}_title', data['title'],
                 cm(1.5), cm(1.2), W - cm(2.5), cm(1.8),
                 font_size=30, bold=True, color=BLACK, align='END')
    add_line(requests, slide_id, f'{slide_id}_rule', cm(1.5), cm(3.3), cm(12), 1.5, ACCENT)

    rows = data.get('rows', [])
    row_h = cm(1.6)
    col_widths = [cm(8), cm(5.5), cm(5)]
    col_x = [cm(1.5), cm(10.0), cm(15.7)]

    for ri, row in enumerate(rows):
        y = cm(3.8) + ri * row_h
        is_header = (ri == 0)
        bg = ACCENT if is_header else (OFF_WHITE if ri % 2 == 0 else WHITE)
        add_shape(requests, slide_id, f'{slide_id}_rbg{ri}', 'RECTANGLE',
                  cm(1.5), y, W - cm(2.5), row_h, bg)
        for ci, (cell, cx, cw) in enumerate(zip(row, col_x, col_widths)):
            tc = WHITE if is_header else (ACCENT if ci == 1 else BLACK)
            fw = is_header
            add_text_box(requests, slide_id, f'{slide_id}_r{ri}c{ci}', cell,
                         cx + cm(0.2), y + pt_to_emu(4), cw, row_h - pt_to_emu(4),
                         font_size=13, bold=fw, color=tc, align='END')

    if 'kicker' in data:
        add_shape(requests, slide_id, f'{slide_id}_kbg', 'RECTANGLE',
                  cm(1.5), H - cm(2.8), W - cm(2.5), cm(2.0), ACCENT_LIGHT)
        add_text_box(requests, slide_id, f'{slide_id}_kicker', data['kicker'],
                     cm(2), H - cm(2.6), W - cm(3.5), cm(1.8),
                     font_size=14, color=ACCENT, italic=True, align='END')

    add_shape(requests, slide_id, f'{slide_id}_bot', 'RECTANGLE',
              0, H - pt_to_emu(4), W, pt_to_emu(4), ACCENT)


def build_team(requests, slide_id, data):
    set_slide_bg(requests, slide_id, WHITE)
    n = SLIDES.index(data) + 1
    add_shape(requests, slide_id, f'{slide_id}_bar', 'RECTANGLE',
              0, 0, pt_to_emu(6), H, ACCENT)
    add_text_box(requests, slide_id, f'{slide_id}_num', f'{n:02d}',
                 W - cm(3), H - cm(1.5), cm(2.5), cm(1),
                 font_size=11, color=MID_GRAY, align='END')
    add_text_box(requests, slide_id, f'{slide_id}_title', data['title'],
                 cm(1.5), cm(1.2), W - cm(2.5), cm(1.8),
                 font_size=30, bold=True, color=BLACK, align='END')
    add_line(requests, slide_id, f'{slide_id}_rule', cm(1.5), cm(3.3), cm(12), 1.5, ACCENT)

    members = data.get('members', [])
    card_w = cm(11)
    for j, (name, role, bio) in enumerate(members):
        x = cm(1.5) + j * (card_w + cm(1))
        y = cm(4.2)
        # Avatar placeholder
        add_shape(requests, slide_id, f'{slide_id}_av{j}', 'ELLIPSE',
                  x, y, cm(2), cm(2), ACCENT_LIGHT)
        add_text_box(requests, slide_id, f'{slide_id}_avi{j}',
                     name[0],  # initial
                     x, y, cm(2), cm(2),
                     font_size=22, bold=True, color=ACCENT, align='CENTER')
        # Name
        add_text_box(requests, slide_id, f'{slide_id}_nm{j}', name,
                     x + cm(2.3), y, card_w - cm(2.5), cm(1.0),
                     font_size=20, bold=True, color=BLACK, align='END')
        # Role
        add_text_box(requests, slide_id, f'{slide_id}_rl{j}', role,
                     x + cm(2.3), y + cm(1.1), card_w - cm(2.5), cm(0.8),
                     font_size=14, color=ACCENT, align='END')
        # Bio
        add_text_box(requests, slide_id, f'{slide_id}_bio{j}', bio,
                     x, y + cm(2.4), card_w, cm(2.5),
                     font_size=13, color=DARK_GRAY, align='END')

    # Note
    if 'note' in data:
        add_shape(requests, slide_id, f'{slide_id}_nbg', 'RECTANGLE',
                  cm(1.5), H - cm(2.8), W - cm(2.5), cm(2.0), OFF_WHITE)
        add_text_box(requests, slide_id, f'{slide_id}_note', data['note'],
                     cm(2), H - cm(2.6), W - cm(3.5), cm(1.8),
                     font_size=13, color=DARK_GRAY, align='END')

    add_shape(requests, slide_id, f'{slide_id}_bot', 'RECTANGLE',
              0, H - pt_to_emu(4), W, pt_to_emu(4), ACCENT)


def build_cta(requests, slide_id, data):
    set_slide_bg(requests, slide_id, WHITE)
    n = len(SLIDES)
    add_shape(requests, slide_id, f'{slide_id}_bar', 'RECTANGLE',
              0, 0, pt_to_emu(6), H, ACCENT)
    add_text_box(requests, slide_id, f'{slide_id}_num', f'{n:02d}',
                 W - cm(3), H - cm(1.5), cm(2.5), cm(1),
                 font_size=11, color=MID_GRAY, align='END')
    add_text_box(requests, slide_id, f'{slide_id}_title', data['title'],
                 cm(1.5), cm(1.2), W - cm(2.5), cm(1.8),
                 font_size=30, bold=True, color=BLACK, align='END')
    add_line(requests, slide_id, f'{slide_id}_rule', cm(1.5), cm(3.3), cm(12), 1.5, ACCENT)

    y = cm(4.0)
    for j, point in enumerate(data.get('offer', [])):
        add_text_box(requests, slide_id, f'{slide_id}_o{j}', point,
                     cm(1.5), y, W - cm(2.5), cm(1.05),
                     font_size=17, color=BLACK, align='END')
        y += cm(1.2)

    # Big kicker box
    add_shape(requests, slide_id, f'{slide_id}_kbg', 'RECTANGLE',
              cm(1.5), H - cm(3.5), W - cm(2.5), cm(2.8), ACCENT)
    add_text_box(requests, slide_id, f'{slide_id}_kicker', data['kicker'],
                 cm(2), H - cm(3.3), W - cm(3.5), cm(2.4),
                 font_size=15, bold=True, color=WHITE, align='END')

    add_shape(requests, slide_id, f'{slide_id}_bot', 'RECTANGLE',
              0, H - pt_to_emu(4), W, pt_to_emu(4), ACCENT)


# ── Run ───────────────────────────────────────────────────────────────────────
if __name__ == '__main__':
    pres_id = build_presentation()
    print(f"\n✅ Done!")
    print(f"🔗 https://docs.google.com/presentation/d/{pres_id}/edit")

    # Save the link
    with open('/root/.openclaw/workspace/work/pitches/fibo/logistics-deck/deck_url.txt', 'w') as f:
        f.write(f"https://docs.google.com/presentation/d/{pres_id}/edit\n")
