#!/usr/bin/env python3
"""What-If Shorts bot. Uses stock video from Pexels/Pixabay and Pollinations
AI images as fallback, then assembles a vertical Short with FFmpeg.
Selenium is only used for ElevenLabs TTS voiceover.

Output: 1080x1920 MP4 (~30s) + metadata .txt per run.
"""

import os
import sys
import time
import json
import wave
import array
import random
import sqlite3
import datetime
import subprocess
import tempfile
import traceback
import argparse
import glob
import math
import textwrap
import urllib.request
import urllib.parse
import urllib.error

# ---------------------------------------------------------------------------
# Paths
# ---------------------------------------------------------------------------
BOT_DIR = '/home/melon/public_html/shorts'
OUTPUT_DIR = os.path.join(BOT_DIR, 'output')
DOWNLOAD_DIR = os.path.join(BOT_DIR, 'downloads')
STATE_DB = os.path.join(BOT_DIR, 'bot/state.db')
LOG_FILE = os.path.join(BOT_DIR, 'logs/run.log')
CHROME_PROFILE = '/home/melon/.chrome_whatif_profile'

FONT_TITLE = '/root/.fonts/chess_shorts/Oswald-Bold.ttf'
FONT_BODY = '/root/.fonts/chess_shorts/Inter-Bold.ttf'
FONT_FALLBACK = '/usr/share/fonts/liberation-sans/LiberationSans-Bold.ttf'

WIDTH = 1080
HEIGHT = 1920
FPS = 30

# ---------------------------------------------------------------------------
# API Keys — stock video providers (free tier)
# ---------------------------------------------------------------------------
PEXELS_API_KEY = 'JZKNE0GM5eVfC9GU4JWoIGMJCUKBr0Q7hgDhRqj6d8fK6eHtxK7XBiHC'
PIXABAY_API_KEY = ''

# ---------------------------------------------------------------------------
# Concepts — each is a complete Short script
# ---------------------------------------------------------------------------
CONCEPTS = [
    {
        "id": "humans_fly",
        "title": "What If Humans Could Fly?",
        "hook": "What if you woke up tomorrow and could FLY?",
        "scenes": [
            {"narration": "Imagine waking up one morning, stepping outside, and lifting off the ground. No wings. No machines. Just you, soaring through the sky.",
             "image_prompt": "A person floating above a modern city street, arms spread wide, other people flying in the background between skyscrapers, golden hour sunlight, photorealistic, cinematic, 9:16 vertical",
             "video_keywords": ["paragliding city aerial", "skydiving above city", "aerial view city sunset"]},
            {"narration": "Roads would become useless. Highways empty. The entire world would be redesigned for the sky. Airports? Gone. Traffic jams? In the clouds.",
             "image_prompt": "Abandoned empty highway with overgrown plants, people flying overhead between buildings, futuristic sky lanes with glowing paths, dramatic aerial view, photorealistic, 9:16 vertical",
             "video_keywords": ["abandoned road nature", "empty highway aerial", "overgrown road"]},
            {"narration": "But here is the terrifying part. Imagine the mid-air collisions. The sky rage. Falling asleep and plummeting from a thousand feet.",
             "image_prompt": "Dramatic scene of a person falling from the sky above a city at night, motion blur, city lights below, terrifying perspective looking down, cinematic dark mood, photorealistic, 9:16 vertical",
             "video_keywords": ["skydiver falling night", "base jumping city", "falling through sky"]},
            {"narration": "Flying would change everything. The question is, would you actually want it?",
             "image_prompt": "A person standing on the edge of a tall building rooftop at sunset, looking out at a sky full of flying people, inspirational mood, golden light, photorealistic cinematic, 9:16 vertical",
             "video_keywords": ["person rooftop sunset city", "city skyline sunset view", "standing edge building"]},
        ],
        "hashtags": "#whatif #whatifhumanscouldfly #shorts #mindblown #science #imagination #future #viral",
    },
    {
        "id": "gravity_off_10s",
        "title": "What If Gravity Stopped for 10 Seconds?",
        "hook": "What if gravity just SWITCHED OFF for ten seconds?",
        "scenes": [
            {"narration": "The moment gravity stops, everything not bolted down launches upward. Cars. People. Oceans. All of it, floating.",
             "image_prompt": "Cars and people and furniture floating upward off a city street, zero gravity chaos, water rising from fountains into the air, photorealistic dramatic lighting, 9:16 vertical",
             "video_keywords": ["astronaut floating space station", "zero gravity water droplet", "objects floating weightless"]},
            {"narration": "The oceans would lift off the seabed like giant floating blobs. Entire lakes hovering in mid-air. Fish swimming through nothing.",
             "image_prompt": "Massive blob of ocean water floating above the seabed with fish inside it, underwater scene becoming aerial, dramatic blue lighting, surreal photorealistic, 9:16 vertical",
             "video_keywords": ["ocean waves crashing slow motion", "fish swimming underwater school", "bubbles rising underwater blue"]},
            {"narration": "And then, ten seconds later, gravity snaps back. Everything crashes down at once. The destruction would be unimaginable.",
             "image_prompt": "Cars and debris crashing down onto a city street from above, massive impact destruction, dust clouds, dramatic action scene, dark cinematic lighting, photorealistic, 9:16 vertical",
             "video_keywords": ["building demolition explosion", "earthquake destruction rubble", "avalanche rocks falling"]},
            {"narration": "Ten seconds. That is all it would take to reshape the entire planet forever.",
             "image_prompt": "Devastated city landscape after catastrophic event, cracked roads, toppled buildings, eerie calm after destruction, dramatic wide shot, photorealistic cinematic, 9:16 vertical",
             "video_keywords": ["abandoned ruins city drone", "tornado storm destruction aftermath", "earthquake damaged buildings"]},
        ],
        "hashtags": "#whatif #gravity #shorts #science #mindblown #physics #viral #imagination",
    },
    {
        "id": "earth_saturn_rings",
        "title": "What If Earth Had Saturn's Rings?",
        "hook": "What if you looked up and saw THIS in the sky?",
        "scenes": [
            {"narration": "If Earth had rings like Saturn, our sky would never look the same. Massive glowing arcs stretching from horizon to horizon.",
             "image_prompt": "View from a city street looking up at massive Saturn-like rings arcing across Earth's sky, breathtaking cosmic view, people looking up in awe, golden sunset, photorealistic, 9:16 vertical",
             "video_keywords": ["sunset clouds dramatic timelapse", "golden hour sky city", "epic sky colors landscape"]},
            {"narration": "From the equator, the rings would appear as a thin bright line cutting the sky in half. But from higher latitudes, they would dominate everything.",
             "image_prompt": "Northern European city with massive planetary rings visible in the sky taking up half the view, aurora borealis mixed with ring light, nighttime, photorealistic, 9:16 vertical",
             "video_keywords": ["aurora borealis timelapse green", "northern lights dancing sky", "milky way night sky stars"]},
            {"narration": "At night, the rings would reflect sunlight so brightly, we would barely have darkness. Stargazing would be almost impossible.",
             "image_prompt": "Nighttime landscape with planetary rings brightly illuminating the ground like a second moon, no stars visible, bright ring-light casting shadows, photorealistic, 9:16 vertical",
             "video_keywords": ["full moon bright night landscape", "moonrise timelapse city", "bright moon clouds night"]},
            {"narration": "Our sky would be the most beautiful sight in the solar system. But would we even notice after a while?",
             "image_prompt": "Person sitting alone on a hill watching massive planetary rings in the sky during twilight, peaceful contemplative scene, stunning cosmic vista, photorealistic cinematic, 9:16 vertical",
             "video_keywords": ["person silhouette watching sunset", "man hilltop overlooking valley", "woman stargazing night sky"]},
        ],
        "hashtags": "#whatif #saturn #earthrings #shorts #space #science #mindblown #viral",
    },
    {
        "id": "ocean_transparent",
        "title": "What If The Ocean Was Transparent?",
        "hook": "What if you could see EVERYTHING beneath the ocean?",
        "scenes": [
            {"narration": "Imagine standing on a beach and looking straight down through crystal clear water to the ocean floor, miles below.",
             "image_prompt": "Person standing on a glass-like transparent ocean surface, looking down at the deep ocean floor far below with underwater mountains and trenches visible, vertigo-inducing depth, photorealistic, 9:16 vertical",
             "video_keywords": ["crystal clear ocean water", "transparent sea underwater", "clear water beach aerial"]},
            {"narration": "Ships would sail over the Mariana Trench, the deepest point on Earth, and passengers would see the abyss staring back at them.",
             "image_prompt": "A cruise ship sailing over transparent water with the terrifyingly deep Mariana Trench visible far below, dark abyss underneath, dramatic scale contrast, photorealistic, 9:16 vertical",
             "video_keywords": ["cruise ship sailing ocean", "boat ocean waves aerial drone", "ferry sailing blue sea"]},
            {"narration": "Every shipwreck. Every creature of the deep. Giant squids, anglerfish, things we have never even discovered. All visible.",
             "image_prompt": "View through transparent ocean water showing a giant squid and ancient shipwreck on the ocean floor, bioluminescent creatures glowing, eerie deep sea scene from above, photorealistic, 9:16 vertical",
             "video_keywords": ["jellyfish underwater glowing", "coral reef fish colorful", "scuba diving underwater marine life"]},
            {"narration": "The ocean hides more mysteries than outer space. Maybe it is better we cannot see what is down there.",
             "image_prompt": "Dark mysterious transparent ocean view showing unknown massive shadowy creature shape in the deep abyss below, terrifying scale, person looking down from surface, photorealistic horror mood, 9:16 vertical",
             "video_keywords": ["whale underwater deep blue", "shark swimming underwater dark", "diver deep ocean blue"]},
        ],
        "hashtags": "#whatif #ocean #deepsea #shorts #science #mindblown #scary #viral",
    },
    {
        "id": "animals_talk",
        "title": "What If Animals Could Talk?",
        "hook": "What if your dog could actually TALK to you?",
        "scenes": [
            {"narration": "If animals could suddenly speak, the first thing your dog would probably say is: why do you leave me every single day?",
             "image_prompt": "Sad golden retriever sitting by a front door looking directly at camera with emotional human-like expression, tears in eyes, cozy home interior, emotional lighting, photorealistic, 9:16 vertical",
             "video_keywords": ["sad dog waiting door", "dog looking camera emotional", "lonely puppy home"]},
            {"narration": "Farms would shut down overnight. How do you eat a burger when the cow can beg you not to?",
             "image_prompt": "A cow behind a farm fence looking directly at camera with pleading expression, protest signs around the farm, dramatic emotional scene, photorealistic, 9:16 vertical",
             "video_keywords": ["cow farm close up face", "cattle farm fence field", "cow looking camera"]},
            {"narration": "Cats would absolutely become lawyers. They already judge everything you do. Now imagine them arguing their case.",
             "image_prompt": "A cat wearing tiny glasses sitting at a courtroom desk like a lawyer, serious expression, courtroom background, humorous but photorealistic, dramatic lighting, 9:16 vertical",
             "video_keywords": ["cat serious face close up", "funny cat portrait", "cat staring camera"]},
            {"narration": "If animals could talk, we would finally know the truth. And honestly, we might not be ready for it.",
             "image_prompt": "Multiple animals (dog, cat, bird, horse) standing together looking directly at camera with knowing expressions, dramatic group portrait, cinematic lighting, photorealistic, 9:16 vertical",
             "video_keywords": ["animals together group", "pets portrait multiple", "wildlife animals looking"]},
        ],
        "hashtags": "#whatif #animals #animalstalk #shorts #mindblown #funny #viral #imagination",
    },
    {
        "id": "sun_disappears_24h",
        "title": "What If The Sun Disappeared for 24 Hours?",
        "hook": "What if the sun just VANISHED for one day?",
        "scenes": [
            {"narration": "Eight minutes after the sun disappears, Earth goes dark. Not night-time dark. Absolute pitch black. No warmth. No light. Nothing.",
             "image_prompt": "Earth from space in complete darkness, no sunlight, only city lights visible on the surface, stars visible everywhere, terrifying cosmic void, photorealistic, 9:16 vertical",
             "video_keywords": ["earth from space night city lights", "stars timelapse night sky dark", "space station earth orbit view"]},
            {"narration": "Within hours, temperatures plummet below freezing everywhere on the planet. Plants begin dying. Panic spreads across every city.",
             "image_prompt": "Frozen city street with ice forming on buildings and cars, people in heavy winter clothes panicking, dark sky with no sun, emergency lights, dramatic photorealistic, 9:16 vertical",
             "video_keywords": ["blizzard snowstorm city street", "ice frozen waterfall winter", "people walking snow storm cold"]},
            {"narration": "Without the sun's gravity, Earth would drift off into space in a straight line. No orbit. Just floating into the void forever.",
             "image_prompt": "Earth floating alone in dark empty space, drifting away from where the sun used to be, tiny and alone in the cosmic void, terrifying scale, photorealistic, 9:16 vertical",
             "video_keywords": ["stars galaxy cosmos timelapse", "nebula space animation", "deep space stars moving"]},
            {"narration": "Twenty four hours later, the sun returns. But the damage is already done. The world would never be the same.",
             "image_prompt": "Sunrise over a frost-damaged devastated landscape, first rays of sunlight hitting frozen terrain, dramatic hope mixed with destruction, golden light breaking through, photorealistic cinematic, 9:16 vertical",
             "video_keywords": ["sunrise frozen landscape golden", "dramatic sunrise horizon", "dawn breaking ice frost"]},
        ],
        "hashtags": "#whatif #sun #space #shorts #science #scary #mindblown #viral",
    },
    {
        "id": "humans_50ft_tall",
        "title": "What If Humans Were 50 Feet Tall?",
        "hook": "What if humans were FIFTY FEET tall?",
        "scenes": [
            {"narration": "At fifty feet tall, you would tower over most buildings. Your footsteps would shake the ground. Cars would be the size of shoes.",
             "image_prompt": "A giant 50-foot human standing next to small buildings, looking down at tiny cars and normal-sized trees, dramatic scale comparison, city setting, photorealistic cinematic, 9:16 vertical",
             "video_keywords": ["looking down tall building aerial", "city buildings aerial view", "miniature city tilt shift"]},
            {"narration": "A single meal would require an entire farm's harvest. Drinking water? You would drain a swimming pool for breakfast.",
             "image_prompt": "Giant human hand reaching down to pick up an entire farm field of crops, farmers looking up in shock at the massive hand, dramatic perspective from below, photorealistic, 9:16 vertical",
             "video_keywords": ["aerial farmland harvest crops", "large scale farming field", "farm field drone view"]},
            {"narration": "Forget about houses. You would need buildings the size of stadiums just to sleep in. Doors the size of airplane hangars.",
             "image_prompt": "Enormous door on a massive building designed for giant humans, a normal-sized person standing next to it for scale showing how tiny they look, dramatic architecture, photorealistic, 9:16 vertical",
             "video_keywords": ["massive building architecture", "giant door entrance hall", "huge structure scale comparison"]},
            {"narration": "Being fifty feet tall sounds incredible. Until you realize you could never hug someone without crushing them.",
             "image_prompt": "A sad giant human sitting alone on a hillside looking down at a tiny normal-sized city with longing, emotional isolation theme, sunset lighting, photorealistic cinematic, 9:16 vertical",
             "video_keywords": ["person alone hilltop sunset", "lonely silhouette landscape", "solitude nature sunset view"]},
        ],
        "hashtags": "#whatif #giant #shorts #science #mindblown #scale #viral #imagination",
    },
    {
        "id": "trees_grew_instantly",
        "title": "What If Trees Grew in Seconds?",
        "hook": "What if a tree could grow from seed to full size in SECONDS?",
        "scenes": [
            {"narration": "Imagine dropping a seed on the ground and watching a massive tree explode upward in five seconds flat. Roots cracking through concrete. Branches smashing through power lines.",
             "image_prompt": "A massive tree bursting upward through a city sidewalk at incredible speed, concrete cracking, branches growing outward rapidly, motion blur on growing branches, dramatic action shot, photorealistic, 9:16 vertical",
             "video_keywords": ["tree roots breaking concrete", "plant growing timelapse fast", "nature vs urban city"]},
            {"narration": "Forests would spread across continents in hours. Deserts would turn green overnight. The entire planet would be covered in trees.",
             "image_prompt": "Aerial view of a desert landscape being rapidly overtaken by growing forest, green spreading across sand dunes, time-lapse effect, dramatic transformation, photorealistic, 9:16 vertical",
             "video_keywords": ["forest aerial view green lush", "drone forest landscape", "green landscape aerial nature"]},
            {"narration": "Cities would be destroyed in days. Buildings crumbled by roots. Roads split apart. Nature would take back everything.",
             "image_prompt": "City buildings being broken apart by rapidly growing tree roots and branches, nature reclaiming urban landscape, dramatic destruction scene, green overtaking concrete, photorealistic, 9:16 vertical",
             "video_keywords": ["abandoned building overgrown nature", "ruins overgrown vegetation", "urban decay plants trees"]},
            {"narration": "We spend years trying to save the forests. But what if the forests did not need saving? What if they just needed five seconds?",
             "image_prompt": "Beautiful lush green planet Earth from space completely covered in forests, no deserts visible, vibrant green world, hopeful cosmic view, photorealistic cinematic, 9:16 vertical",
             "video_keywords": ["earth from space green blue", "beautiful planet earth view", "earth nature space"]},
        ],
        "hashtags": "#whatif #trees #nature #shorts #science #mindblown #environment #viral",
    },
    {
        "id": "rain_colorful",
        "title": "What If Rain Was Colorful?",
        "hook": "What if every raindrop was a different COLOR?",
        "scenes": [
            {"narration": "Picture this. You look out your window and the rain is not clear. It is red, blue, purple, gold. A rainbow pouring from the sky.",
             "image_prompt": "Colorful multicolored rain falling on a city street, red blue purple gold raindrops, puddles of different colors on the ground, people with umbrellas looking up in wonder, vibrant dramatic lighting, photorealistic, 9:16 vertical",
             "video_keywords": ["rain city street neon lights", "rainy day city umbrella colorful", "rainfall urban night"]},
            {"narration": "Every storm would paint the world. Buildings stained in layers of color. Streets turned into living canvases.",
             "image_prompt": "City buildings covered in colorful rain stains, streaks of purple blue red paint-like marks running down walls, colorful puddles reflecting neon colors, artistic urban scene, photorealistic, 9:16 vertical",
             "video_keywords": ["colorful building walls paint", "street art urban vibrant", "painted walls colors city"]},
            {"narration": "Rivers would flow in technicolor. Waterfalls would look like liquid rainbows. The ocean would become an ever-changing masterpiece.",
             "image_prompt": "A waterfall flowing with multicolored water, rainbow liquid cascading down rocks, colorful mist rising, magical landscape, vibrant saturated colors, photorealistic cinematic, 9:16 vertical",
             "video_keywords": ["rainbow waterfall nature", "colorful waterfall cascade mist", "beautiful waterfall scenic"]},
            {"narration": "We complain about rainy days. But if rain looked like this, we would never want it to stop.",
             "image_prompt": "A child standing in colorful rain with arms spread wide, laughing, splashing in rainbow puddles, joyful scene, vibrant colors everywhere, magical atmosphere, photorealistic cinematic, 9:16 vertical",
             "video_keywords": ["child playing rain happy", "kid splashing puddles fun", "joyful rain dancing outdoor"]},
        ],
        "hashtags": "#whatif #colorfulrain #rainbow #shorts #mindblown #beautiful #viral #imagination",
    },
    {
        "id": "mirrors_show_future",
        "title": "What If Mirrors Showed Your Future?",
        "hook": "What if every mirror showed you YOUR FUTURE?",
        "scenes": [
            {"narration": "You walk past a mirror and instead of your reflection, you see yourself ten years from now. Older. Different. Maybe happier. Maybe not.",
             "image_prompt": "A young person looking into a bathroom mirror but the reflection shows an older version of themselves, two different ages facing each other, dramatic moody lighting, photorealistic, 9:16 vertical",
             "video_keywords": ["person looking mirror reflection", "mirror face close up dramatic", "bathroom mirror moody light"]},
            {"narration": "Some people would become addicted. Staring into mirrors for hours, desperate to see if their future gets better or worse.",
             "image_prompt": "Person sitting in a dark room surrounded by multiple mirrors all showing different future versions of themselves, obsessive atmosphere, eerie multiple reflections, dramatic dark lighting, photorealistic, 9:16 vertical",
             "video_keywords": ["dark corridor walking mysterious", "shadows dark room moody", "candle flickering dark room"]},
            {"narration": "Others would destroy every mirror they own. Terrified of what they might see. A world without reflections.",
             "image_prompt": "Shattered broken mirrors scattered on the floor, fragments showing glimpses of a dark future, person standing over the broken glass, dramatic shadows, photorealistic horror mood, 9:16 vertical",
             "video_keywords": ["glass shattering slow motion", "broken window shattered", "breaking glass fragments slow"]},
            {"narration": "Would you look? If you knew the mirror would show you exactly how your life ends, would you dare to look?",
             "image_prompt": "Close-up of a person's eyes reflected in a mirror fragment, the reflection showing a mysterious dark scene behind them, intense emotional close-up, dramatic cinematic lighting, photorealistic, 9:16 vertical",
             "video_keywords": ["woman face close up dramatic", "eye close up macro detail", "person thinking serious portrait"]},
        ],
        "hashtags": "#whatif #mirrors #future #shorts #mindblown #scary #deep #viral",
    },
    {
        "id": "everyone_same_salary",
        "title": "What If Everyone Earned The Same Salary?",
        "hook": "What if every single person on Earth earned the EXACT same salary?",
        "scenes": [
            {"narration": "If the world's total income was split equally, every person would earn about seventy dollars a day. For some, a massive raise. For others, a devastating cut.",
             "image_prompt": "Split image of a luxury penthouse and a modest simple apartment, equal sign between them, dramatic contrast of lifestyles becoming equal, cinematic lighting, photorealistic, 9:16 vertical",
             "video_keywords": ["luxury apartment penthouse view", "wealth contrast lifestyle", "rich vs poor housing"]},
            {"narration": "Would anyone still choose to be a surgeon? A garbage collector? If the pay is the same, who does the hard jobs?",
             "image_prompt": "A surgeon and a garbage collector standing side by side holding equal paychecks, same amount on both checks, dramatic portrait, equal status composition, photorealistic, 9:16 vertical",
             "video_keywords": ["different professions workers", "surgeon doctor hospital", "diverse jobs people working"]},
            {"narration": "Luxury brands would vanish overnight. No one could afford a Ferrari when everyone earns the same. Status symbols would disappear.",
             "image_prompt": "Abandoned luxury car dealership with expensive cars gathering dust, empty designer stores, faded luxury brand logos, eerie abandoned commercial district, photorealistic, 9:16 vertical",
             "video_keywords": ["luxury cars showroom empty", "abandoned shopping mall store", "empty retail closed shop"]},
            {"narration": "Equal pay sounds fair. But would it destroy ambition, or would it finally set people free?",
             "image_prompt": "Diverse group of people from different professions standing together as equals, unified peaceful scene, golden hour community gathering, hopeful atmosphere, photorealistic cinematic, 9:16 vertical",
             "video_keywords": ["diverse people community gathering", "multicultural group together", "people unity sunset golden"]},
        ],
        "hashtags": "#whatif #money #salary #shorts #economics #mindblown #society #viral",
    },
    {
        "id": "humans_cant_lie",
        "title": "What If Humans Could Not Lie?",
        "hook": "What if it was IMPOSSIBLE for humans to lie?",
        "scenes": [
            {"narration": "If lying became physically impossible, every conversation would become dangerously honest. No white lies. No excuses. Just raw truth.",
             "image_prompt": "Two people in an office meeting staring at each other intensely, truth-telling confrontation, transparent glass-like effect around their heads showing visible words, dramatic tense atmosphere, photorealistic, 9:16 vertical",
             "video_keywords": ["intense business meeting confrontation", "tense office conversation", "people staring serious meeting"]},
            {"narration": "Politicians would be finished. Every campaign promise would have to be real. Every scandal would be admitted instantly.",
             "image_prompt": "A politician at a podium sweating nervously, truth serum effect, audience gasping, dramatic political scene, TV cameras capturing the moment, photorealistic cinematic, 9:16 vertical",
             "video_keywords": ["politician speech podium", "political debate stage", "speaker podium press conference"]},
            {"narration": "Relationships would either become unbreakable or shatter in seconds. Does this outfit look good? You would finally get the real answer.",
             "image_prompt": "A couple having an intense emotional conversation, one person looking shocked by brutal honesty, dramatic living room scene, emotional tension visible, photorealistic, 9:16 vertical",
             "video_keywords": ["couple emotional conversation", "relationship argument discussion", "couple talking serious"]},
            {"narration": "A world without lies sounds perfect. But could humanity actually survive the truth?",
             "image_prompt": "A world map made of transparent glass, everything visible and exposed, no secrets, dramatic overhead view, concept of total transparency, photorealistic cinematic, 9:16 vertical",
             "video_keywords": ["glass transparent architecture", "crystal clear building modern", "transparent structure design"]},
        ],
        "hashtags": "#whatif #truth #nolies #shorts #mindblown #psychology #society #viral",
    },
]


def log(msg):
    ts = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    line = f'[{ts}] {msg}'
    print(line)
    try:
        with open(LOG_FILE, 'a') as f:
            f.write(line + '\n')
    except OSError:
        pass


# ---------------------------------------------------------------------------
# Database
# ---------------------------------------------------------------------------
def init_db():
    conn = sqlite3.connect(STATE_DB)
    conn.execute('''CREATE TABLE IF NOT EXISTS produced (
        id TEXT PRIMARY KEY,
        title TEXT,
        produced_at TEXT,
        output_file TEXT,
        duration_sec INTEGER
    )''')
    conn.execute('''CREATE TABLE IF NOT EXISTS runs (
        run_id INTEGER PRIMARY KEY AUTOINCREMENT,
        run_at TEXT,
        concept_id TEXT,
        status TEXT,
        note TEXT
    )''')
    conn.commit()
    return conn


def get_produced_ids(conn):
    cur = conn.execute('SELECT id FROM produced')
    return set(r[0] for r in cur.fetchall())


def record_run(conn, concept_id, status, note=''):
    conn.execute('INSERT INTO runs (run_at, concept_id, status, note) VALUES (?,?,?,?)',
                 (datetime.datetime.now().isoformat(), concept_id, status, note))
    conn.commit()


def pick_concept(conn):
    used = get_produced_ids(conn)
    available = [c for c in CONCEPTS if c['id'] not in used]
    if not available:
        log('All concepts used. Resetting pool.')
        available = CONCEPTS
    return random.choice(available)


# ---------------------------------------------------------------------------
# Selenium helpers
# ---------------------------------------------------------------------------
def get_driver():
    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options
    opts = Options()
    opts.add_experimental_option('debuggerAddress', '127.0.0.1:9222')
    return webdriver.Chrome(options=opts)


def ensure_chrome_running():
    """Make sure Chrome with debugging port is running."""
    import shutil
    result = subprocess.run(['pgrep', '-f', 'chrome.*whatif_profile.*9222'],
                            capture_output=True, text=True)
    if result.returncode == 0:
        log('Chrome already running with debugging port.')
        return True

    log('Starting Chrome with debugging port...')
    xvfb_running = subprocess.run(['pgrep', '-f', 'Xvfb :50'],
                                  capture_output=True, text=True)
    if xvfb_running.returncode != 0:
        subprocess.Popen(['Xvfb', ':50', '-screen', '0', '1920x1080x24'],
                         stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        time.sleep(2)

    env = os.environ.copy()
    env['DISPLAY'] = ':50'
    subprocess.Popen([
        'google-chrome', '--no-sandbox', '--disable-gpu',
        '--user-data-dir=' + CHROME_PROFILE,
        '--start-maximized',
        '--remote-debugging-port=9222',
        'about:blank',
    ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, env=env)
    time.sleep(5)
    log('Chrome started.')
    return True


def wait_for_download(download_dir, ext, timeout=120):
    """Wait for a file with given extension to appear in download_dir."""
    start = time.time()
    while time.time() - start < timeout:
        files = glob.glob(os.path.join(download_dir, f'*.{ext}'))
        crdownload = glob.glob(os.path.join(download_dir, '*.crdownload'))
        if files and not crdownload:
            newest = max(files, key=os.path.getmtime)
            if time.time() - os.path.getmtime(newest) < 30:
                return newest
        time.sleep(2)
    return None


def clear_downloads():
    """Remove old files from download directory."""
    for f in os.listdir(DOWNLOAD_DIR):
        fp = os.path.join(DOWNLOAD_DIR, f)
        if os.path.isfile(fp):
            os.remove(fp)


# ---------------------------------------------------------------------------
# ElevenLabs TTS
# ---------------------------------------------------------------------------
def generate_voiceover(script_text, out_mp3):
    """Use Selenium to generate TTS on ElevenLabs and download the MP3."""
    from selenium.webdriver.common.by import By
    from selenium.webdriver.common.keys import Keys
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC

    driver = get_driver()
    log('Navigating to ElevenLabs TTS...')

    # Make sure we're on the right tab / navigate
    driver.get('https://elevenlabs.io/app/speech-synthesis/text-to-speech')
    time.sleep(5)

    # Set download directory via CDP
    driver.execute_cdp_cmd('Page.setDownloadBehavior', {
        'behavior': 'allow',
        'downloadPath': DOWNLOAD_DIR,
    })

    # Get Firebase auth token for API downloads
    token = driver.execute_script('''
        var key = 'firebase:authUser:AIzaSyBSsRE_1Os04-bxpd5JTLIniy3UK4OqKys:[DEFAULT]';
        var authData = localStorage.getItem(key);
        return authData ? JSON.parse(authData).stsTokenManager.accessToken : null;
    ''')

    # Get latest history item ID before generating
    pre_history_id = None
    if token:
        try:
            req = urllib.request.Request(
                'https://api.us.elevenlabs.io/v1/history?page_size=1&source=TTS&sort_direction=desc',
                headers={'Authorization': f'Bearer {token}'})
            resp = urllib.request.urlopen(req)
            history = json.loads(resp.read())
            if history.get('history'):
                pre_history_id = history['history'][0]['history_item_id']
        except Exception:
            pass

    # Dismiss any modal dialogs (upsell, announcements, etc.)
    for _ in range(3):
        driver.execute_script('''
            var dialogs = document.querySelectorAll('[role="dialog"]');
            dialogs.forEach(function(d) {
                // Look for dismiss buttons by text content
                var btns = d.querySelectorAll('button');
                for (var b of btns) {
                    var t = b.textContent.toLowerCase();
                    if (t.includes('close') || t.includes('remind me later')
                        || t.includes('dismiss') || t.includes('not now')
                        || t.includes('maybe later') || t.includes('skip')) {
                        b.click();
                        return;
                    }
                }
                // Try aria-label close buttons
                var closeBtn = d.querySelector('button[aria-label*="lose"]');
                if (closeBtn) closeBtn.click();
            });
        ''')
        time.sleep(0.5)
    # Final escape key press
    from selenium.webdriver.common.action_chains import ActionChains
    ActionChains(driver).send_keys(Keys.ESCAPE).perform()
    time.sleep(0.5)

    # Use the data-testid textarea for reliable input
    try:
        textarea = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.CSS_SELECTOR,
                '[data-testid="tts-editor"], div[contenteditable="true"]'))
        )

        # Set text via React-compatible native value setter
        driver.execute_script('''
            var el = arguments[0];
            var text = arguments[1];
            var nativeSetter = Object.getOwnPropertyDescriptor(
                window.HTMLTextAreaElement.prototype, 'value').set;
            nativeSetter.call(el, text);
            el.dispatchEvent(new Event('input', {bubbles: true}));
            el.dispatchEvent(new Event('change', {bubbles: true}));
        ''', textarea, script_text)
        time.sleep(1)

        actual_len = driver.execute_script('''
            var el = arguments[0];
            return el.tagName === 'TEXTAREA' ? el.value.length : el.textContent.length;
        ''', textarea)
        log(f'Entered script text ({len(script_text)} chars, editor has {actual_len} chars)')
    except Exception as e:
        log(f'Could not find/fill text area: {e}')
        return False

    # Trigger generation via Ctrl+Enter (more reliable than button click)
    try:
        driver.execute_script('arguments[0].focus()', textarea)
        time.sleep(0.3)
        from selenium.webdriver.common.action_chains import ActionChains
        ActionChains(driver).key_down(Keys.CONTROL).send_keys(
            Keys.ENTER).key_up(Keys.CONTROL).perform()
        log('Triggered Generate via Ctrl+Enter')
    except Exception:
        gen_btn = driver.find_element(By.CSS_SELECTOR,
            '[data-testid="tts-generate"]')
        driver.execute_script('arguments[0].click()', gen_btn)
        log('Fallback: JS-clicked Generate')

    # Wait for new history item to appear (different from pre_history_id)
    log('Waiting for TTS generation...')
    time.sleep(10)

    new_item_id = None
    for attempt in range(30):
        if token:
            try:
                req = urllib.request.Request(
                    'https://api.us.elevenlabs.io/v1/history?page_size=1&source=TTS&sort_direction=desc',
                    headers={'Authorization': f'Bearer {token}'})
                resp = urllib.request.urlopen(req)
                history = json.loads(resp.read())
                if history.get('history'):
                    latest_id = history['history'][0]['history_item_id']
                    if latest_id != pre_history_id:
                        new_item_id = latest_id
                        log('New TTS audio ready in history.')
                        break
            except Exception:
                pass
        else:
            # No token - just wait for play button
            try:
                play_btns = driver.find_elements(By.CSS_SELECTOR,
                    'button[aria-label*="Play"], button[aria-label*="play"]')
                if play_btns:
                    log('Audio generation complete (play button visible).')
                    break
            except Exception:
                pass
        time.sleep(3)

    time.sleep(2)

    # Download via API
    if new_item_id and token:
        try:
            req = urllib.request.Request(
                f'https://api.us.elevenlabs.io/v1/history/{new_item_id}/audio',
                headers={'Authorization': f'Bearer {token}'})
            resp = urllib.request.urlopen(req)
            audio_data = resp.read()
            if len(audio_data) > 1000:
                with open(out_mp3, 'wb') as f:
                    f.write(audio_data)
                log(f'Voiceover saved via API ({len(audio_data)} bytes)')
                return True
        except Exception as e:
            log(f'API download failed: {e}')

    # Fallback: click download button in UI
    clear_downloads()
    try:
        download_btns = driver.find_elements(By.CSS_SELECTOR,
            'button[aria-label*="ownload"], [data-testid*="download"]')
        for btn in download_btns:
            try:
                btn.click()
                log('Clicked download button')
                time.sleep(5)
                mp3_file = wait_for_download(DOWNLOAD_DIR, 'mp3', timeout=30)
                if mp3_file:
                    subprocess.check_call(['cp', mp3_file, out_mp3])
                    log(f'Voiceover saved to {out_mp3}')
                    return True
            except Exception:
                continue
    except Exception:
        pass

    log('WARNING: Could not download voiceover. Will use silent audio.')
    return False


# ---------------------------------------------------------------------------
# Pollinations.ai Image Generation (HTTP GET, no browser needed)
# ---------------------------------------------------------------------------
def generate_image_pollinations(prompt, out_png):
    """Generate image via Pollinations.ai — free, unlimited, no login."""
    encoded = urllib.parse.quote(prompt)
    url = (f'https://image.pollinations.ai/prompt/{encoded}'
           f'?width=1080&height=1920&model=flux&nologo=true')
    log(f'Pollinations.ai: requesting image...')
    try:
        req = urllib.request.Request(url, headers={
            'User-Agent': 'Mozilla/5.0'
        })
        resp = urllib.request.urlopen(req, timeout=120)
        data = resp.read()
        if len(data) < 5000:
            log(f'Pollinations.ai: response too small ({len(data)} bytes)')
            return False
        with open(out_png, 'wb') as f:
            f.write(data)
        log(f'Pollinations.ai: saved {len(data)} bytes to {out_png}')
        return True
    except Exception as e:
        log(f'Pollinations.ai failed: {e}')
        return False


# ---------------------------------------------------------------------------
# Multi-source image generation orchestrator
# ---------------------------------------------------------------------------
IMAGE_SOURCES = [
    ('pollinations', generate_image_pollinations),
]

_failed_sources = set()


def generate_image_multi(prompt, out_png):
    """Try image sources in order until one succeeds."""
    for name, func in IMAGE_SOURCES:
        if name in _failed_sources:
            log(f'Skipping {name} (failed earlier this run)')
            continue
        try:
            log(f'Trying image source: {name}')
            success = func(prompt, out_png)
            if success and os.path.exists(out_png) and os.path.getsize(out_png) > 5000:
                log(f'Image generated successfully via {name}')
                return True
            else:
                log(f'{name}: generation returned but file missing or too small')
        except Exception as e:
            log(f'{name}: exception — {e}')
            _failed_sources.add(name)

    log('All image sources failed')
    return False


# ---------------------------------------------------------------------------
# Pexels Stock Video (HTTP API, no browser needed)
# ---------------------------------------------------------------------------
def generate_video_pexels(keywords, out_mp4, used_ids, orientation='portrait'):
    """Search Pexels for a stock video clip and download it."""
    if not PEXELS_API_KEY:
        log('Pexels: no API key configured')
        return False
    for kw in keywords:
        params = urllib.parse.urlencode({
            'query': kw, 'orientation': orientation,
            'size': 'medium', 'per_page': '15',
        })
        url = f'https://api.pexels.com/videos/search?{params}'
        req = urllib.request.Request(url, headers={
            'Authorization': PEXELS_API_KEY,
            'User-Agent': 'Mozilla/5.0',
        })
        try:
            resp = urllib.request.urlopen(req, timeout=15)
            data = json.loads(resp.read())
        except Exception as e:
            log(f'Pexels: API error for "{kw}": {e}')
            continue

        videos = data.get('videos', [])
        candidates = [v for v in videos
                      if v['id'] not in used_ids and v.get('duration', 0) >= 4]
        if not candidates:
            log(f'Pexels: no results for "{kw}" ({orientation})')
            continue

        chosen = random.choice(candidates[:5])
        video_files = [f for f in chosen.get('video_files', [])
                       if f.get('width', 0) >= 720 and f.get('file_type') == 'video/mp4']
        video_files.sort(key=lambda f: f.get('width', 9999))
        if not video_files:
            continue

        dl_url = video_files[0]['link']
        try:
            dl_req = urllib.request.Request(dl_url, headers={'User-Agent': 'Mozilla/5.0'})
            dl_resp = urllib.request.urlopen(dl_req, timeout=60)
            video_data = dl_resp.read()
            if len(video_data) < 50000:
                log(f'Pexels: file too small ({len(video_data)} bytes)')
                continue
            with open(out_mp4, 'wb') as f:
                f.write(video_data)
            used_ids.add(chosen['id'])
            log(f'Pexels: saved video {chosen["id"]} ({len(video_data)/1024/1024:.1f} MB) for "{kw}"')
            return True
        except Exception as e:
            log(f'Pexels: download failed: {e}')
            continue
    return False


# ---------------------------------------------------------------------------
# Pixabay Stock Video (HTTP API, no browser needed)
# ---------------------------------------------------------------------------
def generate_video_pixabay(keywords, out_mp4, used_ids, orientation='vertical'):
    """Search Pixabay for a stock video clip and download it."""
    if not PIXABAY_API_KEY:
        log('Pixabay: no API key configured')
        return False
    for kw in keywords:
        params = urllib.parse.urlencode({
            'key': PIXABAY_API_KEY, 'q': kw,
            'orientation': orientation, 'per_page': '15',
            'safesearch': 'true',
        })
        url = f'https://pixabay.com/api/videos/?{params}'
        req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
        try:
            resp = urllib.request.urlopen(req, timeout=15)
            data = json.loads(resp.read())
        except Exception as e:
            log(f'Pixabay: API error for "{kw}": {e}')
            continue

        hits = data.get('hits', [])
        candidates = [h for h in hits
                      if h['id'] not in used_ids and h.get('duration', 0) >= 4]
        if not candidates:
            log(f'Pixabay: no results for "{kw}" ({orientation})')
            continue

        chosen = random.choice(candidates[:5])
        video_info = chosen.get('videos', {})
        dl_url = None
        for quality in ('medium', 'small', 'large'):
            entry = video_info.get(quality, {})
            if entry.get('url'):
                dl_url = entry['url']
                break
        if not dl_url:
            continue

        try:
            dl_req = urllib.request.Request(dl_url, headers={'User-Agent': 'Mozilla/5.0'})
            dl_resp = urllib.request.urlopen(dl_req, timeout=60)
            video_data = dl_resp.read()
            if len(video_data) < 50000:
                log(f'Pixabay: file too small')
                continue
            with open(out_mp4, 'wb') as f:
                f.write(video_data)
            used_ids.add(chosen['id'])
            log(f'Pixabay: saved video {chosen["id"]} ({len(video_data)/1024/1024:.1f} MB) for "{kw}"')
            return True
        except Exception as e:
            log(f'Pixabay: download failed: {e}')
            continue
    return False


# ---------------------------------------------------------------------------
# Multi-source video generation orchestrator
# ---------------------------------------------------------------------------
_failed_video_sources = set()
_used_pexels_ids = set()
_used_pixabay_ids = set()


def generate_video_multi(keywords, out_mp4):
    """Try stock video sources. Portrait first, then landscape + center-crop."""
    if not keywords:
        return False

    # Pass 1: portrait / vertical orientation
    sources_portrait = [
        ('pexels', generate_video_pexels, _used_pexels_ids, 'portrait'),
        ('pixabay', generate_video_pixabay, _used_pixabay_ids, 'vertical'),
    ]
    for name, func, ids, orient in sources_portrait:
        if name in _failed_video_sources:
            log(f'Skipping video source {name} (failed earlier)')
            continue
        try:
            log(f'Trying video source: {name} ({orient})')
            success = func(keywords, out_mp4, ids, orientation=orient)
            if success and os.path.exists(out_mp4) and os.path.getsize(out_mp4) > 50000:
                log(f'Video obtained via {name} ({orient})')
                return True
        except Exception as e:
            log(f'{name}: exception — {e}')
            _failed_video_sources.add(name)

    # Pass 2: landscape / horizontal (will be center-cropped by prepare_video_clip)
    sources_landscape = [
        ('pexels', generate_video_pexels, _used_pexels_ids, 'landscape'),
        ('pixabay', generate_video_pixabay, _used_pixabay_ids, 'horizontal'),
    ]
    for name, func, ids, orient in sources_landscape:
        if name in _failed_video_sources:
            continue
        try:
            log(f'Trying video source: {name} ({orient})')
            success = func(keywords, out_mp4, ids, orientation=orient)
            if success and os.path.exists(out_mp4) and os.path.getsize(out_mp4) > 50000:
                log(f'Video obtained via {name} ({orient}, will center-crop)')
                return True
        except Exception as e:
            log(f'{name}: exception — {e}')
            _failed_video_sources.add(name)

    return False


# ---------------------------------------------------------------------------
# Fallback: generate a styled placeholder image with ImageMagick
# ---------------------------------------------------------------------------
def generate_placeholder_image(text, out_png, bg_color='#0a0a2e'):
    """Create a styled placeholder image when AI generation fails."""
    wrapped = textwrap.fill(text[:100], width=25)
    cmd = [
        'convert', '-size', f'{WIDTH}x{HEIGHT}',
        f'xc:{bg_color}',
        '-font', FONT_TITLE, '-pointsize', '60',
        '-fill', 'white', '-gravity', 'center',
        '-annotate', '+0+0', wrapped,
        out_png
    ]
    subprocess.check_call(cmd)
    log(f'Placeholder image created: {out_png}')


# ---------------------------------------------------------------------------
# Video clip preparation (trim/scale to match scene duration)
# ---------------------------------------------------------------------------
def prepare_video_clip(input_mp4, duration, out_mp4, crop_watermark=False):
    """Scale, trim, and add fades to a video clip for scene use."""
    filters = []
    if crop_watermark:
        filters.append('crop=iw:ih*0.95:0:0')
    filters.extend([
        f'scale={WIDTH}:{HEIGHT}:force_original_aspect_ratio=increase',
        f'crop={WIDTH}:{HEIGHT}',
        f'fade=in:0:d=0.3,fade=out:st={duration-0.3}:d=0.3',
    ])
    subprocess.check_call([
        'ffmpeg', '-y', '-i', input_mp4,
        '-vf', ','.join(filters),
        '-c:v', 'libx264', '-pix_fmt', 'yuv420p',
        '-an', '-t', str(duration),
        out_mp4,
    ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)


# ---------------------------------------------------------------------------
# FFmpeg video assembly
# ---------------------------------------------------------------------------
def get_audio_duration(audio_path):
    """Get duration of an audio file in seconds."""
    result = subprocess.run([
        'ffprobe', '-v', 'quiet', '-print_format', 'json',
        '-show_format', audio_path
    ], capture_output=True, text=True)
    data = json.loads(result.stdout)
    return float(data['format']['duration'])


def generate_silent_audio(duration, out_wav):
    """Generate silent audio of given duration."""
    sample_rate = 44100
    n_samples = int(sample_rate * duration)
    samples = array.array('h', [0] * n_samples)
    with wave.open(out_wav, 'w') as wf:
        wf.setnchannels(1)
        wf.setsampwidth(2)
        wf.setframerate(sample_rate)
        wf.writeframes(samples.tobytes())


def build_scene_clip(image_path, duration, out_mp4, zoom_direction='in'):
    """Create a Ken Burns effect clip from a single image."""
    if zoom_direction == 'in':
        zoompan = (f"zoompan=z='min(zoom+0.0008,1.3)':x='iw/2-(iw/zoom/2)':"
                   f"y='ih/2-(ih/zoom/2)':d={int(duration*FPS)}:s={WIDTH}x{HEIGHT}:fps={FPS}")
    elif zoom_direction == 'out':
        zoompan = (f"zoompan=z='if(eq(on,1),1.3,max(zoom-0.0008,1.0))':x='iw/2-(iw/zoom/2)':"
                   f"y='ih/2-(ih/zoom/2)':d={int(duration*FPS)}:s={WIDTH}x{HEIGHT}:fps={FPS}")
    else:
        # Pan left to right
        zoompan = (f"zoompan=z='1.15':x='iw*0.15*(on/{int(duration*FPS)})':"
                   f"y='ih/2-(ih/zoom/2)':d={int(duration*FPS)}:s={WIDTH}x{HEIGHT}:fps={FPS}")

    subprocess.check_call([
        'ffmpeg', '-y',
        '-i', image_path,
        '-vf', f'scale=2160x3840,{zoompan},fade=in:0:d=0.3,fade=out:st={duration-0.3}:d=0.3',
        '-c:v', 'libx264', '-pix_fmt', 'yuv420p',
        '-t', str(duration),
        out_mp4,
    ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)


def build_hook_clip(title, duration, out_mp4):
    """Build the opening hook frame with animated text."""
    workdir = tempfile.mkdtemp(prefix='hook_')
    frame_path = os.path.join(workdir, 'hook.png')

    # Create a dramatic dark background with the hook text
    title_escaped = title.replace("'", "'")
    cmd = [
        'convert', '-size', f'{WIDTH}x{HEIGHT}',
        'xc:#0a0a2e',
        # Gradient overlay
        '(', '-size', f'{WIDTH}x{HEIGHT}',
        '-define', 'gradient:angle=180',
        'gradient:#1a0a3e-#0a0a2e', ')',
        '-compose', 'over', '-composite',
        # Main title text
        '-font', FONT_TITLE, '-pointsize', '72',
        '-fill', '#FFD700', '-gravity', 'center',
        '-annotate', '+0-100', 'WHAT IF...',
        # Question text
        '-font', FONT_BODY, '-pointsize', '48',
        '-fill', 'white', '-gravity', 'center',
        '-annotate', '+0+50', textwrap.fill(title, width=22),
        # Channel branding
        '-font', FONT_BODY, '-pointsize', '28',
        '-fill', '#888888', '-gravity', 'south',
        '-annotate', '+0+80', '@WhatIfWorld',
        frame_path
    ]
    subprocess.check_call(cmd)

    # Convert to video with fade in
    subprocess.check_call([
        'ffmpeg', '-y',
        '-loop', '1', '-i', frame_path,
        '-vf', f'fade=in:0:d=0.5,fade=out:st={duration-0.3}:d=0.3',
        '-c:v', 'libx264', '-pix_fmt', 'yuv420p',
        '-t', str(duration),
        out_mp4,
    ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

    return frame_path  # Keep for thumbnail


def build_endcard_clip(duration, out_mp4):
    """Build the end card with CTA."""
    workdir = tempfile.mkdtemp(prefix='end_')
    frame_path = os.path.join(workdir, 'endcard.png')

    cmd = [
        'convert', '-size', f'{WIDTH}x{HEIGHT}',
        'xc:#0a0a2e',
        '-font', FONT_TITLE, '-pointsize', '64',
        '-fill', '#FFD700', '-gravity', 'center',
        '-annotate', '+0-100', 'WHAT DO YOU THINK?',
        '-font', FONT_BODY, '-pointsize', '40',
        '-fill', 'white', '-gravity', 'center',
        '-annotate', '+0+20', 'Comment below!',
        '-font', FONT_BODY, '-pointsize', '36',
        '-fill', '#FF4444', '-gravity', 'center',
        '-annotate', '+0+120', 'FOLLOW for more What If',
        '-font', FONT_BODY, '-pointsize', '28',
        '-fill', '#888888', '-gravity', 'south',
        '-annotate', '+0+80', '@WhatIfWorld',
        frame_path
    ]
    subprocess.check_call(cmd)

    subprocess.check_call([
        'ffmpeg', '-y',
        '-loop', '1', '-i', frame_path,
        '-vf', f'fade=in:0:d=0.3',
        '-c:v', 'libx264', '-pix_fmt', 'yuv420p',
        '-t', str(duration),
        out_mp4,
    ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)


def secs_to_ass_time(s):
    """Convert seconds to ASS timestamp format H:MM:SS.CC"""
    h = int(s // 3600)
    m = int((s % 3600) // 60)
    sec = int(s % 60)
    cs = int((s % 1) * 100)
    return f'{h}:{m:02d}:{sec:02d}.{cs:02d}'


def build_ass_file(events, ass_path):
    """Build an ASS subtitle file with styled captions.

    events: list of (start_sec, end_sec, text, fontsize) tuples
    """
    header = f"""[Script Info]
ScriptType: v4.00+
PlayResX: {WIDTH}
PlayResY: {HEIGHT}
WrapStyle: 0

[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Caption,Inter,44,&H00FFFFFF,&H000000FF,&H00000000,&H80000000,1,0,0,0,100,100,0,0,1,3,1,2,40,40,200,1
Style: Hook,Oswald,52,&H00FFFFFF,&H000000FF,&H00000000,&H80000000,1,0,0,0,100,100,0,0,1,3,1,2,40,40,200,1

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
"""
    lines = []
    for start, end, text, fontsize in events:
        style = 'Hook' if fontsize > 44 else 'Caption'
        ass_text = text.replace('\n', '\\N')
        t_start = secs_to_ass_time(start)
        t_end = secs_to_ass_time(end)
        lines.append(
            f'Dialogue: 0,{t_start},{t_end},{style},,0,0,0,,{ass_text}')

    with open(ass_path, 'w') as f:
        f.write(header)
        f.write('\n'.join(lines))
        f.write('\n')


def concat_clips(clip_paths, out_mp4):
    """Concatenate multiple video clips."""
    workdir = tempfile.mkdtemp(prefix='concat_')
    concat_txt = os.path.join(workdir, 'concat.txt')
    with open(concat_txt, 'w') as f:
        for path in clip_paths:
            f.write(f"file '{path}'\n")

    subprocess.check_call([
        'ffmpeg', '-y', '-f', 'concat', '-safe', '0',
        '-i', concat_txt,
        '-c:v', 'libx264', '-pix_fmt', 'yuv420p',
        out_mp4,
    ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)


def mux_audio_video(video_mp4, audio_path, out_mp4):
    """Combine video with audio track."""
    subprocess.check_call([
        'ffmpeg', '-y',
        '-i', video_mp4, '-i', audio_path,
        '-c:v', 'copy', '-c:a', 'aac', '-b:a', '128k',
        '-shortest',
        out_mp4,
    ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)


def build_thumbnail(hook_frame, concept, out_jpg):
    """Build a YouTube thumbnail."""
    title_short = concept['title'].replace('What If ', '').replace('?', '?!')
    cmd = [
        'convert', '-size', f'{WIDTH}x{HEIGHT}',
        'xc:#0a0a2e',
        '-font', FONT_TITLE, '-pointsize', '80',
        '-fill', '#FFD700', '-gravity', 'center',
        '-annotate', '+0-200', 'WHAT IF',
        '-font', FONT_TITLE, '-pointsize', '60',
        '-fill', 'white', '-gravity', 'center',
        '-annotate', '+0+0', textwrap.fill(title_short, width=18),
        '-font', FONT_BODY, '-pointsize', '48',
        '-fill', '#FF4444', '-gravity', 'center',
        '-annotate', '+0+250', '???',
        '-quality', '90',
        out_jpg,
    ]
    subprocess.check_call(cmd)


# ---------------------------------------------------------------------------
# Main production pipeline
# ---------------------------------------------------------------------------
def produce_short(conn, concept, dry_run=False):
    """Full pipeline: script -> voiceover -> stock video -> assembly -> output."""
    log(f'=== Producing: {concept["title"]} ===')
    workdir = tempfile.mkdtemp(prefix='whatif_')
    log(f'Workdir: {workdir}')

    # Combine all narrations into one script, keeping it under ~50 seconds
    # (~130 words at 150 wpm for shorts under 60s with hook/endcard)
    scene_narrations = [s['narration'][:100] for s in concept['scenes']]
    full_script = concept['hook'] + ' ' + ' '.join(scene_narrations)
    if len(full_script) > 700:
        full_script = full_script[:700]

    # Step 1: Generate voiceover
    voiceover_mp3 = os.path.join(workdir, 'voiceover.mp3')
    has_voice = generate_voiceover(full_script, voiceover_mp3)

    if has_voice:
        total_duration = get_audio_duration(voiceover_mp3)
        log(f'Voiceover duration: {total_duration:.1f}s')
    else:
        total_duration = 30.0
        silent_wav = os.path.join(workdir, 'silent.wav')
        generate_silent_audio(total_duration, silent_wav)
        subprocess.check_call([
            'ffmpeg', '-y', '-i', silent_wav, voiceover_mp3,
        ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

    # Calculate scene timing
    n_scenes = len(concept['scenes'])
    hook_dur = 3.0
    endcard_dur = 3.0
    scene_dur = max(4.0, (total_duration - hook_dur - endcard_dur) / n_scenes)
    total_duration = hook_dur + (scene_dur * n_scenes) + endcard_dur

    # Step 2: Generate scene clips (try stock video first, fall back to image+Ken Burns)
    _failed_video_sources.clear()
    _failed_sources.clear()
    _used_pexels_ids.clear()
    _used_pixabay_ids.clear()
    zoom_directions = ['in', 'out', 'pan', 'in']

    log('Building scene clips...')
    clips = []

    # Hook clip
    hook_mp4 = os.path.join(workdir, 'clip_hook.mp4')
    hook_frame = build_hook_clip(concept['title'], hook_dur, hook_mp4)
    clips.append(hook_mp4)

    for i, scene in enumerate(concept['scenes']):
        clip_mp4 = os.path.join(workdir, f'clip_scene_{i}.mp4')
        log(f'Scene {i+1}/{n_scenes}: trying stock video...')

        # Try stock video first
        raw_video = os.path.join(workdir, f'scene_{i}_raw.mp4')
        video_keywords = scene.get('video_keywords', [])
        video_ok = generate_video_multi(video_keywords, raw_video) if video_keywords else False

        if video_ok:
            log(f'Scene {i+1}: got stock video, preparing clip...')
            try:
                prepare_video_clip(raw_video, scene_dur, clip_mp4)
                clips.append(clip_mp4)
                continue
            except Exception as e:
                log(f'Scene {i+1}: video prep failed ({e}), falling back to image')

        # Fallback: generate image + Ken Burns
        log(f'Scene {i+1}: falling back to image + Ken Burns')
        img_path = os.path.join(workdir, f'scene_{i}.png')
        success = generate_image_multi(scene['image_prompt'], img_path)
        if not success or not os.path.exists(img_path):
            log(f'Image generation failed for scene {i+1}, using placeholder')
            generate_placeholder_image(scene['narration'][:80], img_path)

        resized = os.path.join(workdir, f'scene_{i}_resized.png')
        subprocess.check_call([
            'convert', img_path + '[0]',
            '-resize', f'{WIDTH}x{HEIGHT}^',
            '-gravity', 'center',
            '-extent', f'{WIDTH}x{HEIGHT}',
            resized,
        ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        zoom = zoom_directions[i % len(zoom_directions)]
        build_scene_clip(resized, scene_dur, clip_mp4, zoom_direction=zoom)
        clips.append(clip_mp4)
        time.sleep(2)

    # End card
    endcard_mp4 = os.path.join(workdir, 'clip_endcard.mp4')
    build_endcard_clip(endcard_dur, endcard_mp4)
    clips.append(endcard_mp4)

    # Step 4: Concatenate all clips
    silent_video = os.path.join(workdir, 'silent_video.mp4')
    concat_clips(clips, silent_video)

    # Step 5: Add captions via ASS subtitles
    captioned_video = os.path.join(workdir, 'captioned_video.mp4')
    ass_path = os.path.join(workdir, 'captions.ass')

    ass_events = []
    t = hook_dur
    hook_wrapped = textwrap.fill(concept['hook'], width=30)
    ass_events.append((0.5, hook_dur, hook_wrapped, 52))

    for i, scene in enumerate(concept['scenes']):
        start = t
        end = t + scene_dur
        narr = scene['narration'][:120]
        narr_wrapped = textwrap.fill(narr, width=32)
        ass_events.append((start, end, narr_wrapped, 38))
        t = end

    build_ass_file(ass_events, ass_path)

    subprocess.check_call([
        'ffmpeg', '-y', '-i', silent_video,
        '-vf', f"ass={ass_path}",
        '-c:v', 'libx264', '-pix_fmt', 'yuv420p',
        captioned_video,
    ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

    # Step 6: Mux with audio
    final_mp4 = os.path.join(workdir, 'final.mp4')
    mux_audio_video(captioned_video, voiceover_mp3, final_mp4)

    # Step 7: Output
    if dry_run:
        log(f'[DRY RUN] Video at: {final_mp4}')
        duration_sec = get_audio_duration(final_mp4) if os.path.exists(final_mp4) else 0
        print(f'\n===== DRY RUN OUTPUT =====')
        print(f'Concept: {concept["id"]}')
        print(f'Title: {concept["title"]}')
        print(f'Duration: {duration_sec:.1f}s')
        print(f'MP4: {final_mp4}')
        return 'dry-run'

    # Copy to output directory
    cur = conn.execute('SELECT COUNT(*) FROM produced')
    num = cur.fetchone()[0] + 1
    base = f'short_{num:04d}'

    out_mp4 = os.path.join(OUTPUT_DIR, f'{base}.mp4')
    out_txt = os.path.join(OUTPUT_DIR, f'{base}.txt')
    out_thumb = os.path.join(OUTPUT_DIR, f'{base}_thumb.jpg')

    subprocess.check_call(['cp', final_mp4, out_mp4])

    # Build thumbnail
    try:
        build_thumbnail(hook_frame, concept, out_thumb)
    except Exception as e:
        log(f'Thumbnail failed: {e}')

    # Get final duration
    duration_sec = int(get_audio_duration(out_mp4))

    # Write metadata
    with open(out_txt, 'w') as f:
        f.write(f'=== What If Short #{num} ===\n')
        f.write(f'Concept: {concept["id"]}\n')
        f.write(f'Produced: {datetime.datetime.now().isoformat()}\n')
        f.write(f'Duration: {duration_sec}s\n\n')
        f.write(f'--- TITLE ---\n{concept["title"]} #shorts\n\n')
        f.write(f'--- DESCRIPTION ---\n')
        f.write(f'{concept["hook"]}\n\n')
        f.write(f'{concept["hashtags"]}\n\n')
        f.write(f'--- THUMBNAIL ---\n{base}_thumb.jpg\n\n')
        f.write(f'--- SCRIPT ---\n')
        for i, scene in enumerate(concept['scenes']):
            f.write(f'Scene {i+1}: {scene["narration"]}\n')

    # Record in database
    conn.execute(
        'INSERT INTO produced (id, title, produced_at, output_file, duration_sec) '
        'VALUES (?,?,?,?,?)',
        (concept['id'], concept['title'], datetime.datetime.now().isoformat(),
         base, duration_sec)
    )
    conn.commit()

    log(f'=== DONE: {out_mp4} ({duration_sec}s) ===')
    return base


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
    parser = argparse.ArgumentParser(description='What-If Shorts Bot')
    parser.add_argument('--dry-run', action='store_true',
                        help='Build one video without writing to output/ or DB')
    parser.add_argument('--concept', default=None,
                        help='Force a specific concept ID')
    args = parser.parse_args()

    conn = init_db()
    concept_id = 'unknown'

    try:
        ensure_chrome_running()

        if args.concept:
            matches = [c for c in CONCEPTS if c['id'] == args.concept]
            if not matches:
                print(f'Unknown concept: {args.concept}')
                print(f'Available: {", ".join(c["id"] for c in CONCEPTS)}')
                return 1
            concept = matches[0]
        else:
            concept = pick_concept(conn)

        concept_id = concept['id']
        log(f'Selected concept: {concept_id} — {concept["title"]}')

        base = produce_short(conn, concept, dry_run=args.dry_run)
        if not args.dry_run:
            record_run(conn, concept_id, 'ok', base)
        return 0

    except Exception as e:
        log(f'ERROR: {e}')
        log(traceback.format_exc())
        try:
            record_run(conn, concept_id, 'error', str(e))
        except sqlite3.Error:
            pass
        return 1
    finally:
        conn.close()


if __name__ == '__main__':
    sys.exit(main())
